Repository: quartz-scheduler/quartz Branch: main Commit: 9294eac4c3b6 Files: 475 Total size: 3.0 MB Directory structure: gitextract_68hlgy34/ ├── .gitattributes ├── .github/ │ ├── dependabot.yml │ ├── pull_request_template.md │ └── stale.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── azure-pipelines.yml ├── build.gradle ├── checkstyle/ │ ├── README.txt │ └── src/ │ └── main/ │ └── resources/ │ ├── checkstyle.xml │ ├── header.txt │ └── suppressions.xml ├── docs/ │ └── index.md ├── examples/ │ ├── build.gradle │ ├── examples_guide.txt │ └── src/ │ └── main/ │ ├── java/ │ │ └── org/ │ │ └── quartz/ │ │ └── examples/ │ │ ├── example1/ │ │ │ ├── HelloJob.java │ │ │ └── SimpleExample.java │ │ ├── example10/ │ │ │ ├── PlugInExample.java │ │ │ └── SimpleJob.java │ │ ├── example11/ │ │ │ ├── LoadExample.java │ │ │ └── SimpleJob.java │ │ ├── example12/ │ │ │ ├── RemoteClientExample.java │ │ │ ├── RemoteServerExample.java │ │ │ └── SimpleJob.java │ │ ├── example13/ │ │ │ ├── ClusterExample.java │ │ │ ├── SimpleRecoveryJob.java │ │ │ └── SimpleRecoveryStatefulJob.java │ │ ├── example14/ │ │ │ ├── PriorityExample.java │ │ │ └── TriggerEchoJob.java │ │ ├── example15/ │ │ │ ├── NativeJob.java │ │ │ └── NativeJobExample.java │ │ ├── example2/ │ │ │ ├── SimpleJob.java │ │ │ └── SimpleTriggerExample.java │ │ ├── example3/ │ │ │ ├── CronTriggerExample.java │ │ │ └── SimpleJob.java │ │ ├── example4/ │ │ │ ├── ColorJob.java │ │ │ └── JobStateExample.java │ │ ├── example5/ │ │ │ ├── MisfireExample.java │ │ │ └── StatefulDumbJob.java │ │ ├── example6/ │ │ │ ├── BadJob1.java │ │ │ ├── BadJob2.java │ │ │ └── JobExceptionExample.java │ │ ├── example7/ │ │ │ ├── DumbInterruptableJob.java │ │ │ └── InterruptExample.java │ │ ├── example8/ │ │ │ ├── CalendarExample.java │ │ │ └── SimpleJob.java │ │ └── example9/ │ │ ├── Job1Listener.java │ │ ├── ListenerExample.java │ │ ├── SimpleJob1.java │ │ └── SimpleJob2.java │ └── resources/ │ ├── log4j.xml │ └── org/ │ └── quartz/ │ └── examples/ │ ├── example10/ │ │ ├── quartz.properties │ │ └── quartz_data.xml │ ├── example11/ │ │ └── quartz.properties │ ├── example12/ │ │ ├── client.properties │ │ └── server.properties │ ├── example13/ │ │ ├── instance1.properties │ │ └── instance2.properties │ ├── example14/ │ │ └── quartz_priority.properties │ └── example5/ │ └── quartz_misfire.properties ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── quartz/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── org/ │ │ │ │ └── quartz/ │ │ │ │ ├── Calendar.java │ │ │ │ ├── CalendarIntervalScheduleBuilder.java │ │ │ │ ├── CalendarIntervalTrigger.java │ │ │ │ ├── CronExpression.java │ │ │ │ ├── CronScheduleBuilder.java │ │ │ │ ├── CronTrigger.java │ │ │ │ ├── DailyTimeIntervalScheduleBuilder.java │ │ │ │ ├── DailyTimeIntervalTrigger.java │ │ │ │ ├── DateBuilder.java │ │ │ │ ├── DisallowConcurrentExecution.java │ │ │ │ ├── ExecuteInJTATransaction.java │ │ │ │ ├── InterruptableJob.java │ │ │ │ ├── Job.java │ │ │ │ ├── JobBuilder.java │ │ │ │ ├── JobDataMap.java │ │ │ │ ├── JobDetail.java │ │ │ │ ├── JobExecutionContext.java │ │ │ │ ├── JobExecutionException.java │ │ │ │ ├── JobKey.java │ │ │ │ ├── JobListener.java │ │ │ │ ├── JobPersistenceException.java │ │ │ │ ├── ListenerManager.java │ │ │ │ ├── Matcher.java │ │ │ │ ├── ObjectAlreadyExistsException.java │ │ │ │ ├── PersistJobDataAfterExecution.java │ │ │ │ ├── ScheduleBuilder.java │ │ │ │ ├── Scheduler.java │ │ │ │ ├── SchedulerConfigException.java │ │ │ │ ├── SchedulerContext.java │ │ │ │ ├── SchedulerException.java │ │ │ │ ├── SchedulerFactory.java │ │ │ │ ├── SchedulerListener.java │ │ │ │ ├── SchedulerMetaData.java │ │ │ │ ├── SimpleScheduleBuilder.java │ │ │ │ ├── SimpleTrigger.java │ │ │ │ ├── StatefulJob.java │ │ │ │ ├── TimeOfDay.java │ │ │ │ ├── Trigger.java │ │ │ │ ├── TriggerBuilder.java │ │ │ │ ├── TriggerKey.java │ │ │ │ ├── TriggerListener.java │ │ │ │ ├── TriggerUtils.java │ │ │ │ ├── UnableToInterruptJobException.java │ │ │ │ ├── commonj/ │ │ │ │ │ └── WorkManagerThreadExecutor.java │ │ │ │ ├── core/ │ │ │ │ │ ├── JobExecutionProcessException.java │ │ │ │ │ ├── JobRunShell.java │ │ │ │ │ ├── JobRunShellFactory.java │ │ │ │ │ ├── ListenerManagerImpl.java │ │ │ │ │ ├── NullSampledStatisticsImpl.java │ │ │ │ │ ├── QuartzScheduler.java │ │ │ │ │ ├── QuartzSchedulerMBeanImpl.java │ │ │ │ │ ├── QuartzSchedulerResources.java │ │ │ │ │ ├── QuartzSchedulerThread.java │ │ │ │ │ ├── RemotableQuartzScheduler.java │ │ │ │ │ ├── SampledStatistics.java │ │ │ │ │ ├── SampledStatisticsImpl.java │ │ │ │ │ ├── SchedulerSignalerImpl.java │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── CronTriggerSupport.java │ │ │ │ │ │ ├── JobDataMapSupport.java │ │ │ │ │ │ ├── JobDetailSupport.java │ │ │ │ │ │ ├── JobExecutionContextSupport.java │ │ │ │ │ │ ├── QuartzSchedulerMBean.java │ │ │ │ │ │ ├── SimpleTriggerSupport.java │ │ │ │ │ │ └── TriggerSupport.java │ │ │ │ │ ├── mbeans-descriptors.xml │ │ │ │ │ └── package.html │ │ │ │ ├── ee/ │ │ │ │ │ ├── jboss/ │ │ │ │ │ │ └── doc-files/ │ │ │ │ │ │ └── quartz-service.xml │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ └── jboss/ │ │ │ │ │ │ ├── JBoss4RMIRemoteMBeanScheduler.java │ │ │ │ │ │ ├── QuartzService.java │ │ │ │ │ │ └── QuartzServiceMBean.java │ │ │ │ │ ├── jta/ │ │ │ │ │ │ ├── JTAAnnotationAwareJobRunShellFactory.java │ │ │ │ │ │ ├── JTAJobRunShell.java │ │ │ │ │ │ ├── JTAJobRunShellFactory.java │ │ │ │ │ │ └── UserTransactionHelper.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── QuartzInitializerListener.java │ │ │ │ │ └── QuartzInitializerServlet.java │ │ │ │ ├── helpers/ │ │ │ │ │ ├── VersionPrinter.java │ │ │ │ │ └── package.html │ │ │ │ ├── impl/ │ │ │ │ │ ├── DefaultThreadExecutor.java │ │ │ │ │ ├── DirectSchedulerFactory.java │ │ │ │ │ ├── JobDetailImpl.java │ │ │ │ │ ├── JobExecutionContextImpl.java │ │ │ │ │ ├── QuartzServer.java │ │ │ │ │ ├── RemoteMBeanScheduler.java │ │ │ │ │ ├── RemoteScheduler.java │ │ │ │ │ ├── SchedulerDetailsSetter.java │ │ │ │ │ ├── SchedulerRepository.java │ │ │ │ │ ├── StdJobRunShellFactory.java │ │ │ │ │ ├── StdScheduler.java │ │ │ │ │ ├── StdSchedulerFactory.java │ │ │ │ │ ├── calendar/ │ │ │ │ │ │ ├── AnnualCalendar.java │ │ │ │ │ │ ├── BaseCalendar.java │ │ │ │ │ │ ├── CronCalendar.java │ │ │ │ │ │ ├── DailyCalendar.java │ │ │ │ │ │ ├── HolidayCalendar.java │ │ │ │ │ │ ├── MonthlyCalendar.java │ │ │ │ │ │ └── WeeklyCalendar.java │ │ │ │ │ ├── jdbcjobstore/ │ │ │ │ │ │ ├── AttributeRestoringConnectionInvocationHandler.java │ │ │ │ │ │ ├── CUBRIDDelegate.java │ │ │ │ │ │ ├── CacheDelegate.java │ │ │ │ │ │ ├── CalendarIntervalTriggerPersistenceDelegate.java │ │ │ │ │ │ ├── Constants.java │ │ │ │ │ │ ├── CronTriggerPersistenceDelegate.java │ │ │ │ │ │ ├── DB2v6Delegate.java │ │ │ │ │ │ ├── DB2v7Delegate.java │ │ │ │ │ │ ├── DB2v8Delegate.java │ │ │ │ │ │ ├── DBSemaphore.java │ │ │ │ │ │ ├── DailyTimeIntervalTriggerPersistenceDelegate.java │ │ │ │ │ │ ├── DriverDelegate.java │ │ │ │ │ │ ├── FiredTriggerRecord.java │ │ │ │ │ │ ├── GaussDBDelegate.java │ │ │ │ │ │ ├── HSQLDBDelegate.java │ │ │ │ │ │ ├── InvalidConfigurationException.java │ │ │ │ │ │ ├── JTANonClusteredSemaphore.java │ │ │ │ │ │ ├── JobStoreCMT.java │ │ │ │ │ │ ├── JobStoreSupport.java │ │ │ │ │ │ ├── JobStoreTX.java │ │ │ │ │ │ ├── LockException.java │ │ │ │ │ │ ├── MSSQLDelegate.java │ │ │ │ │ │ ├── NoRecordFoundException.java │ │ │ │ │ │ ├── NoSuchDelegateException.java │ │ │ │ │ │ ├── PointbaseDelegate.java │ │ │ │ │ │ ├── PostgreSQLDelegate.java │ │ │ │ │ │ ├── SchedulerStateRecord.java │ │ │ │ │ │ ├── Semaphore.java │ │ │ │ │ │ ├── SimplePropertiesTriggerPersistenceDelegateSupport.java │ │ │ │ │ │ ├── SimplePropertiesTriggerProperties.java │ │ │ │ │ │ ├── SimpleSemaphore.java │ │ │ │ │ │ ├── SimpleTriggerPersistenceDelegate.java │ │ │ │ │ │ ├── StdJDBCConstants.java │ │ │ │ │ │ ├── StdJDBCDelegate.java │ │ │ │ │ │ ├── StdRowLockSemaphore.java │ │ │ │ │ │ ├── SybaseDelegate.java │ │ │ │ │ │ ├── TablePrefixAware.java │ │ │ │ │ │ ├── TriggerPersistenceDelegate.java │ │ │ │ │ │ ├── TriggerStatus.java │ │ │ │ │ │ ├── UpdateLockRowSemaphore.java │ │ │ │ │ │ ├── Util.java │ │ │ │ │ │ ├── WebLogicDelegate.java │ │ │ │ │ │ └── oracle/ │ │ │ │ │ │ ├── OracleDelegate.java │ │ │ │ │ │ └── weblogic/ │ │ │ │ │ │ └── WebLogicOracleDelegate.java │ │ │ │ │ ├── matchers/ │ │ │ │ │ │ ├── AndMatcher.java │ │ │ │ │ │ ├── EverythingMatcher.java │ │ │ │ │ │ ├── GroupMatcher.java │ │ │ │ │ │ ├── KeyMatcher.java │ │ │ │ │ │ ├── NameMatcher.java │ │ │ │ │ │ ├── NotMatcher.java │ │ │ │ │ │ ├── OrMatcher.java │ │ │ │ │ │ └── StringMatcher.java │ │ │ │ │ ├── package.html │ │ │ │ │ └── triggers/ │ │ │ │ │ ├── AbstractTrigger.java │ │ │ │ │ ├── CalendarIntervalTriggerImpl.java │ │ │ │ │ ├── CoreTrigger.java │ │ │ │ │ ├── CronTriggerImpl.java │ │ │ │ │ ├── DailyTimeIntervalTriggerImpl.java │ │ │ │ │ ├── SimpleTriggerImpl.java │ │ │ │ │ └── package.html │ │ │ │ ├── listeners/ │ │ │ │ │ ├── BroadcastJobListener.java │ │ │ │ │ ├── BroadcastSchedulerListener.java │ │ │ │ │ ├── BroadcastTriggerListener.java │ │ │ │ │ ├── JobChainingJobListener.java │ │ │ │ │ ├── JobListenerSupport.java │ │ │ │ │ ├── SchedulerListenerSupport.java │ │ │ │ │ └── TriggerListenerSupport.java │ │ │ │ ├── management/ │ │ │ │ │ ├── ManagementRESTServiceConfiguration.java │ │ │ │ │ └── ManagementServer.java │ │ │ │ ├── package.html │ │ │ │ ├── plugins/ │ │ │ │ │ ├── SchedulerPluginWithUserTransactionSupport.java │ │ │ │ │ ├── history/ │ │ │ │ │ │ ├── LoggingJobHistoryPlugin.java │ │ │ │ │ │ └── LoggingTriggerHistoryPlugin.java │ │ │ │ │ ├── interrupt/ │ │ │ │ │ │ └── JobInterruptMonitorPlugin.java │ │ │ │ │ ├── management/ │ │ │ │ │ │ └── ShutdownHookPlugin.java │ │ │ │ │ └── xml/ │ │ │ │ │ ├── FileScanJob.java │ │ │ │ │ ├── FileScanListener.java │ │ │ │ │ └── XMLSchedulingDataProcessorPlugin.java │ │ │ │ ├── simpl/ │ │ │ │ │ ├── CascadingClassLoadHelper.java │ │ │ │ │ ├── HostnameInstanceIdGenerator.java │ │ │ │ │ ├── InitThreadContextClassLoadHelper.java │ │ │ │ │ ├── LoadingLoaderClassLoadHelper.java │ │ │ │ │ ├── PropertySettingJobFactory.java │ │ │ │ │ ├── RAMJobStore.java │ │ │ │ │ ├── SimpleClassLoadHelper.java │ │ │ │ │ ├── SimpleInstanceIdGenerator.java │ │ │ │ │ ├── SimpleJobFactory.java │ │ │ │ │ ├── SimpleThreadPool.java │ │ │ │ │ ├── SimpleTimeBroker.java │ │ │ │ │ ├── SystemPropertyInstanceIdGenerator.java │ │ │ │ │ ├── ThreadContextClassLoadHelper.java │ │ │ │ │ ├── ZeroSizeThreadPool.java │ │ │ │ │ └── package.html │ │ │ │ ├── spi/ │ │ │ │ │ ├── ClassLoadHelper.java │ │ │ │ │ ├── InstanceIdGenerator.java │ │ │ │ │ ├── JobFactory.java │ │ │ │ │ ├── JobStore.java │ │ │ │ │ ├── MutableTrigger.java │ │ │ │ │ ├── OperableTrigger.java │ │ │ │ │ ├── SchedulerPlugin.java │ │ │ │ │ ├── SchedulerSignaler.java │ │ │ │ │ ├── ThreadExecutor.java │ │ │ │ │ ├── ThreadPool.java │ │ │ │ │ ├── TimeBroker.java │ │ │ │ │ ├── TriggerFiredBundle.java │ │ │ │ │ ├── TriggerFiredResult.java │ │ │ │ │ └── package.html │ │ │ │ ├── utils/ │ │ │ │ │ ├── C3p0PoolingConnectionProvider.java │ │ │ │ │ ├── CircularLossyQueue.java │ │ │ │ │ ├── ClassUtils.java │ │ │ │ │ ├── ConnectionProvider.java │ │ │ │ │ ├── DBConnectionManager.java │ │ │ │ │ ├── DirtyFlagMap.java │ │ │ │ │ ├── FindbugsSuppressWarnings.java │ │ │ │ │ ├── HikariCpPoolingConnectionProvider.java │ │ │ │ │ ├── JNDIConnectionProvider.java │ │ │ │ │ ├── Key.java │ │ │ │ │ ├── PoolingConnectionProvider.java │ │ │ │ │ ├── PropertiesParser.java │ │ │ │ │ ├── StringKeyDirtyFlagMap.java │ │ │ │ │ ├── counter/ │ │ │ │ │ │ ├── Counter.java │ │ │ │ │ │ ├── CounterConfig.java │ │ │ │ │ │ ├── CounterImpl.java │ │ │ │ │ │ ├── CounterManager.java │ │ │ │ │ │ ├── CounterManagerImpl.java │ │ │ │ │ │ └── sampled/ │ │ │ │ │ │ ├── SampledCounter.java │ │ │ │ │ │ ├── SampledCounterConfig.java │ │ │ │ │ │ ├── SampledCounterImpl.java │ │ │ │ │ │ ├── SampledRateCounter.java │ │ │ │ │ │ ├── SampledRateCounterConfig.java │ │ │ │ │ │ ├── SampledRateCounterImpl.java │ │ │ │ │ │ └── TimeStampedCounterValue.java │ │ │ │ │ └── weblogic/ │ │ │ │ │ └── WeblogicConnectionProvider.java │ │ │ │ └── xml/ │ │ │ │ ├── ValidationException.java │ │ │ │ └── XMLSchedulingDataProcessor.java │ │ │ └── overview.html │ │ └── resources/ │ │ ├── checkstyle.xml │ │ └── org/ │ │ └── quartz/ │ │ ├── core/ │ │ │ └── quartz-build.properties │ │ ├── impl/ │ │ │ └── jdbcjobstore/ │ │ │ ├── liquibase.quartz.init.xml │ │ │ ├── tables_cloudscape.sql │ │ │ ├── tables_cubrid.sql │ │ │ ├── tables_db2.sql │ │ │ ├── tables_db2_v72.sql │ │ │ ├── tables_db2_v8.sql │ │ │ ├── tables_db2_v95.sql │ │ │ ├── tables_derby.sql │ │ │ ├── tables_derby_previous.sql │ │ │ ├── tables_firebird.sql │ │ │ ├── tables_gauss_default_compatibility.sql │ │ │ ├── tables_gauss_m_compatibility.sql │ │ │ ├── tables_h2.sql │ │ │ ├── tables_hsqldb.sql │ │ │ ├── tables_hsqldb_old.sql │ │ │ ├── tables_informix.sql │ │ │ ├── tables_mysql.sql │ │ │ ├── tables_mysql_innodb.sql │ │ │ ├── tables_oracle.sql │ │ │ ├── tables_oracle23.sql │ │ │ ├── tables_pointbase.sql │ │ │ ├── tables_postgres.sql │ │ │ ├── tables_sapdb.sql │ │ │ ├── tables_solid.sql │ │ │ ├── tables_sqlServer.sql │ │ │ └── tables_sybase.sql │ │ ├── quartz.properties │ │ └── xml/ │ │ ├── job_scheduling_data_1_8.xsd │ │ └── job_scheduling_data_2_0.xsd │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── quartz/ │ │ ├── AbstractJobStoreTest.java │ │ ├── AbstractSchedulerTest.java │ │ ├── AnnualCalendarTest.java │ │ ├── CalendarIntervalTriggerTest.java │ │ ├── CronExpressionTest.java │ │ ├── CronScheduleBuilderTest.java │ │ ├── CronTriggerTest.java │ │ ├── DailyTimeIntervalScheduleBuilderTest.java │ │ ├── DateBuilderTest.java │ │ ├── DefaultSchedulerTest.java │ │ ├── DisallowConcurrentExecutionJobTest.java │ │ ├── FlakyJdbcSchedulerTest.java │ │ ├── InterruptableJobTest.java │ │ ├── JdbcSchedulerTest.java │ │ ├── JobBuilderTest.java │ │ ├── JobDataMapTest.java │ │ ├── JobDetailTest.java │ │ ├── MonthlyCalendarTest.java │ │ ├── PriorityTest.java │ │ ├── Qtz205SchedulerListenerTest.java │ │ ├── Quartz601Test.java │ │ ├── RAMSchedulerTest.java │ │ ├── SerializationTestSupport.java │ │ ├── SimpleTriggerTest.java │ │ ├── TriggerBuilderTest.java │ │ ├── TriggerComparatorTest.java │ │ ├── VersionTest.java │ │ ├── core/ │ │ │ ├── ListenerManagerTest.java │ │ │ ├── QTZ212_SchedulerListener_Test.java │ │ │ ├── QTZ385Test.java │ │ │ └── RecoverJobsTest.java │ │ ├── impl/ │ │ │ ├── DirectSchedulerFactoryTest.java │ │ │ ├── JobDetailImplTest.java │ │ │ ├── MockConnectionProvider.java │ │ │ ├── RemoteMBeanSchedulerTest.java │ │ │ ├── SchedulerDetailsSetterTest.java │ │ │ ├── StdSchedulerFactoryCustomConnectionProviderTest.java │ │ │ ├── StdSchedulerFactoryTest.java │ │ │ ├── calendar/ │ │ │ │ ├── BaseCalendarTest.java │ │ │ │ └── DailyCalendarTest.java │ │ │ ├── jdbcjobstore/ │ │ │ │ ├── DeleteNonExistsJobTest.java │ │ │ │ ├── GaussDBDelegateTest.java │ │ │ │ ├── JdbcJobStoreTest.java │ │ │ │ ├── JdbcQuartzTestUtilities.java │ │ │ │ ├── MSSQLJdbcStoreTest.java │ │ │ │ ├── MariaDBJdbcStoreTest.java │ │ │ │ ├── PostgresJdbcStoreTest.java │ │ │ │ ├── StdJDBCDelegateTest.java │ │ │ │ └── UpdateLockRowSemaphoreTest.java │ │ │ ├── matchers/ │ │ │ │ └── GroupMatcherTest.java │ │ │ └── triggers/ │ │ │ └── DailyTimeIntervalTriggerImplTest.java │ │ ├── integrations/ │ │ │ └── tests/ │ │ │ ├── HelloJob.java │ │ │ ├── JdbcQuartzDerbyUtilities.java │ │ │ ├── JobClassNotFoundExceptionErrorsTriggersTest.java │ │ │ ├── JobDataMapStorageTest.java │ │ │ ├── QTZ179_TriggerLostAfterDbRestart_Test.java │ │ │ ├── QTZ283_IgnoreMisfirePolicyJdbcStore_Test.java │ │ │ ├── QTZ336_MissSchedulingChangeSignalTest.java │ │ │ ├── QuartzDatabasePauseAndResumeTest.java │ │ │ ├── QuartzDatabaseSimplePropertiesTest.java │ │ │ ├── QuartzDerbyCronTriggerTest.java │ │ │ ├── QuartzDerbyTestSupport.java │ │ │ ├── QuartzMSSQLDatabaseCronTriggerTest.java │ │ │ ├── QuartzMSSQLTestSupport.java │ │ │ ├── QuartzMemoryCronTriggerTest.java │ │ │ ├── QuartzMemoryPauseAndResumeTest.java │ │ │ ├── QuartzMemoryTestSupport.java │ │ │ ├── StdRowLockSemaphoreTest.java │ │ │ └── TrackingJob.java │ │ ├── simpl/ │ │ │ ├── PropertySettingJobFactoryTest.java │ │ │ ├── RAMJobStoreTest.java │ │ │ └── SystemPropertyInstanceIdGeneratorTest.java │ │ ├── utils/ │ │ │ ├── C3p0PoolingConnectionProviderTest.java │ │ │ ├── CircularLossyQueueTest.java │ │ │ ├── ClassUtilsTest.java │ │ │ ├── DirtyFlagMapTest.java │ │ │ ├── HikariCpPoolingConnectionProviderTest.java │ │ │ └── PropertiesParserTest.java │ │ └── xml/ │ │ ├── XMLSchedulingDataProcessorPluginTest.java │ │ └── XMLSchedulingDataProcessorTest.java │ └── resources/ │ ├── container-license-acceptance.txt │ ├── log4j.properties │ ├── org/ │ │ └── quartz/ │ │ ├── AnnualCalendar-1.5.1.ser │ │ ├── CalendarIntervalTriggerImpl-2.0.ser │ │ ├── CronExpression-1.5.2.ser │ │ ├── CronTrigger-1.5.2.ser │ │ ├── CronTriggerImpl-2.0.ser │ │ ├── JobDataMap-1.4.5.ser │ │ ├── JobDataMap-1.5.1.ser │ │ ├── JobDataMap-2.1.ser │ │ ├── SimpleTrigger-1.5.2.ser │ │ ├── SimpleTriggerImpl-2.0.ser │ │ ├── impl/ │ │ │ └── calendar/ │ │ │ └── DailyCalendar-1.5.2.ser │ │ ├── properties/ │ │ │ └── quartzCustomConnectionProvider.properties │ │ └── xml/ │ │ ├── bad-job-config.xml │ │ ├── delete-no-jobclass.xml │ │ ├── directives_no-overwrite_ignoredups.xml │ │ ├── directives_overwrite_no-ignoredups.xml │ │ ├── job-scheduling-data-2.0_trigger-samples.xml │ │ ├── overwrite-no-jobclass.xml │ │ ├── quartz-test.properties │ │ ├── quartz-xml-plugin-test.properties │ │ ├── simple-job-trigger-no-repeat.xml │ │ ├── simple-job-trigger-with-timezones.xml │ │ └── simple-job-trigger.xml │ └── tables_derby_drop.sql ├── quartz-jobs/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── quartz/ │ │ └── jobs/ │ │ ├── DirectoryScanJob.java │ │ ├── DirectoryScanListener.java │ │ ├── FileScanJob.java │ │ ├── FileScanListener.java │ │ ├── NoOpJob.java │ │ └── ee/ │ │ ├── ejb/ │ │ │ ├── EJB3InvokerJob.java │ │ │ └── EJBInvokerJob.java │ │ ├── jms/ │ │ │ ├── JmsHelper.java │ │ │ ├── JmsJobException.java │ │ │ ├── JmsMessageFactory.java │ │ │ ├── SendDestinationMessageJob.java │ │ │ ├── SendQueueMessageJob.java │ │ │ └── SendTopicMessageJob.java │ │ ├── jmx/ │ │ │ └── JMXInvokerJob.java │ │ └── mail/ │ │ └── SendMailJob.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── quartz/ │ │ ├── integrations/ │ │ │ └── tests/ │ │ │ ├── AutoInterruptableJobTest.java │ │ │ ├── DummyClassLoadHelper.java │ │ │ ├── HelloJob.java │ │ │ └── QTZ225_SchedulerClassLoadHelperForPlugins_Test.java │ │ └── jobs/ │ │ ├── MyJobListener.java │ │ ├── SendMailJobAuthTestBase.java │ │ ├── SendMailJobFakeAuth.java │ │ ├── SendMailJobRealAuth.java │ │ ├── SendMailJobTest.java │ │ └── SimpleValidator.java │ └── resources/ │ ├── log4j.properties │ └── org/ │ └── quartz/ │ └── tests/ │ └── QTZ225/ │ ├── log4j.xml │ ├── quartz.properties │ └── quartz_data.xml ├── quartz-stubs/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ ├── oracle/ │ │ └── sql/ │ │ └── BLOB.java │ ├── org/ │ │ └── quartz/ │ │ └── jobs/ │ │ ├── DirectoryScanListener.java │ │ └── FileScanListener.java │ └── weblogic/ │ └── jdbc/ │ ├── jts/ │ │ └── Driver.java │ └── vendor/ │ └── oracle/ │ └── OracleThinBlob.java ├── readme.adoc └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # # https://help.github.com/articles/dealing-with-line-endings/ # # Linux start script should use lf /gradlew text eol=lf # These are Windows script files and should use crlf *.bat text eol=crlf ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "gradle" # See documentation for possible values directory: "/" schedule: interval: "daily" target-branch: main ignore: - dependency-name: "org.mockito:mockito-core" - package-ecosystem: "gradle" # See documentation for possible values directory: "/" schedule: interval: "daily" target-branch: quartz-2.4.x ignore: - dependency-name: "org.mockito:mockito-core" ================================================ FILE: .github/pull_request_template.md ================================================ This PR... Fixes issue # ## Changes - ----------------- ## Checklist - [ ] tested locally - [ ] updated the docs - [ ] added appropriate test - [ ] signed-off on the DCO referenced in the CONTRIBUTING link below via `git commit -s` on my commits, and submit this code under terms of the Apache 2.0 license and assign copyright to the Quartz project owners (If you're not using command-line, you can use a [browser extension](https://github.com/scottrigby/dco-gh-ui) ) ----------------- In submitting this contribution, I agree to the terms of contributing as referred to here: https://github.com/quartz-scheduler/contributing/blob/main/CONTRIBUTING.md ================================================ FILE: .github/stale.yml ================================================ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 90 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 10 # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) onlyLabels: [] # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - todo # Set to true to ignore issues in a project (defaults to false) exemptProjects: false # Set to true to ignore issues in a milestone (defaults to false) exemptMilestones: true # Set to true to ignore issues with an assignee (defaults to false) exemptAssignees: false # Label to use when marking as stale staleLabel: stale # Comment to post when marking as stale. Set to `false` to disable markComment: > Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward? This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. # Comment to post when removing the stale label. # unmarkComment: > # Your comment here. # Comment to post when closing a stale Issue or Pull Request. # closeComment: > # Your comment here. # Limit the number of actions per hour, from 1-30. Default is 30 limitPerRun: 30 # Limit to only `issues` or `pulls` # only: issues # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': # pulls: # daysUntilStale: 30 # markComment: > # This pull request has been automatically marked as stale because it has not had # recent activity. It will be closed if no further activity occurs. Thank you # for your contributions. # issues: # exemptLabels: # - confirmed ================================================ FILE: .gitignore ================================================ # gradle .gradle/ **/build/ !**/src/**/build # misplaced test artifacts derby.log logs/ tmp/ # IntelliJ IDEA noise out/ .idea/ *.iml *.ipr *.iws # Eclipse IDE noise .settings/ .project .classpath # NetBeans noise nbproject/ # Other Editor's noise *~ *.swp # MacOSX noise .DS_Store # Ignore Gradle project-specific cache directory .gradle # Ignore Gradle build output directory build ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to the Quartz Scheduler Project _We're happy to have you participate in our community with your contributions!_ Thanks for your interest in contributing to the to the Quartz Scheduler Project. Start your journey by reading here: [Contributing](https://github.com/quartz-scheduler/contributing/blob/main/CONTRIBUTING.md) ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: azure-pipelines.yml ================================================ # # Copyright Terracotta, Inc. # Copyright IBM Corp. 2024, 2025 # # 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. # # See shared code location for steps and parameters: # https://dev.azure.com/TerracottaCI/_git/terracotta resources: repositories: - repository: templates type: git name: terracotta/terracotta jobs: - template: build-templates/gradle-common.yml@templates parameters: jobName: Java11 jdkVersion: '1.11' options: '-Dtest.databasePort=1527' - template: build-templates/gradle-common.yml@templates parameters: jobName: Java17 jdkVersion: '1.17' options: '-Dtest.databasePort=1527' - template: build-templates/gradle-common.yml@templates parameters: jobName: Java21 jdkVersion: '1.21' options: '-Dtest.databasePort=1527' ================================================ FILE: build.gradle ================================================ import org.gradle.api.publish.maven.internal.publication.MavenPomInternal; import org.gradle.api.publish.maven.internal.publisher.MavenPublicationCoordinates; import static org.gradle.api.publish.plugins.PublishingPlugin.PUBLISH_TASK_GROUP; plugins { // This adds tasks to auto close or release nexus staging repos // see https://github.com/gradle-nexus/publish-plugin/ id "io.github.gradle-nexus.publish-plugin" } allprojects { group 'org.quartz-scheduler' version quartzVersion } subprojects { repositories { mavenCentral() } } allprojects { plugins.withType(JavaBasePlugin) { java { toolchain { languageVersion = JavaLanguageVersion.of(11) } } } plugins.withType(MavenPublishPlugin) { plugins.apply(SigningPlugin) signing { required { !version.endsWith("SNAPSHOT") } sign publishing.publications } publishing { configurations { provided runtimeClasspath.extendsFrom(provided) } publications { maven(MavenPublication) { pom { name = "$project.name" url = 'https://www.quartz-scheduler.org/' licenses { license { name = 'The Apache License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } scm { connection = 'scm:git:git://github.com:quartz-scheduler/quartz.git' developerConnection = 'scm:git:ssh:git@github.com:quartz-scheduler/quartz.git' url = 'https://github.com/quartz-scheduler/quartz' } developers { developer { name = "Terracotta Engineers" email = "tc-oss@wwpdl.vnet.ibm.com" organization = "IBM Corp." organizationUrl = "http://www.quartz-scheduler.org/" } } } from components.java pom.withXml { asNode() .dependencies .dependency .findAll { dependency -> // Patch up dependency scopes per expectations of maven users (dependency.groupId.text() == 'com.mchange' || dependency.groupId.text() == 'com.zaxxer' || dependency.artifactId.text() == 'slf4j-log4j12' || dependency.groupId.text() == 'jakarta.xml' ) } .each { dependency -> // Set scope value to compile. dependency.scope*.value = 'provided' } } } } } // for MANIFEST.MF jar { manifest { attributes( "Implementation-Title": project.name, "Implementation-Vendor-Id": project.group, "Implementation-Version": project.version, "Built-By": System.getProperty("user.name"), "Built-JDK": System.getProperty("java.version"), "Automatic-Module-Name": 'org.quartz', "-removeheaders": 'Private-Package', "Export-Package": 'org.quartz.*', "Import-Package": '*;resolution:=optional' ) } } } // Put pom.xml and pom.properties in the jar: project.afterEvaluate{p -> // module.json publishing confuses sonatype and it doesn't process the jar. tasks.withType(GenerateModuleMetadata) { enabled = false } tasks.withType(GenerateMavenPom) {pomTask -> MavenPublicationCoordinates coordinates = ((MavenPomInternal) pomTask.getPom()).getCoordinates() TaskProvider pomPropertiesTask = project.getTasks().register(pomTask.getName().replace("PomFile", "PomProperties"), WriteProperties.class, task -> { task.dependsOn(pomTask); task.setGroup(PUBLISH_TASK_GROUP); task.setOutputFile(new File(pomTask.getDestination().getParentFile(), "pom.properties")); task.property("groupId", coordinates.getGroupId()); task.property("artifactId", coordinates.getArtifactId()); task.property("version", coordinates.getVersion()); }); project.tasks.withType(Jar).configureEach{jar -> jar.into("META-INF/maven/" + coordinates.getGroupId().get() + "/" + coordinates.getArtifactId().get(), spec -> { spec.from(pomTask, pom -> pom.rename(".*", "pom.xml")); spec.from(pomPropertiesTask); }); } } } } nexusPublishing { repositories { sonatype{ nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) } } } ================================================ FILE: checkstyle/README.txt ================================================ Checkstyle configuration files can be found under src/main/resources 1. You can edit checkstyle rules in checkstyle.xml 2. You can suppress a rule for any files in suppressions.xml 3. You can suppress a rule in a source file with comment: // CHECKSTYLE_OFF: NameOfTheCheck|OtherTheCheck { code } // CHECKSTYLE_ON: NameOfTheCheck|OtherTheCheck ================================================ FILE: checkstyle/src/main/resources/checkstyle.xml ================================================ ================================================ FILE: checkstyle/src/main/resources/header.txt ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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. * */ ================================================ FILE: checkstyle/src/main/resources/suppressions.xml ================================================ ================================================ FILE: docs/index.md ================================================ # Quartz Documentation Quartz Documentation for various releases can be found on the Quartz website: [Quartz Documentation](https://www.quartz-scheduler.org/documentation/)

The source files for the documentation on the site can be found here: [Doc Source](https://github.com/quartz-scheduler/quartz-scheduler.org-site/tree/master/documentation) ================================================ FILE: examples/build.gradle ================================================ plugins { id 'java' } dependencies { implementation project(':quartz') implementation project(':quartz-jobs') implementation "org.slf4j:slf4j-api:$slf4jVersion" implementation "org.slf4j:slf4j-log4j12:$slf4jVersion" } tasks.withType(JavaExec).configureEach { classpath = sourceSets.main.runtimeClasspath group = JavaBasePlugin.DOCUMENTATION_GROUP } tasks.register('runExample1', JavaExec) { description = 'Run Example 1' mainClass = 'org.quartz.examples.example1.SimpleExample' } tasks.register('runExample2', JavaExec) { description = 'Run Example 2' mainClass = 'org.quartz.examples.example2.SimpleTriggerExample' } tasks.register('runExample3', JavaExec) { description = 'Run Example 3' mainClass = 'org.quartz.examples.example3.CronTriggerExample' } tasks.register('runExample4', JavaExec) { description = 'Run Example 4' mainClass = 'org.quartz.examples.example4.JobStateExample' } tasks.register('runExample5', JavaExec) { description = 'Run Example 5' mainClass = 'org.quartz.examples.example5.MisfireExample' } tasks.register('runExample6', JavaExec) { description = 'Run Example 6' mainClass = 'org.quartz.examples.example6.JobExceptionExample' } tasks.register('runExample7', JavaExec) { description = 'Run Example 7' mainClass = 'org.quartz.examples.example7.InterruptExample' } tasks.register('runExample8', JavaExec) { description = 'Run Example 8' mainClass = 'org.quartz.examples.example8.CalendarExample' } tasks.register('runExample9', JavaExec) { description = 'Run Example 9' mainClass = 'org.quartz.examples.example9.ListenerExample' } tasks.register('runEExample10', JavaExec) { description = 'Run Example 10' mainClass = 'org.quartz.examples.example10.PlugInExample' systemProperty('org.quartz.properties', 'org/quartz/examples/example10/quartz.properties') } tasks.register('runExample11', JavaExec) { description = 'Run Example 11' mainClass = 'org.quartz.examples.example11.LoadExample' systemProperty 'org.quartz.properties', 'org/quartz/examples/example11/quartz.properties' } tasks.register('runExample12Client', JavaExec) { description = 'Run Example 12 Client' mainClass = 'org.quartz.examples.example12.RemoteClientExample' systemProperty 'org.quartz.properties', 'org/quartz/examples/example12/client.properties' } tasks.register('runExample12Server', JavaExec) { description = 'Run Example 12 Server' mainClass = 'org.quartz.examples.example12.RemoteServerExample' systemProperty 'org.quartz.properties', 'org/quartz/examples/example12/server.properties' } tasks.register('runExample13Instance1', JavaExec) { description = 'Run Example 13 Instance 1' mainClass = 'org.quartz.examples.example13.ClusterExample' systemProperty 'org.quartz.properties', 'org/quartz/examples/example13/instance1.properties' } tasks.register('runExample13Instance2', JavaExec) { description = 'Run Example 13 Instance 2' mainClass = 'org.quartz.examples.example13.ClusterExample' systemProperty 'org.quartz.properties', 'org/quartz/examples/example13/instance2.properties' } tasks.register('runExample14', JavaExec) { description = 'Run Example 14' mainClass = 'org.quartz.examples.example14.PriorityExample' } tasks.register('runExample15', JavaExec) { description = 'Run Example 15' mainClass = "org.quartz.examples.example15.NativeJobExample" } ================================================ FILE: examples/examples_guide.txt ================================================ Welcome ======= Welcome to the Quartz Examples directory. This directory contains 15 examples that show you how to use various features of Quartz. Each example is located in its own subdirectory. Every example can be run using Windows .bat files or Linux/UNIX .sh files. Additionally, each example directory contains a readme.txt file. Please read this file first, as it will contain useful information for running the examples. Examples Listing ================ example1 - Your first Quartz Program example2 - Using Simple Triggers example3 - Using Cron Triggers example4 - Job State and Job Parameters example5 - Job Misfires example6 - Handling Job Exceptions example7 - Interrupting Jobs example8 - How to use Quartz Calendars example9 - Using Job Listeners example10 - Using Quartz Plug-Ins example11 - Loading Up Quartz with Many Jobs example12 - Remoting with Quartz using RMI example13 - Clustering Quartz and JDBC Job Stores example14 - Quartz Trigger Priorities example15 - Running a native (shell) command from a Job Further description of each example and possible configuration changes to them is documented on the example class javadoc themselves, e.g. within examples/src/main/java/org/quartz/examples/example11/LoadExample.java Running the Examples ==================== The examples can be run from the examples folder of the repository, such as this: ../gradlew :examples:runExample1 or ../gradlew :examples:runExample4 or etc. Note that examples 12 and 13 require running two process (this will need to be done in separate terminals, or on separate machines): For Example 12 you need to first run the "server" portion, and then run the "client" portion of the example: ../gradlew :examples:runExample12Server ../gradlew :examples:runExample12Client For Example 13 you need to run two instances (two cluster members): ../gradlew :examples:runExample13Instance1 ../gradlew :examples:runExample13Instance2 ================================================ FILE: examples/src/main/java/org/quartz/examples/example1/HelloJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * 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 org.quartz.examples.example1; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* This is just a simple job that says "Hello" to the world. *

* * @author Bill Kratzer */ public class HelloJob implements Job { private static Logger _log = LoggerFactory.getLogger(HelloJob.class); /** *

* Empty constructor for job initialization *

*

* Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. *

*/ public HelloJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // Say Hello to the World and display the date/time _log.info("Hello World! - " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example1/SimpleExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * 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 org.quartz.examples.example1; import static org.quartz.DateBuilder.evenMinuteDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * This Example will demonstrate how to start and shutdown the Quartz scheduler and how to schedule a job to run in * Quartz. * * @author Bill Kratzer */ public class SimpleExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(SimpleExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); // computer a time that is on the next round minute Date runTime = evenMinuteDate(new Date()); log.info("------- Scheduling Job -------------------"); // define the job and tie it to our HelloJob class JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build(); // Trigger the job to run on the next round minute Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build(); // Tell quartz to schedule the job using our trigger sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + runTime); // Start up the scheduler (nothing can actually run until the // scheduler has been started) sched.start(); log.info("------- Started Scheduler -----------------"); // wait long enough so that the scheduler as an opportunity to // run the job! log.info("------- Waiting 65 seconds... -------------"); try { // wait 65 seconds to show job Thread.sleep(65L * 1000L); // executing... } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); } public static void main(String[] args) throws Exception { SimpleExample example = new SimpleExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example10/PlugInExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * 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 org.quartz.examples.example10; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This example will spawn a large number of jobs to run * * @author James House, Bill Kratzer */ public class PlugInExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(PlugInExample.class); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = null; try { sched = sf.getScheduler(); } catch (NoClassDefFoundError e) { log.error(" Unable to load a class - most likely you do not have jta.jar on the classpath. If not present in the examples/lib folder, please " + "add it there for this sample to run.", e); return; } log.info("------- Initialization Complete -----------"); log.info("------- (Not directly Scheduling any Jobs - relying on XML definitions of jobs/triggers to be loaded by plugin --"); log.info("------- Starting Scheduler ----------------"); // start the schedule sched.start(); log.info("------- Started Scheduler -----------------"); log.info("------- Waiting five minutes... -----------"); // wait five minutes to give our jobs a chance to run try { Thread.sleep(300L * 1000L); } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { PlugInExample example = new PlugInExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example10/SimpleJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example10; import java.util.Date; import java.util.Set; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); /** * Empty constructor for job initialization */ public SimpleJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ @SuppressWarnings("unchecked") public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("Executing job: " + jobKey + " executing at " + new Date() + ", fired by: " + context.getTrigger().getKey()); if(context.getMergedJobDataMap().size() > 0) { Set keys = context.getMergedJobDataMap().keySet(); for(String key: keys) { String val = context.getMergedJobDataMap().getString(key); _log.info(" - jobDataMap entry: " + key + " = " + val); } } context.setResult("hello"); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example11/LoadExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example11; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This example will spawn a large number of jobs to run * * This example demonstrates how Quartz can handle a large * number of jobs. This example starts with 500 jobs. However, * this number can be changed by modifying the start scripts. * * Due to the size of the thread pool (this example uses as thread * count of 15), only 15 threads will run concurrently in the * scheduler. * * You can change this parameter in the quartz.properties file. * * @author James House, Bill Kratzer */ public class LoadExample { private int _numberOfJobs = 500; public LoadExample(int inNumberOfJobs) { _numberOfJobs = inNumberOfJobs; } public void run() throws Exception { Logger log = LoggerFactory.getLogger(LoadExample.class); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); // schedule 500 jobs to run for (int count = 1; count <= _numberOfJobs; count++) { JobDetail job = newJob(SimpleJob.class).withIdentity("job" + count, "group_1").requestRecovery() // ask scheduler // to re-execute // this job if it // was in // progress when // the scheduler // went down... .build(); // tell the job to delay some small amount... to simulate work... long timeDelay = (long) (java.lang.Math.random() * 2500); job.getJobDataMap().put(SimpleJob.DELAY_TIME, timeDelay); Trigger trigger = newTrigger().withIdentity("trigger_" + count, "group_1") .startAt(futureDate((10000 + (count * 100)), IntervalUnit.MILLISECOND)) // space fire times a small bit .build(); sched.scheduleJob(job, trigger); if (count % 25 == 0) { log.info("...scheduled " + count + " jobs"); } } log.info("------- Starting Scheduler ----------------"); // start the schedule sched.start(); log.info("------- Started Scheduler -----------------"); log.info("------- Waiting five minutes... -----------"); // wait five minutes to give our jobs a chance to run try { Thread.sleep(300L * 1000L); } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { int numberOfJobs = 500; if (args.length == 1) { numberOfJobs = Integer.parseInt(args[0]); } if (args.length > 1) { System.out.println("Usage: java " + LoadExample.class.getName() + "[# of jobs]"); return; } LoadExample example = new LoadExample(numberOfJobs); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example11/SimpleJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example11; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); // job parameter public static final String DELAY_TIME = "delay time"; /** * Empty constructor for job initialization */ public SimpleJob() { } /** *

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

* * @throws JobExecutionException if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("Executing job: " + jobKey + " executing at " + new Date()); // wait for a period of time long delayTime = context.getJobDetail().getJobDataMap().getLong(DELAY_TIME); try { Thread.sleep(delayTime); } catch (Exception e) { // } _log.info("Finished Executing job: " + jobKey + " at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example12/RemoteClientExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example12; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This example is a client program that will remotely * talk to the scheduler to schedule a job. In this * example, we will need to use the JDBC Job Store. The * client will connect to the JDBC Job Store remotely to * schedule the job. * * This example demonstrates how Quartz can be used in a client/server * environment to remotely schedule jobs on a remote server using * RMI (Remote Method Invocation). * * This example will run a server that will execute the schedule. The * server itself will not schedule any jobs. This example will also * execute a client that will connect to the server (via RMI) to * schedule the job. Once the job is remotely scheduled, the scheduler on * the server will run the job (at the correct time). * * Note: This example works best when you run the client and server on * different computers. However, you can certainly run the server and * the client on the same box! * * Port # used for RMI connection can be modified in the example's * property files * * @author James House, Bill Kratzer */ public class RemoteClientExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(RemoteClientExample.class); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // define the job and ask it to run JobDetail job = newJob(SimpleJob.class) .withIdentity("remotelyAddedJob", "default") .build(); JobDataMap map = job.getJobDataMap(); map.put("msg", "Your remotely added job has executed!"); Trigger trigger = newTrigger() .withIdentity("remotelyAddedTrigger", "default") .forJob(job.getKey()) .withSchedule(cronSchedule("/5 * * ? * *")) .build(); // schedule the job sched.scheduleJob(job, trigger); log.info("Remote job scheduled."); } public static void main(String[] args) throws Exception { RemoteClientExample example = new RemoteClientExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example12/RemoteServerExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example12; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This example demonstrates how Quartz can be used in a client/server * environment to remotely schedule jobs on a remote server using * RMI (Remote Method Invocation). * * This example will run a server that will execute the schedule. The * server itself will not schedule any jobs. This example will also * execute a client that will connect to the server (via RMI) to * schedule the job. Once the job is remotely scheduled, the scheduler on * the server will run the job (at the correct time). * Note: This example works best when you run the client and server on * different computers. However, you can certainly run the server and * the client on the same box! * * Port # used for RMI connection can be modified in the example's * property files * * @author Bill Kratzer */ public class RemoteServerExample { /** * This example will spawn a large number of jobs to run * * @author James House, Bill Kratzer */ public void run() throws Exception { Logger log = LoggerFactory.getLogger(RemoteServerExample.class); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- (Not Scheduling any Jobs - relying on a remote client to schedule jobs --"); log.info("------- Starting Scheduler ----------------"); // start the schedule sched.start(); log.info("------- Started Scheduler -----------------"); log.info("------- Waiting ten minutes... ------------"); // wait five minutes to give our jobs a chance to run try { Thread.sleep(600L * 1000L); } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { RemoteServerExample example = new RemoteServerExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example12/SimpleJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example12; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; /** *

* A dumb implementation of Job, for unittesting purposes. *

* * @author James House */ public class SimpleJob implements Job { public static final String MESSAGE = "msg"; private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); /** * Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. */ public SimpleJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); String message = (String) context.getJobDetail().getJobDataMap().get(MESSAGE); _log.info("SimpleJob: " + jobKey + " executing at " + new Date()); _log.info("SimpleJob: msg: " + message); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example13/ClusterExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example13; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Used to test/show the clustering features of JDBCJobStore (JobStoreTX or JobStoreCMT). *

* All instances MUST use a different properties file, because their instance Ids must be different, however all other * properties should be the same. *

*

* If you want it to clear out existing jobs and triggers, pass a command-line argument called "clearJobs". *

*

* You should probably start with a "fresh" set of tables (assuming you may have some data lingering in it from other * tests), since mixing data from a non-clustered setup with a clustered one can be bad. *

*

* Try killing one of the cluster instances while they are running, and see that the remaining instance(s) recover the * in-progress jobs. Note that detection of the failure may take up to 15 or so seconds with the default settings. *

*

* Also try running it with/without the shutdown-hook plugin registered with the scheduler. * (org.quartz.plugins.management.ShutdownHookPlugin). *

*

* Note: Never run clustering on separate machines, unless their clocks are synchronized using some form of * time-sync service (such as an NTP daemon). *

* * Configure the instance1.properties file and the instance2.properties * file as necessary (see the "Configuration" section below for details). * * This example uses a database to maintain scheduling information in a * clustered environment. You will need to first install the Quartz * database tables. SQL table creation scripts are included with the Quartz * distribution for many popular database platforms. * * You will need a JDBC Driver for your database. The example uses Postgres to demonstrate * You can download Postgres JDBC driver here http://jdbc.postgresql.org * Just put the jar under "lib" folder of the Quartz distribution * * After you have installed the database scripts, you will need to * configure both properties file so that Quartz knows how to connect to * your database. * * The following parameters need to be set: (this shows a PostgreSQL example) * *
 * org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
 * org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
 * org.quartz.jobStore.useProperties=false
 * org.quartz.jobStore.dataSource=myDS
 * org.quartz.jobStore.tablePrefix=QRTZ_
 * org.quartz.jobStore.isClustered=true
 *
 * org.quartz.dataSource.myDS.driver = org.postgresql.Driver
 * org.quartz.dataSource.myDS.URL = jdbc:postgresql://localhost:5432/quartz
 * org.quartz.dataSource.myDS.user = quartz
 * org.quartz.dataSource.myDS.password = quartz
 * org.quartz.dataSource.myDS.maxConnections = 5
 * org.quartz.dataSource.myDS.validationQuery=
 * 
* * @see SimpleRecoveryJob * @see SimpleRecoveryStatefulJob * @author James House */ public class ClusterExample { private static Logger _log = LoggerFactory.getLogger(ClusterExample.class); public void run(boolean inClearJobs, boolean inScheduleJobs) throws Exception { // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); if (inClearJobs) { _log.warn("***** Deleting existing jobs/triggers *****"); sched.clear(); } _log.info("------- Initialization Complete -----------"); if (inScheduleJobs) { _log.info("------- Scheduling Jobs ------------------"); String schedId = sched.getSchedulerInstanceId(); int count = 1; JobDetail job = newJob(SimpleRecoveryJob.class).withIdentity("job_" + count, schedId) // put triggers in group // named after the cluster // node instance just to // distinguish (in logging) // what was scheduled from // where .requestRecovery() // ask scheduler to re-execute this job if it was in progress when the scheduler went // down... .build(); SimpleTrigger trigger = newTrigger().withIdentity("trigger_" + count, schedId) .startAt(futureDate(1, IntervalUnit.SECOND)) .withSchedule(simpleSchedule().withRepeatCount(20).withIntervalInSeconds(5)).build(); _log.info(job.getKey() + " will run at: " + trigger.getNextFireTime() + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); sched.scheduleJob(job, trigger); count++; job = newJob(SimpleRecoveryJob.class).withIdentity("job_" + count, schedId) // put triggers in group named after // the cluster node instance just to // distinguish (in logging) what was // scheduled from where .requestRecovery() // ask scheduler to re-execute this job if it was in progress when the scheduler went // down... .build(); trigger = newTrigger().withIdentity("trigger_" + count, schedId).startAt(futureDate(2, IntervalUnit.SECOND)) .withSchedule(simpleSchedule().withRepeatCount(20).withIntervalInSeconds(5)).build(); _log.info(job.getKey() + " will run at: " + trigger.getNextFireTime() + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); sched.scheduleJob(job, trigger); count++; job = newJob(SimpleRecoveryStatefulJob.class).withIdentity("job_" + count, schedId) // put triggers in group named // after the cluster node // instance just to // distinguish (in logging) // what was scheduled from // where .requestRecovery() // ask scheduler to re-execute this job if it was in progress when the scheduler went // down... .build(); trigger = newTrigger().withIdentity("trigger_" + count, schedId).startAt(futureDate(1, IntervalUnit.SECOND)) .withSchedule(simpleSchedule().withRepeatCount(20).withIntervalInSeconds(3)).build(); _log.info(job.getKey() + " will run at: " + trigger.getNextFireTime() + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); sched.scheduleJob(job, trigger); count++; job = newJob(SimpleRecoveryJob.class).withIdentity("job_" + count, schedId) // put triggers in group named after // the cluster node instance just to // distinguish (in logging) what was // scheduled from where .requestRecovery() // ask scheduler to re-execute this job if it was in progress when the scheduler went // down... .build(); trigger = newTrigger().withIdentity("trigger_" + count, schedId).startAt(futureDate(1, IntervalUnit.SECOND)) .withSchedule(simpleSchedule().withRepeatCount(20).withIntervalInSeconds(4)).build(); _log.info(job.getKey() + " will run at: " + trigger.getNextFireTime() + " & repeat: " + trigger.getRepeatCount() + "/" + trigger.getRepeatInterval()); sched.scheduleJob(job, trigger); count++; job = newJob(SimpleRecoveryJob.class).withIdentity("job_" + count, schedId) // put triggers in group named after // the cluster node instance just to // distinguish (in logging) what was // scheduled from where .requestRecovery() // ask scheduler to re-execute this job if it was in progress when the scheduler went // down... .build(); trigger = newTrigger().withIdentity("trigger_" + count, schedId).startAt(futureDate(1, IntervalUnit.SECOND)) .withSchedule(simpleSchedule().withRepeatCount(20).withIntervalInMilliseconds(4500L)).build(); _log.info(job.getKey() + " will run at: " + trigger.getNextFireTime() + " & repeat: " + trigger.getRepeatCount() + "/" + trigger.getRepeatInterval()); sched.scheduleJob(job, trigger); } // jobs don't start firing until start() has been called... _log.info("------- Starting Scheduler ---------------"); sched.start(); _log.info("------- Started Scheduler ----------------"); _log.info("------- Waiting for one hour... ----------"); try { Thread.sleep(3600L * 1000L); } catch (Exception e) { // } _log.info("------- Shutting Down --------------------"); sched.shutdown(); _log.info("------- Shutdown Complete ----------------"); } public static void main(String[] args) throws Exception { boolean clearJobs = false; boolean scheduleJobs = true; for (String arg : args) { if (arg.equalsIgnoreCase("clearJobs")) { clearJobs = true; } else if (arg.equalsIgnoreCase("dontScheduleJobs")) { scheduleJobs = false; } } ClusterExample example = new ClusterExample(); example.run(clearJobs, scheduleJobs); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example13/SimpleRecoveryJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example13; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** *

* A dumb implementation of Job, for unit testing purposes. *

* * @author James House */ public class SimpleRecoveryJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleRecoveryJob.class); private static final String COUNT = "count"; /** * Quartz requires a public empty constructor so that the scheduler can instantiate the class whenever it needs. */ public SimpleRecoveryJob() { } /** *

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

* * @throws JobExecutionException if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { JobKey jobKey = context.getJobDetail().getKey(); // if the job is recovering print a message if (context.isRecovering()) { _log.info("SimpleRecoveryJob: " + jobKey + " RECOVERING at " + new Date()); } else { _log.info("SimpleRecoveryJob: " + jobKey + " starting at " + new Date()); } // delay for ten seconds long delay = 10L * 1000L; try { Thread.sleep(delay); } catch (Exception e) { // } JobDataMap data = context.getJobDetail().getJobDataMap(); int count; if (data.containsKey(COUNT)) { count = data.getInt(COUNT); } else { count = 0; } count++; data.put(COUNT, count); _log.info("SimpleRecoveryJob: " + jobKey + " done at " + new Date() + "\n Execution #" + count); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example13/SimpleRecoveryStatefulJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example13; import org.quartz.DisallowConcurrentExecution; import org.quartz.PersistJobDataAfterExecution; /** * This job has the same functionality of SimpleRecoveryJob except that this job implements is 'stateful', in that it * will have it's data (JobDataMap) automatically re-persisted after each execution, and only one instance of the * JobDetail can be executed at a time. * * @author Bill Kratzer */ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class SimpleRecoveryStatefulJob extends SimpleRecoveryJob { public SimpleRecoveryStatefulJob() { super(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example14/PriorityExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example14; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * This Example will demonstrate how Triggers are ordered by priority. */ public class PriorityExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(PriorityExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory("org/quartz/examples/example14/quartz_priority.properties"); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -------------------"); JobDetail job = newJob(TriggerEchoJob.class).withIdentity("TriggerEchoJob").build(); // All three triggers will fire their first time at the same time, // ordered by their priority, and then repeat once, firing in a // staggered order that therefore ignores priority. // // We should see the following firing order: // 1. Priority10Trigger15SecondRepeat // 2. Priority5Trigger10SecondRepeat // 3. Priority1Trigger5SecondRepeat // 4. Priority1Trigger5SecondRepeat // 5. Priority5Trigger10SecondRepeat // 6. Priority10Trigger15SecondRepeat // Calculate the start time of all triggers as 5 seconds from now Date startTime = futureDate(5, IntervalUnit.SECOND); // First trigger has priority of 1, and will repeat after 5 seconds Trigger trigger1 = newTrigger().withIdentity("Priority1Trigger5SecondRepeat").startAt(startTime) .withSchedule(simpleSchedule().withRepeatCount(1).withIntervalInSeconds(5)).withPriority(1).forJob(job).build(); // Second trigger has default priority of 5 (default), and will repeat after 10 seconds Trigger trigger2 = newTrigger().withIdentity("Priority5Trigger10SecondRepeat").startAt(startTime) .withSchedule(simpleSchedule().withRepeatCount(1).withIntervalInSeconds(10)).forJob(job).build(); // Third trigger has priority 10, and will repeat after 15 seconds Trigger trigger3 = newTrigger().withIdentity("Priority10Trigger15SecondRepeat").startAt(startTime) .withSchedule(simpleSchedule().withRepeatCount(1).withIntervalInSeconds(15)).withPriority(10).forJob(job) .build(); // Tell quartz to schedule the job using our trigger sched.scheduleJob(job, trigger1); sched.scheduleJob(trigger2); sched.scheduleJob(trigger3); // Start up the scheduler (nothing can actually run until the // scheduler has been started) sched.start(); log.info("------- Started Scheduler -----------------"); // wait long enough so that the scheduler as an opportunity to // fire the triggers log.info("------- Waiting 30 seconds... -------------"); try { Thread.sleep(30L * 1000L); // executing... } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); } public static void main(String[] args) throws Exception { PriorityExample example = new PriorityExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example14/TriggerEchoJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example14; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * This is just a simple job that echos the name of the Trigger * that fired it. */ public class TriggerEchoJob implements Job { private static final Logger LOG = LoggerFactory.getLogger(TriggerEchoJob.class); /** * Empty constructor for job initialization * *

* Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. *

*/ public TriggerEchoJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { LOG.info("TRIGGER: " + context.getTrigger().getKey()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example15/NativeJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example15; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

Built in job for executing native executables in a separate process.

* *
 *             JobDetail job = new JobDetail("dumbJob", null, org.quartz.jobs.NativeJob.class);
 *             job.getJobDataMap().put(org.quartz.jobs.NativeJob.PROP_COMMAND, "echo \"hi\" >> foobar.txt");
 *             Trigger trigger = TriggerUtils.makeSecondlyTrigger(5);
 *             trigger.setName("dumbTrigger");
 *             sched.scheduleJob(job, trigger);
 * 
* * 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. * * IMPORTANT SECURITY NOTE: having a class such as this one on your application's classpath, along with a way for * users of the application to schedule their own jobs, may provide a security risk of letting users run arbitrary * commands on your system. Take necessary precautions to avoid arbitrary configuring and scheduling of native jobs. * * @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 */ public class NativeJob implements Job { private final Logger log = LoggerFactory.getLogger(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 Logger getLog() { return log; } private Integer runNativeCommand(String command, String parameters, boolean wait, boolean consumeStreams) throws JobExecutionException { String[] cmd; String[] args = new String[2]; Integer result = null; args[0] = command; args[1] = parameters; try { //with this variable will be done the switching String osName = System.getProperty("os.name"); // specific for Windows if (osName.startsWith("Windows")) { cmd = new String[args.length + 2]; if (osName.equals("Windows 95")) { // windows 95 only cmd[0] = "command.com"; } else { cmd[0] = "cmd.exe"; } cmd[1] = "/C"; System.arraycopy(args, 0, cmd, 2, args.length); } else if (osName.equals("Linux")) { 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 = proc.waitFor(); } // any error message? } catch (Throwable 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 */ @Override public void run() { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(is)); String line; 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) {} } } } } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example15/NativeJobExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example15; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.examples.example14.TriggerEchoJob; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; /** * This Example will demonstrate how run a NativeJob. * * Please see the note int the NativeJob javadoc about security concerns. */ public class NativeJobExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(NativeJobExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -------------------"); JobDetail job = newJob(NativeJob.class).withIdentity("MyNativeJob").build(); job.getJobDataMap().put(NativeJob.PROP_COMMAND, "echo"); job.getJobDataMap().put(NativeJob.PROP_PARAMETERS, "\"ran the native command and captured the stdout.\""); job.getJobDataMap().put(NativeJob.PROP_CONSUME_STREAMS, true); // Calculate the start time of all triggers as 5 seconds from now Date startTime = futureDate(5, IntervalUnit.SECOND); // First trigger has priority of 1, and will repeat after 5 seconds Trigger trigger1 = newTrigger().withIdentity("TriggerWith5SecondRepeat").startAt(startTime) .withSchedule(simpleSchedule().withRepeatCount(1).withIntervalInSeconds(5)).forJob(job).build(); // Tell quartz to schedule the job using our trigger sched.scheduleJob(job, trigger1); // Start up the scheduler (nothing can actually run until the // scheduler has been started) sched.start(); log.info("------- Started Scheduler -----------------"); // wait long enough so that the scheduler as an opportunity to // fire the triggers log.info("------- Waiting 30 seconds... -------------"); try { Thread.sleep(30L * 1000L); // executing... } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); } public static void main(String[] args) throws Exception { NativeJobExample example = new NativeJobExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example2/SimpleJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example2; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); /** * Empty constructor for job initialization */ public SimpleJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example2/SimpleTriggerExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * 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 org.quartz.examples.example2; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.JobKey.jobKey; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.DateBuilder; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * This Example will demonstrate all of the basics of scheduling capabilities of Quartz using Simple Triggers. * * @author Bill Kratzer */ public class SimpleTriggerExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(SimpleTriggerExample.class); log.info("------- Initializing -------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete --------"); log.info("------- Scheduling Jobs ----------------"); // jobs can be scheduled before sched.start() has been called // get a "nice round" time a few seconds in the future... Date startTime = DateBuilder.nextGivenSecondDate(null, 15); // job1 will only fire once at date/time "ts" JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build(); SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build(); // schedule it to run! Date ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // job2 will only fire once at date/time "ts" job = newJob(SimpleJob.class).withIdentity("job2", "group1").build(); trigger = (SimpleTrigger) newTrigger().withIdentity("trigger2", "group1").startAt(startTime).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // job3 will run 11 times (run once and repeat 10 more times) // job3 will repeat every 10 seconds job = newJob(SimpleJob.class).withIdentity("job3", "group1").build(); trigger = newTrigger().withIdentity("trigger3", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(10)).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // the same job (job3) will be scheduled by a another trigger // this time will only repeat twice at a 70 second interval trigger = newTrigger().withIdentity("trigger3", "group2").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(2)).forJob(job).build(); ft = sched.scheduleJob(trigger); log.info(job.getKey() + " will [also] run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // job4 will run 6 times (run once and repeat 5 more times) // job4 will repeat every 10 seconds job = newJob(SimpleJob.class).withIdentity("job4", "group1").build(); trigger = newTrigger().withIdentity("trigger4", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(5)).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // job5 will run once, five minutes in the future job = newJob(SimpleJob.class).withIdentity("job5", "group1").build(); trigger = (SimpleTrigger) newTrigger().withIdentity("trigger5", "group1") .startAt(futureDate(5, IntervalUnit.MINUTE)).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // job6 will run indefinitely, every 40 seconds job = newJob(SimpleJob.class).withIdentity("job6", "group1").build(); trigger = newTrigger().withIdentity("trigger6", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever()).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); log.info("------- Starting Scheduler ----------------"); // All of the jobs have been added to the scheduler, but none of the jobs // will run until the scheduler has been started sched.start(); log.info("------- Started Scheduler -----------------"); // jobs can also be scheduled after start() has been called... // job7 will repeat 20 times, repeat every five minutes job = newJob(SimpleJob.class).withIdentity("job7", "group1").build(); trigger = newTrigger().withIdentity("trigger7", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInMinutes(5).withRepeatCount(20)).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // jobs can be fired directly... (rather than waiting for a trigger) job = newJob(SimpleJob.class).withIdentity("job8", "group1").storeDurably().build(); sched.addJob(job, true); log.info("'Manually' triggering job8..."); sched.triggerJob(jobKey("job8", "group1")); log.info("------- Waiting 30 seconds... --------------"); try { // wait 33 seconds to show jobs Thread.sleep(30L * 1000L); // executing... } catch (Exception e) { // } // jobs can be re-scheduled... // job 7 will run immediately and repeat 10 times for every second log.info("------- Rescheduling... --------------------"); trigger = newTrigger().withIdentity("trigger7", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInMinutes(5).withRepeatCount(20)).build(); ft = sched.rescheduleJob(trigger.getKey(), trigger); log.info("job7 rescheduled to run at: " + ft); log.info("------- Waiting five minutes... ------------"); try { // wait five minutes to show jobs Thread.sleep(300L * 1000L); // executing... } catch (Exception e) { // } log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); // display some stats about the schedule that just ran SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example3/CronTriggerExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example3; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * This Example will demonstrate all of the basics of scheduling capabilities of Quartz using Cron Triggers. * * @author Bill Kratzer */ public class CronTriggerExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(CronTriggerExample.class); log.info("------- Initializing -------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete --------"); log.info("------- Scheduling Jobs ----------------"); // jobs can be scheduled before sched.start() has been called // job 1 will run every 20 seconds JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build(); CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?")) .build(); Date ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); // job 2 will run every other minute (at 15 seconds past the minute) job = newJob(SimpleJob.class).withIdentity("job2", "group1").build(); trigger = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule("15 0/2 * * * ?")).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); // job 3 will run every other minute but only between 8am and 5pm job = newJob(SimpleJob.class).withIdentity("job3", "group1").build(); trigger = newTrigger().withIdentity("trigger3", "group1").withSchedule(cronSchedule("0 0/2 8-17 * * ?")).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); // job 4 will run every three minutes but only between 5pm and 11pm job = newJob(SimpleJob.class).withIdentity("job4", "group1").build(); trigger = newTrigger().withIdentity("trigger4", "group1").withSchedule(cronSchedule("0 0/3 17-23 * * ?")).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); // job 5 will run at 10am on the 1st and 15th days of the month job = newJob(SimpleJob.class).withIdentity("job5", "group1").build(); trigger = newTrigger().withIdentity("trigger5", "group1").withSchedule(cronSchedule("0 0 10am 1,15 * ?")).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); // job 6 will run every 30 seconds but only on Weekdays (Monday through Friday) job = newJob(SimpleJob.class).withIdentity("job6", "group1").build(); trigger = newTrigger().withIdentity("trigger6", "group1").withSchedule(cronSchedule("0,30 * * ? * MON-FRI")) .build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); // job 7 will run every 30 seconds but only on Weekends (Saturday and Sunday) job = newJob(SimpleJob.class).withIdentity("job7", "group1").build(); trigger = newTrigger().withIdentity("trigger7", "group1").withSchedule(cronSchedule("0,30 * * ? * SAT,SUN")) .build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: " + trigger.getCronExpression()); log.info("------- Starting Scheduler ----------------"); // All of the jobs have been added to the scheduler, but none of the // jobs // will run until the scheduler has been started sched.start(); log.info("------- Started Scheduler -----------------"); log.info("------- Waiting five minutes... ------------"); try { // wait five minutes to show jobs Thread.sleep(300L * 1000L); // executing... } catch (Exception e) { // } log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { CronTriggerExample example = new CronTriggerExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example3/SimpleJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example3; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); /** * Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. */ public SimpleJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example4/ColorJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example4; import java.util.Date; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.PersistJobDataAfterExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is just a simple job that receives parameters and * maintains state *

* * @author Bill Kratzer */ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class ColorJob implements Job { private static Logger _log = LoggerFactory.getLogger(ColorJob.class); // parameter names specific to this job public static final String FAVORITE_COLOR = "favorite color"; public static final String EXECUTION_COUNT = "count"; // Since Quartz will re-instantiate a class every time it // gets executed, members non-static member variables can // not be used to maintain state! private int _counter = 1; /** *

* Empty constructor for job initialization *

*

* Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. *

*/ public ColorJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); // Grab and print passed parameters JobDataMap data = context.getJobDetail().getJobDataMap(); String favoriteColor = data.getString(FAVORITE_COLOR); int count = data.getInt(EXECUTION_COUNT); _log.info("ColorJob: " + jobKey + " executing at " + new Date() + "\n" + " favorite color is " + favoriteColor + "\n" + " execution count (from job map) is " + count + "\n" + " execution count (from job member variable) is " + _counter); // increment the count and store it back into the // job map so that job state can be properly maintained count++; data.put(EXECUTION_COUNT, count); // Increment the local member variable // This serves no real purpose since job state can not // be maintained via member variables! _counter++; } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example4/JobStateExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example4; import static org.quartz.DateBuilder.nextGivenSecondDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * This Example will demonstrate how job parameters can be passed into jobs and how state can be maintained * * @author Bill Kratzer */ public class JobStateExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(JobStateExample.class); log.info("------- Initializing -------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete --------"); log.info("------- Scheduling Jobs ----------------"); // get a "nice round" time a few seconds in the future.... Date startTime = nextGivenSecondDate(null, 10); // job1 will only run 5 times (at start time, plus 4 repeats), every 10 seconds JobDetail job1 = newJob(ColorJob.class).withIdentity("job1", "group1").build(); SimpleTrigger trigger1 = newTrigger().withIdentity("trigger1", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(4)).build(); // pass initialization parameters into the job job1.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Green"); job1.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1); // schedule the job to run Date scheduleTime1 = sched.scheduleJob(job1, trigger1); log.info(job1.getKey() + " will run at: " + scheduleTime1 + " and repeat: " + trigger1.getRepeatCount() + " times, every " + trigger1.getRepeatInterval() / 1000 + " seconds"); // job2 will also run 5 times, every 10 seconds JobDetail job2 = newJob(ColorJob.class).withIdentity("job2", "group1").build(); SimpleTrigger trigger2 = newTrigger().withIdentity("trigger2", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(4)).build(); // pass initialization parameters into the job // this job has a different favorite color! job2.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Red"); job2.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1); // schedule the job to run Date scheduleTime2 = sched.scheduleJob(job2, trigger2); log.info(job2.getKey().toString() + " will run at: " + scheduleTime2 + " and repeat: " + trigger2.getRepeatCount() + " times, every " + trigger2.getRepeatInterval() / 1000 + " seconds"); log.info("------- Starting Scheduler ----------------"); // All of the jobs have been added to the scheduler, but none of the jobs // will run until the scheduler has been started sched.start(); log.info("------- Started Scheduler -----------------"); log.info("------- Waiting 60 seconds... -------------"); try { // wait five minutes to show jobs Thread.sleep(60L * 1000L); // executing... } catch (Exception e) { // } log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { JobStateExample example = new JobStateExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example5/MisfireExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example5; import static org.quartz.DateBuilder.nextGivenSecondDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * Demonstrates the behavior of StatefulJobs, as well as how misfire instructions affect the firings of * triggers of StatefulJob s - when the jobs take longer to execute that the frequency of the trigger's * repetition. *

* While the example is running, you should note that there are two triggers with identical schedules, firing identical * jobs. The triggers "want" to fire every 3 seconds, but the jobs take 10 seconds to execute. Therefore, by the time * the jobs complete their execution, the triggers have already "misfired" (unless the scheduler's "misfire threshold" * has been set to more than 7 seconds). You should see that one of the jobs has its misfire instruction set to * SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT, which causes it to fire * immediately, when the misfire is detected. The other trigger uses the default "smart policy" misfire instruction, * which causes the trigger to advance to its next fire time (skipping those that it has missed) - so that it does not * refire immediately, but rather at the next scheduled time. *

* * @author Chris Bonham */ public class MisfireExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(MisfireExample.class); log.info("------- Initializing -------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory("org/quartz/examples/example5/quartz_misfire.properties"); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -----------"); // jobs can be scheduled before start() has been called // get a "nice round" time a few seconds in the future... Date startTime = nextGivenSecondDate(null, 15); // statefulJob1 will run every three seconds // (but it will delay for ten seconds) JobDetail job = newJob(StatefulDumbJob.class).withIdentity("statefulJob1", "group1") .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L).build(); SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(3).repeatForever()).build(); Date ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // statefulJob2 will run every three seconds // (but it will delay for ten seconds - and therefore purposely misfire after a few iterations) job = newJob(StatefulDumbJob.class).withIdentity("statefulJob2", "group1") .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L).build(); trigger = newTrigger() .withIdentity("trigger2", "group1") .startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(3).repeatForever() .withMisfireHandlingInstructionNowWithExistingCount()) // set misfire instructions .build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); log.info("------- Starting Scheduler ----------------"); // jobs don't start firing until start() has been called... sched.start(); log.info("------- Started Scheduler -----------------"); try { // sleep for ten minutes for triggers to file.... Thread.sleep(600L * 1000L); } catch (Exception e) { // } log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { MisfireExample example = new MisfireExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example5/StatefulDumbJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example5; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import java.util.Date; /** *

* A dumb implementation of Job, for unit testing purposes. *

* * @author James House */ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class StatefulDumbJob implements Job { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Constants. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static final String NUM_EXECUTIONS = "NumExecutions"; public static final String EXECUTION_DELAY = "ExecutionDelay"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Constructors. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public StatefulDumbJob() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interface. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

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

* * @throws JobExecutionException if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { System.err.println("---" + context.getJobDetail().getKey() + " executing.[" + new Date() + "]"); JobDataMap map = context.getJobDetail().getJobDataMap(); int executeCount = 0; if (map.containsKey(NUM_EXECUTIONS)) { executeCount = map.getInt(NUM_EXECUTIONS); } executeCount++; map.put(NUM_EXECUTIONS, executeCount); long delay = 5000l; if (map.containsKey(EXECUTION_DELAY)) { delay = map.getLong(EXECUTION_DELAY); } try { Thread.sleep(delay); } catch (Exception ignore) { // } System.err.println(" -" + context.getJobDetail().getKey() + " complete (" + executeCount + ")."); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example6/BadJob1.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example6; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.PersistJobDataAfterExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** *

* A job dumb job that will throw a job execution exception *

* * @author Bill Kratzer */ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class BadJob1 implements Job { // Logging private static Logger _log = LoggerFactory.getLogger(BadJob1.class); private int calculation; /** * Empty public constructor for job initialization */ public BadJob1() { } /** *

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

* * @throws JobExecutionException if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { JobKey jobKey = context.getJobDetail().getKey(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); int denominator = dataMap.getInt("denominator"); _log.info("---" + jobKey + " executing at " + new Date() + " with denominator " + denominator); // a contrived example of an exception that // will be generated by this job due to a // divide by zero error (only on first run) try { calculation = 4815 / denominator; } catch (Exception e) { _log.info("--- Error in job!"); JobExecutionException e2 = new JobExecutionException(e); // fix denominator so the next time this job run // it won't fail again dataMap.put("denominator", "1"); // this job will refire immediately e2.setRefireImmediately(true); throw e2; } _log.info("---" + jobKey + " completed at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example6/BadJob2.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example6; import java.util.Date; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.PersistJobDataAfterExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* A job dumb job that will throw a job execution exception *

* * @author Bill Kratzer */ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class BadJob2 implements Job { // Logging private static Logger _log = LoggerFactory.getLogger(BadJob2.class); private int calculation; /** * Empty public constructor for job initialization */ public BadJob2() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { JobKey jobKey = context.getJobDetail().getKey(); _log.info("---" + jobKey + " executing at " + new Date()); // a contrived example of an exception that // will be generated by this job due to a // divide by zero error try { int zero = 0; calculation = 4815 / zero; } catch (Exception e) { _log.info("--- Error in job!"); JobExecutionException e2 = new JobExecutionException(e); // Quartz will automatically unschedule // all triggers associated with this job // so that it does not run again e2.setUnscheduleAllTriggers(true); throw e2; } _log.info("---" + jobKey + " completed at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example6/JobExceptionExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example6; import static org.quartz.DateBuilder.nextGivenSecondDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * This job demonstrates how Quartz can handle JobExecutionExceptions that are thrown by jobs. * * @author Bill Kratzer */ public class JobExceptionExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(JobExceptionExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete ------------"); log.info("------- Scheduling Jobs -------------------"); // jobs can be scheduled before start() has been called // get a "nice round" time a few seconds in the future... Date startTime = nextGivenSecondDate(null, 15); // badJob1 will run every 10 seconds // this job will throw an exception and refire // immediately JobDetail job = newJob(BadJob1.class).withIdentity("badJob1", "group1").usingJobData("denominator", "0").build(); SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever()).build(); Date ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // badJob2 will run every five seconds // this job will throw an exception and never // refire job = newJob(BadJob2.class).withIdentity("badJob2", "group1").build(); trigger = newTrigger().withIdentity("trigger2", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(5).repeatForever()).build(); ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); log.info("------- Starting Scheduler ----------------"); // jobs don't start firing until start() has been called... sched.start(); log.info("------- Started Scheduler -----------------"); try { // sleep for 30 seconds Thread.sleep(30L * 1000L); } catch (Exception e) { // } log.info("------- Shutting Down ---------------------"); sched.shutdown(false); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { JobExceptionExample example = new JobExceptionExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example7/DumbInterruptableJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example7; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.InterruptableJob; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.UnableToInterruptJobException; /** *

* A dumb implementation of an InterruptableJob, for unit testing purposes. *

* * @author Chris Bonham * @author Bill Kratzer */ public class DumbInterruptableJob implements InterruptableJob { // logging services private static Logger _log = LoggerFactory.getLogger(DumbInterruptableJob.class); // has the job been interrupted? private boolean _interrupted = false; // job name private JobKey _jobKey = null; /** *

* Empty constructor for job initialization *

*/ public DumbInterruptableJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { _jobKey = context.getJobDetail().getKey(); _log.info("---- " + _jobKey + " executing at " + new Date()); try { // main job loop... see the JavaDOC for InterruptableJob for discussion... // do some work... in this example we are 'simulating' work by sleeping... :) for (int i = 0; i < 4; i++) { try { Thread.sleep(1000L); } catch (Exception ignore) { ignore.printStackTrace(); } // periodically check if we've been interrupted... if(_interrupted) { _log.info("--- " + _jobKey + " -- Interrupted... bailing out!"); return; // could also choose to throw a JobExecutionException // if that made for sense based on the particular // job's responsibilities/behaviors } } } finally { _log.info("---- " + _jobKey + " completed at " + new Date()); } } /** *

* Called by the Scheduler when a user * interrupts the Job. *

* * @throws org.quartz.UnableToInterruptJobException * if there is an exception while interrupting the job. */ public void interrupt() throws UnableToInterruptJobException { _log.info("---" + _jobKey + " -- INTERRUPTING --"); _interrupted = true; } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example7/InterruptExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example7; import static org.quartz.DateBuilder.nextGivenSecondDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * Demonstrates the behavior of StatefulJobs, as well as how misfire instructions affect the firings of * triggers of StatefulJob s - when the jobs take longer to execute that the frequency of the trigger's * repetition. *

* While the example is running, you should note that there are two triggers with identical schedules, firing identical * jobs. The triggers "want" to fire every 3 seconds, but the jobs take 10 seconds to execute. Therefore, by the time * the jobs complete their execution, the triggers have already "misfired" (unless the scheduler's "misfire threshold" * has been set to more than 7 seconds). You should see that one of the jobs has its misfire instruction set to * SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT- which causes it to fire * immediately, when the misfire is detected. The other trigger uses the default "smart policy" misfire instruction, * which causes the trigger to advance to its next fire time (skipping those that it has missed) - so that it does not * refire immediately, but rather at the next scheduled time. *

* * @author Chris Bonham */ public class InterruptExample { public void run() throws Exception { final Logger log = LoggerFactory.getLogger(InterruptExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -------------------"); // get a "nice round" time a few seconds in the future... Date startTime = nextGivenSecondDate(null, 15); JobDetail job = newJob(DumbInterruptableJob.class).withIdentity("interruptableJob1", "group1").build(); SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(5).repeatForever()).build(); Date ft = sched.scheduleJob(job, trigger); log.info(job.getKey() + " will run at: " + ft + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // start up the scheduler (jobs do not start to fire until // the scheduler has been started) sched.start(); log.info("------- Started Scheduler -----------------"); log.info("------- Starting loop to interrupt job every 7 seconds ----------"); for (int i = 0; i < 50; i++) { try { Thread.sleep(7000L); // tell the scheduler to interrupt our job sched.interrupt(job.getKey()); } catch (Exception e) { // } } log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { InterruptExample example = new InterruptExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example8/CalendarExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example8; import static org.quartz.DateBuilder.dateOf; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.examples.example2.SimpleJob; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.AnnualCalendar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; /** * This example will demonstrate how calendars can be used to exclude periods of time when scheduling should not take * place. */ public class CalendarExample { public void run() throws Exception { final Logger log = LoggerFactory.getLogger(CalendarExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -------------------"); // Add the holiday calendar to the schedule AnnualCalendar holidays = new AnnualCalendar(); // fourth of July (July 4) Calendar fourthOfJuly = new GregorianCalendar(2005, 6, 4); holidays.setDayExcluded(fourthOfJuly, true); // halloween (Oct 31) Calendar halloween = new GregorianCalendar(2005, 9, 31); holidays.setDayExcluded(halloween, true); // christmas (Dec 25) Calendar christmas = new GregorianCalendar(2005, 11, 25); holidays.setDayExcluded(christmas, true); // tell the schedule about our holiday calendar sched.addCalendar("holidays", holidays, false, false); // schedule a job to run hourly, starting on halloween // at 10 am Date runDate = dateOf(0, 0, 10, 31, 10); JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build(); SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runDate) .withSchedule(simpleSchedule().withIntervalInHours(1).repeatForever()).modifiedByCalendar("holidays").build(); // schedule the job and print the first run date Date firstRunTime = sched.scheduleJob(job, trigger); // print out the first execution date. // Note: Since Halloween (Oct 31) is a holiday, then // we will not run until the next day! (Nov 1) log.info(job.getKey() + " will run at: " + firstRunTime + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // All of the jobs have been added to the scheduler, but none of the jobs // will run until the scheduler has been started log.info("------- Starting Scheduler ----------------"); sched.start(); // wait 30 seconds: // note: nothing will run log.info("------- Waiting 30 seconds... --------------"); try { // wait 30 seconds to show jobs Thread.sleep(30L * 1000L); // executing... } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { CalendarExample example = new CalendarExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example8/SimpleJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example8; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); /** * Empty constructor for job initialization */ public SimpleJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example9/Job1Listener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example9; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author wkratzer */ public class Job1Listener implements JobListener { private static Logger _log = LoggerFactory.getLogger(Job1Listener.class); public String getName() { return "job1_to_job2"; } public void jobToBeExecuted(JobExecutionContext inContext) { _log.info("Job1Listener says: Job Is about to be executed."); } public void jobExecutionVetoed(JobExecutionContext inContext) { _log.info("Job1Listener says: Job Execution was vetoed."); } public void jobWasExecuted(JobExecutionContext inContext, JobExecutionException inException) { _log.info("Job1Listener says: Job was executed."); // Simple job #2 JobDetail job2 = newJob(SimpleJob2.class).withIdentity("job2").build(); Trigger trigger = newTrigger().withIdentity("job2Trigger").startNow().build(); try { // schedule the job to run! inContext.getScheduler().scheduleJob(job2, trigger); } catch (SchedulerException e) { _log.warn("Unable to schedule job2!"); e.printStackTrace(); } } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example9/ListenerExample.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example9; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.JobListener; import org.quartz.Matcher; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.KeyMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Demonstrates the behavior of JobListeners. In particular, this example will use a job listener to * trigger another job after one job successfully executes. */ public class ListenerExample { public void run() throws Exception { Logger log = LoggerFactory.getLogger(ListenerExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -------------------"); // schedule a job to run immediately JobDetail job = newJob(SimpleJob1.class).withIdentity("job1").build(); Trigger trigger = newTrigger().withIdentity("trigger1").startNow().build(); // Set up the listener JobListener listener = new Job1Listener(); Matcher matcher = KeyMatcher.keyEquals(job.getKey()); sched.getListenerManager().addJobListener(listener, matcher); // schedule the job to run sched.scheduleJob(job, trigger); // All of the jobs have been added to the scheduler, but none of the jobs // will run until the scheduler has been started log.info("------- Starting Scheduler ----------------"); sched.start(); // wait 30 seconds: // note: nothing will run log.info("------- Waiting 30 seconds... --------------"); try { // wait 30 seconds to show jobs Thread.sleep(30L * 1000L); // executing... } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { ListenerExample example = new ListenerExample(); example.run(); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example9/SimpleJob1.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example9; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob1 implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob1.class); /** * Empty constructor for job initialization */ public SimpleJob1() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("SimpleJob1 says: " + jobKey + " executing at " + new Date()); } } ================================================ FILE: examples/src/main/java/org/quartz/examples/example9/SimpleJob2.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.examples.example9; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is just a simple job that gets fired off many times by example 1 *

* * @author Bill Kratzer */ public class SimpleJob2 implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob2.class); /** * Empty constructor for job initialization */ public SimpleJob2() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("SimpleJob2 says: " + jobKey + " executing at " + new Date()); } } ================================================ FILE: examples/src/main/resources/log4j.xml ================================================ ================================================ FILE: examples/src/main/resources/org/quartz/examples/example10/quartz.properties ================================================ #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: Example10Scheduler org.quartz.scheduler.instanceId: AUTO #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 3 org.quartz.threadPool.threadPriority: 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore #============================================================================ # Configure Plugins #============================================================================ org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingJobHistoryPlugin org.quartz.plugin.jobInitializer.class: org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin org.quartz.plugin.jobInitializer.fileNames: org/quartz/examples/example10/quartz_data.xml org.quartz.plugin.jobInitializer.failOnFileNotFound: true org.quartz.plugin.jobInitializer.scanInterval: 120 org.quartz.plugin.jobInitializer.wrapInUserTransaction: false ================================================ FILE: examples/src/main/resources/org/quartz/examples/example10/quartz_data.xml ================================================ * * true false TestJob1 org.quartz.examples.example10.SimpleJob TestDurableJob org.quartz.examples.example10.SimpleJob true false TestSimpleTrigger1AtFiveSecondInterval TestJob1 -1 5000 TestJob2 GroupOfTestJob2 This is the description of TestJob2 org.quartz.examples.example10.SimpleJob false true someKey someValue someOtherKey someOtherValue TestSimpleTrigger2AtTenSecondIntervalAndFiveRepeats GroupOfTestJob2Triggers TestJob2 GroupOfTestJob2 2010-02-09T10:15:00 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 5 10000 TestCronTrigger2AtEveryMinute GroupOfTestJob2Triggers TestJob2 GroupOfTestJob2 someKey overriddenValue someOtherKey someOtherOverriddenValue 0 * * ? * * TestCronTrigger2AtEveryMinuteOnThe45thSecond GroupOfTestJob2Triggers TestJob2 GroupOfTestJob2 2010-02-09T12:26:00.0 2012-02-09T12:26:00.0 MISFIRE_INSTRUCTION_SMART_POLICY 45 * * ? * * America/Los_Angeles ================================================ FILE: examples/src/main/resources/org/quartz/examples/example11/quartz.properties ================================================ #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: TestScheduler org.quartz.scheduler.instanceId: AUTO #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 15 org.quartz.threadPool.threadPriority: 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore ================================================ FILE: examples/src/main/resources/org/quartz/examples/example12/client.properties ================================================ # Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance. # # Configure Main Scheduler Properties ====================================== org.quartz.scheduler.instanceName: Sched1 org.quartz.scheduler.logger: schedLogger org.quartz.scheduler.rmi.proxy: true org.quartz.scheduler.rmi.registryHost: localhost org.quartz.scheduler.rmi.registryPort: 1099 ================================================ FILE: examples/src/main/resources/org/quartz/examples/example12/server.properties ================================================ #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: Sched1 org.quartz.scheduler.rmi.export: true org.quartz.scheduler.rmi.registryHost: localhost org.quartz.scheduler.rmi.registryPort: 1099 org.quartz.scheduler.rmi.createRegistry: true #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore ================================================ FILE: examples/src/main/resources/org/quartz/examples/example13/instance1.properties ================================================ #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: TestScheduler org.quartz.scheduler.instanceId: instance_one #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 5 org.quartz.threadPool.threadPriority: 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate org.quartz.jobStore.useProperties=false org.quartz.jobStore.dataSource=myDS org.quartz.jobStore.tablePrefix=QRTZ_ org.quartz.jobStore.isClustered=true #============================================================================ # Configure Datasources #============================================================================ org.quartz.dataSource.myDS.driver: org.postgresql.Driver org.quartz.dataSource.myDS.URL: jdbc:postgresql://localhost:5432/quartz org.quartz.dataSource.myDS.user: quartz org.quartz.dataSource.myDS.password: quartz org.quartz.dataSource.myDS.maxConnections: 5 org.quartz.dataSource.myDS.validationQuery: select 0 ================================================ FILE: examples/src/main/resources/org/quartz/examples/example13/instance2.properties ================================================ #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: TestScheduler org.quartz.scheduler.instanceId: instance_two #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 5 org.quartz.threadPool.threadPriority: 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate org.quartz.jobStore.useProperties=false org.quartz.jobStore.dataSource=myDS org.quartz.jobStore.tablePrefix=QRTZ_ org.quartz.jobStore.isClustered=true #============================================================================ # Configure Datasources #============================================================================ org.quartz.dataSource.myDS.driver: org.postgresql.Driver org.quartz.dataSource.myDS.URL: jdbc:postgresql://localhost:5432/quartz org.quartz.dataSource.myDS.user: quartz org.quartz.dataSource.myDS.password: quartz org.quartz.dataSource.myDS.maxConnections: 5 org.quartz.dataSource.myDS.validationQuery: select 0 ================================================ FILE: examples/src/main/resources/org/quartz/examples/example14/quartz_priority.properties ================================================ org.quartz.scheduler.instanceName: PriorityExampleScheduler # Set thread count to 1 to force Triggers scheduled for the same time to # to be ordered by priority. org.quartz.threadPool.threadCount: 1 org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore ================================================ FILE: examples/src/main/resources/org/quartz/examples/example5/quartz_misfire.properties ================================================ org.quartz.scheduler.instanceName: MisfireExampleScheduler org.quartz.threadPool.threadCount: 2 org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool # Set misfire threshold to 1 second org.quartz.jobStore.misfireThreshold: 1000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ quartzVersion = 2.5.1-SNAPSHOT fullname=quartz-scheduler name=quartz version=$quartzVersion slf4jVersion = 2.0.16 log4jVersion = 2.24.1 c3p0Version = 0.10.1 hikaricpVersion = 5.0.1 jbossVersion = 4.0.2 commonjVersion = 1.0 junitVersion = 4.13.2 junitPlatformVersion = 5.11.3 derbyVersion = 10.15.2.0 testcontainersVersion = 1.21.3 # For nexus-publish plugin sonatypeUsername = OVERRIDE_ME sonatypePassword = OVERRIDE_ME ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # 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 # # https://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. # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and # * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: quartz/build.gradle ================================================ plugins { id 'java-library' id 'maven-publish' id 'biz.aQute.bnd.builder' } repositories { mavenCentral() } publishing { publications { getByName('maven').pom.description = 'Quartz Enterprise Job Scheduler' } } dependencies { compileOnly project(':quartz-stubs') implementation "org.slf4j:slf4j-api:$slf4jVersion" runtimeOnly "org.slf4j:slf4j-log4j12:$slf4jVersion" implementation "com.mchange:c3p0:$c3p0Version" implementation "com.zaxxer:HikariCP:$hikaricpVersion" compileOnly "jboss:jboss-common:$jbossVersion" compileOnly "jboss:jboss-minimal:$jbossVersion" compileOnly "jboss:jboss-system:$jbossVersion" compileOnly "jboss:jboss-jmx:$jbossVersion" compileOnly "org.apache.geronimo.specs:geronimo-commonj_1.1_spec:$commonjVersion" implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.4' compileOnly 'jakarta.transaction:jakarta.transaction-api:2.0.1' testImplementation 'jakarta.transaction:jakarta.transaction-api:2.0.1' compileOnly 'jakarta.servlet:jakarta.servlet-api:6.1.0' testImplementation "org.apache.derby:derby:$derbyVersion" testImplementation "org.apache.derby:derbyclient:$derbyVersion" testImplementation "org.apache.derby:derbynet:$derbyVersion" testImplementation 'asm:asm:3.3.1' testImplementation 'org.hamcrest:hamcrest:3.0' testImplementation(platform("org.junit:junit-bom:5.11.4")) testImplementation 'org.junit.jupiter:junit-jupiter-engine' testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation 'org.mockito:mockito-core:5.14.2' testImplementation "org.testcontainers:mssqlserver:$testcontainersVersion" testImplementation "org.testcontainers:mariadb:$testcontainersVersion" testImplementation "org.testcontainers:postgresql:$testcontainersVersion" testImplementation 'com.microsoft.sqlserver:mssql-jdbc:13.2.1.jre11' testImplementation 'org.mariadb.jdbc:mariadb-java-client:3.5.7' testImplementation 'org.postgresql:postgresql:42.7.8' testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } test { useJUnitPlatform(){ includeEngines("junit-jupiter") } maxParallelForks 1 forkEvery 1 } java { withJavadocJar() withSourcesJar() } javadoc { options { docTitle = "Quartz Enterprise Job Scheduler $project.version API" encoding = 'UTF-8' outputLevel = JavadocOutputLevel.QUIET splitIndex = false author = true; windowTitle = "Quartz Enterprise Job Scheduler $project.version API" bottom = 'Copyright IBM Corp. 2024, 2025' } } processResources { filesMatching('**/quartz-build.properties') { expand([version: project.version, fullname: project.fullname, name: project.name]) } } ================================================ FILE: quartz/src/main/java/org/quartz/Calendar.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * An interface to be implemented by objects that define spaces of time during * which an associated {@link Trigger} may (not) 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)

* *

Implementations MUST take care of being properly Cloneable * and Serializable.

* * @author James House * @author Juergen Donnerstag */ public interface Calendar extends java.io.Serializable, java.lang.Cloneable { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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); Object clone(); } ================================================ FILE: quartz/src/main/java/org/quartz/CalendarIntervalScheduleBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.TimeZone; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.triggers.CalendarIntervalTriggerImpl; import org.quartz.spi.MutableTrigger; /** * CalendarIntervalScheduleBuilder is a {@link ScheduleBuilder} * that defines calendar time (day, week, month, year) interval-based * schedules for Triggers. * *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(withIntervalInDays(3))
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * @see DailyTimeIntervalScheduleBuilder * @see CronScheduleBuilder * @see ScheduleBuilder * @see SimpleScheduleBuilder * @see TriggerBuilder */ public class CalendarIntervalScheduleBuilder extends ScheduleBuilder { private int interval = 1; private IntervalUnit intervalUnit = IntervalUnit.DAY; private int misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_SMART_POLICY; private TimeZone timeZone; private boolean preserveHourOfDayAcrossDaylightSavings; private boolean skipDayIfHourDoesNotExist; protected CalendarIntervalScheduleBuilder() { } /** * Create a CalendarIntervalScheduleBuilder. * * @return the new CalendarIntervalScheduleBuilder */ public static CalendarIntervalScheduleBuilder calendarIntervalSchedule() { return new CalendarIntervalScheduleBuilder(); } /** * Build the actual Trigger -- NOT intended to be invoked by end users, * but will rather be invoked by a TriggerBuilder which this * ScheduleBuilder is given to. * * @see TriggerBuilder#withSchedule(ScheduleBuilder) */ @Override public MutableTrigger build() { CalendarIntervalTriggerImpl st = new CalendarIntervalTriggerImpl(); st.setRepeatInterval(interval); st.setRepeatIntervalUnit(intervalUnit); st.setMisfireInstruction(misfireInstruction); st.setTimeZone(timeZone); st.setPreserveHourOfDayAcrossDaylightSavings(preserveHourOfDayAcrossDaylightSavings); st.setSkipDayIfHourDoesNotExist(skipDayIfHourDoesNotExist); return st; } /** * Specify the time unit and interval for the Trigger to be produced. * * @param timeInterval the interval at which the trigger should repeat. * @param unit the time unit (IntervalUnit) of the interval. Cannot be less than IntervalUnit.SECOND . * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() * @throws IllegalArgumentException if unit is null or is IntervalUnit.MILLISECOND */ public CalendarIntervalScheduleBuilder withInterval(int timeInterval, IntervalUnit unit) { if(unit == null) throw new IllegalArgumentException("TimeUnit must be specified."); if(unit.equals(IntervalUnit.MILLISECOND)) throw new IllegalArgumentException("TimeUnit cannot be less than SECOND."); validateInterval(timeInterval); this.interval = timeInterval; this.intervalUnit = unit; return this; } /** * Specify an interval in the IntervalUnit.SECOND that the produced * Trigger will repeat at. * * @param intervalInSeconds the number of seconds at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInSeconds(int intervalInSeconds) { validateInterval(intervalInSeconds); this.interval = intervalInSeconds; this.intervalUnit = IntervalUnit.SECOND; return this; } /** * Specify an interval in the IntervalUnit.MINUTE that the produced * Trigger will repeat at. * * @param intervalInMinutes the number of minutes at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInMinutes(int intervalInMinutes) { validateInterval(intervalInMinutes); this.interval = intervalInMinutes; this.intervalUnit = IntervalUnit.MINUTE; return this; } /** * Specify an interval in the IntervalUnit.HOUR that the produced * Trigger will repeat at. * * @param intervalInHours the number of hours at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInHours(int intervalInHours) { validateInterval(intervalInHours); this.interval = intervalInHours; this.intervalUnit = IntervalUnit.HOUR; return this; } /** * Specify an interval in the IntervalUnit.DAY that the produced * Trigger will repeat at. * * @param intervalInDays the number of days at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInDays(int intervalInDays) { validateInterval(intervalInDays); this.interval = intervalInDays; this.intervalUnit = IntervalUnit.DAY; return this; } /** * Specify an interval in the IntervalUnit.WEEK that the produced * Trigger will repeat at. * * @param intervalInWeeks the number of weeks at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInWeeks(int intervalInWeeks) { validateInterval(intervalInWeeks); this.interval = intervalInWeeks; this.intervalUnit = IntervalUnit.WEEK; return this; } /** * Specify an interval in the IntervalUnit.MONTH that the produced * Trigger will repeat at. * * @param intervalInMonths the number of months at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInMonths(int intervalInMonths) { validateInterval(intervalInMonths); this.interval = intervalInMonths; this.intervalUnit = IntervalUnit.MONTH; return this; } /** * Specify an interval in the IntervalUnit.YEAR that the produced * Trigger will repeat at. * * @param intervalInYears the number of years at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getRepeatInterval() * @see CalendarIntervalTrigger#getRepeatIntervalUnit() */ public CalendarIntervalScheduleBuilder withIntervalInYears(int intervalInYears) { validateInterval(intervalInYears); this.interval = intervalInYears; this.intervalUnit = IntervalUnit.YEAR; return this; } /** * If the Trigger misfires, use the * {@link Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY} instruction. * * @return the updated CronScheduleBuilder * @see Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY */ public CalendarIntervalScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() { misfireInstruction = Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY; return this; } /** * If the Trigger misfires, use the * {@link CalendarIntervalTrigger#MISFIRE_INSTRUCTION_DO_NOTHING} instruction. * * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#MISFIRE_INSTRUCTION_DO_NOTHING */ public CalendarIntervalScheduleBuilder withMisfireHandlingInstructionDoNothing() { misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_DO_NOTHING; return this; } /** * If the Trigger misfires, use the * {@link CalendarIntervalTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW} instruction. * * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW */ public CalendarIntervalScheduleBuilder withMisfireHandlingInstructionFireAndProceed() { misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; return this; } /** * The TimeZone in which to base the schedule. * * @param timezone the time-zone for the schedule. * @return the updated CalendarIntervalScheduleBuilder * @see CalendarIntervalTrigger#getTimeZone() */ public CalendarIntervalScheduleBuilder inTimeZone(TimeZone timezone) { this.timeZone = timezone; return this; } /** * If intervals are a day or greater, this property (set to true) will * cause the firing of the trigger to always occur at the same time of day, * (the time of day of the startTime) regardless of daylight saving time * transitions. Default value is false. * *

* For example, without the property set, your trigger may have a start * time of 9:00 am on March 1st, and a repeat interval of 2 days. But * after the daylight saving transition occurs, the trigger may start * firing at 8:00 am every other day. *

* *

* If however, the time of day does not exist on a given day to fire * (e.g. 2:00 am in the United States on the days of daylight saving * transition), the trigger will go ahead and fire one hour off on * that day, and then resume the normal hour on other days. If * you wish for the trigger to never fire at the "wrong" hour, then * you should set the property skipDayIfHourDoesNotExist. *

* * @see #skipDayIfHourDoesNotExist(boolean) * @see #inTimeZone(TimeZone) * @see TriggerBuilder#startAt(java.util.Date) */ public CalendarIntervalScheduleBuilder preserveHourOfDayAcrossDaylightSavings(boolean preserveHourOfDay) { this.preserveHourOfDayAcrossDaylightSavings = preserveHourOfDay; return this; } /** * If intervals are a day or greater, and * preserveHourOfDayAcrossDaylightSavings property is set to true, and the * hour of the day does not exist on a given day for which the trigger * would fire, the day will be skipped and the trigger advanced a second * interval if this property is set to true. Defaults to false. * *

* CAUTION! If you enable this property, and your hour of day happens * to be that of daylight savings transition (e.g. 2:00 am in the United * States) and the trigger's interval would have had the trigger fire on * that day, then you may actually completely miss a firing on the day of * transition if that hour of day does not exist on that day! In such a * case the next fire time of the trigger will be computed as double (if * the interval is 2 days, then a span of 4 days between firings will * occur). *

* * @see #preserveHourOfDayAcrossDaylightSavings(boolean) */ public CalendarIntervalScheduleBuilder skipDayIfHourDoesNotExist(boolean skipDay) { this.skipDayIfHourDoesNotExist = skipDay; return this; } private void validateInterval(int timeInterval) { if(timeInterval <= 0) throw new IllegalArgumentException("Interval must be a positive value."); } } ================================================ FILE: quartz/src/main/java/org/quartz/CalendarIntervalTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import java.util.TimeZone; import org.quartz.DateBuilder.IntervalUnit; /** * A concrete {@link Trigger} that is used to fire a {@link org.quartz.JobDetail} * based upon repeating calendar time intervals. * *

The trigger will fire every N (see {@link #getRepeatInterval()} ) units of calendar time * (see {@link #getRepeatIntervalUnit()}) as specified in the trigger's definition. * This trigger can achieve schedules that are not possible with {@link SimpleTrigger} (e.g * because months are not a fixed number of seconds) or {@link CronTrigger} (e.g. because * "every 5 months" is not an even divisor of 12).

* *

If you use an interval unit of MONTH then care should be taken when setting * a startTime value that is on a day near the end of the month. For example, * if you choose a start time that occurs on January 31st, and have a trigger with unit * MONTH and interval 1, then the next fire time will be February 28th, * and the next time after that will be March 28th - and essentially each subsequent firing will * occur on the 28th of the month, even if a 31st day exists. If you want a trigger that always * fires on the last day of the month - regardless of the number of days in the month, * you should use CronTrigger.

* * @see TriggerBuilder * @see CalendarIntervalScheduleBuilder * @see SimpleScheduleBuilder * @see CronScheduleBuilder * * @author James House */ public interface CalendarIntervalTrigger extends Trigger { /** *

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

*/ int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1; /** *

* Instructs the {@link Scheduler} that upon a mis-fire * situation, the {@link CalendarIntervalTrigger} 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. *

*/ int MISFIRE_INSTRUCTION_DO_NOTHING = 2; /** *

Get the interval unit - the time unit on with the interval applies.

*/ IntervalUnit getRepeatIntervalUnit(); /** *

* Get the time interval that will be added to the DateIntervalTrigger's * fire time (in the set repeat interval unit) in order to calculate the time of the * next trigger repeat. *

*/ int getRepeatInterval(); /** *

* Get the number of times the DateIntervalTrigger has already * fired. *

*/ int getTimesTriggered(); /** *

* Gets the time zone within which time calculations related to this * trigger will be performed. *

* *

* If null, the system default TimeZone will be used. *

*/ TimeZone getTimeZone(); /** * If intervals are a day or greater, this property (set to true) will * cause the firing of the trigger to always occur at the same time of day, * (the time of day of the startTime) regardless of daylight saving time * transitions. Default value is false. * *

* For example, without the property set, your trigger may have a start * time of 9:00 am on March 1st, and a repeat interval of 2 days. But * after the daylight saving transition occurs, the trigger may start * firing at 8:00 am every other day. *

* *

* If however, the time of day does not exist on a given day to fire * (e.g. 2:00 am in the United States on the days of daylight saving * transition), the trigger will go ahead and fire one hour off on * that day, and then resume the normal hour on other days. If * you wish for the trigger to never fire at the "wrong" hour, then * you should set the property skipDayIfHourDoesNotExist. *

* * @see #isSkipDayIfHourDoesNotExist() * @see #getStartTime() * @see #getTimeZone() */ boolean isPreserveHourOfDayAcrossDaylightSavings(); /** * If intervals are a day or greater, and * preserveHourOfDayAcrossDaylightSavings property is set to true, and the * hour of the day does not exist on a given day for which the trigger * would fire, the day will be skipped and the trigger advanced a second * interval if this property is set to true. Defaults to false. * *

* CAUTION! If you enable this property, and your hour of day happens * to be that of daylight savings transition (e.g. 2:00 am in the United * States) and the trigger's interval would have had the trigger fire on * that day, then you may actually completely miss a firing on the day of * transition if that hour of day does not exist on that day! In such a * case the next fire time of the trigger will be computed as double (if * the interval is 2 days, then a span of 4 days between firings will * occur). *

* * @see #isPreserveHourOfDayAcrossDaylightSavings() */ boolean isSkipDayIfHourDoesNotExist(); TriggerBuilder getTriggerBuilder(); } ================================================ FILE: quartz/src/main/java/org/quartz/CronExpression.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.Optional; 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: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Examples of cron expressions and their meanings.
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-2199 , - * /
*

* 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 0 to 11 (JAN to DEC). 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". You can also specify an offset * from the last day of the month, such as "L-3" which would mean the third-to-last * day of the calendar month. When using the 'L' option, it is important not to * specify lists, or ranges of values, as you'll get confusing/unexpected 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: *

*
    *
  • Support for specifying both a day-of-week and a day-of-month value is * not complete (you'll need to use the '?' character in one of these fields). *
  • *
  • Overflowing ranges is supported - that is, having a larger number on * the left hand side than the right. You might do 22-2 to catch 10 o'clock * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is * very important to note that overuse of overflowing ranges creates ranges * that don't make sense and no effort has been made to determine which * interpretation CronExpression chooses. An example would be * "0 0 14-6 ? * FRI-MON".
  • *
* * * @author Sharada Jambula, James House * @author Contributions from Mads Henderson * @author Refactoring from CronTrigger to CronExpression by Aaron Craven */ public final 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 int MAX_LAST_DAY_OFFSET = 30; protected static final int LAST_DAY_OFFSET_START = 32; // "L-30" protected static final int LAST_DAY_OFFSET_END = LAST_DAY_OFFSET_START + MAX_LAST_DAY_OFFSET; // 'L' protected static final Integer ALL_SPEC = ALL_SPEC_INT; protected static final Integer NO_SPEC = NO_SPEC_INT; protected static final Map monthMap = new HashMap<>(20); protected static final Map dayMap = new HashMap<>(60); static { monthMap.put("JAN", 0); monthMap.put("FEB", 1); monthMap.put("MAR", 2); monthMap.put("APR", 3); monthMap.put("MAY", 4); monthMap.put("JUN", 5); monthMap.put("JUL", 6); monthMap.put("AUG", 7); monthMap.put("SEP", 8); monthMap.put("OCT", 9); monthMap.put("NOV", 10); monthMap.put("DEC", 11); dayMap.put("SUN", 1); dayMap.put("MON", 2); dayMap.put("TUE", 3); dayMap.put("WED", 4); dayMap.put("THU", 5); dayMap.put("FRI", 6); dayMap.put("SAT", 7); } private final String cronExpression; private TimeZone timeZone = null; protected transient TreeSet seconds; protected transient TreeSet minutes; protected transient TreeSet hours; protected transient TreeSet daysOfMonth; protected transient TreeSet nearestWeekdays; 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 expressionParsed = false; public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; /** * 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); } /** * Constructs a new {@code CronExpression} as a copy of an existing * instance. * * @param expression * The existing cron expression to be copied */ public CronExpression(CronExpression expression) { /* * We don't call the other constructor here since we need to swallow the * ParseException. We also elide some of the sanity checking as it is * not logically trippable. */ this.cronExpression = expression.getCronExpression(); try { buildExpression(cronExpression); } catch (ParseException ex) { throw new AssertionError("Could not parse expression!", ex); } if (expression.getTimeZone() != null) { setTimeZone((TimeZone) expression.getTimeZone().clone()); } } /** * 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; //FUTURE_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); if(newDate == null) break; 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 */ @Override 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; } public static void validateExpression(String cronExpression) throws ParseException { new CronExpression(cronExpression); } //////////////////////////////////////////////////////////////////////////// // // 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 (nearestWeekdays == null) { nearestWeekdays = 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); if(exprsTok.countTokens() > 7) { throw new ParseException("Invalid expression has too many terms: " + expression, -1); } while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { String expr = exprsTok.nextToken().trim(); // 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.contains(",")) { throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); } if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) { throw new ParseException("Support for specifying multiple \"nth\" days 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) { if (!dayOfWSpec || dayOfMSpec) { 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 + ")", 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")) && (!s.matches("^L-[0-9]*[W]?"))) { 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 specified for Day-of-Month or Day-of-Week.", i); } if (type == DAY_OF_WEEK) { if (!daysOfMonth.isEmpty() && daysOfMonth.last() == NO_SPEC_INT) { throw new ParseException( "'?' can only be specified 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++; } checkIncrementRange(incr, type, i); } else { incr = 1; } addToSet(ALL_SPEC_INT, -1, incr, type); return i; } else if (c == 'L') { if(type < DAY_OF_MONTH) throw new ParseException("'L' not expected in seconds, minutes or hours fields.", i); i++; if (type == DAY_OF_WEEK) { addToSet(7, 7, 0, type); } if (type == DAY_OF_MONTH) { int dom = LAST_DAY_OFFSET_END; boolean nearestWeekday = false; if (s.length() > i) { c = s.charAt(i); if (c == '-') { ValueSet vs = getValue(0, s, i + 1); int offset = vs.value; if (offset > MAX_LAST_DAY_OFFSET) throw new ParseException("Offset from last day must be <= " + MAX_LAST_DAY_OFFSET, i + 1); dom -= offset; i = vs.pos; } if (s.length() > i) { c = s.charAt(i); if (c == 'W') { nearestWeekday = true; i++; } } } if (nearestWeekday) { nearestWeekdays.add(dom); } else { daysOfMonth.add(dom); } } 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; } private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException { if (incr > 59 && (type == SECOND || type == MINUTE)) { throw new ParseException("Increment >= 60 : " + incr, idxPos); } else if (incr > 23 && (type == HOUR)) { throw new ParseException("Increment >= 24 : " + incr, idxPos); } else if (incr > 31 && (type == DAY_OF_MONTH)) { throw new ParseException("Increment >= 31 : " + incr, idxPos); } else if (incr > 7 && (type == DAY_OF_WEEK)) { throw new ParseException("Increment >= 7 : " + incr, idxPos); } else if (incr > 12 && (type == MONTH)) { throw new ParseException("Increment >= 12 : " + incr, idxPos); } } 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) { if(val < 1 || val > 7) throw new ParseException("Day-of-Week values must be between 1 and 7", -1); lastDayOfWeek = true; } else { throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); } TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == 'W') { if (type != DAY_OF_MONTH) { throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); } if(val > 31) throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); nearestWeekdays.add(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(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); end = vs.value; 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 == '/') { if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') { throw new ParseException("'/' must be followed by an integer.", i); } i++; c = s.charAt(i); int v2 = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { checkIncrementRange(v2, type, i); 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; checkIncrementRange(v3, type, i); 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() { StringBuilder buf = new StringBuilder(); 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("nearestWeekdays: "); buf.append(getExpressionSetSummary(nearestWeekdays)); 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("NthDayOfWeek: "); buf.append(nthDayOfWeek); 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 "*"; } StringBuilder buf = new StringBuilder(); Iterator itr = set.iterator(); boolean first = true; while (itr.hasNext()) { Integer iVal = 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 "*"; } StringBuilder buf = new StringBuilder(); Iterator itr = list.iterator(); boolean first = true; while (itr.hasNext()) { Integer iVal = 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(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 = MAX_YEAR; } 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(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(i2); } } } 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); StringBuilder s1 = new StringBuilder(String.valueOf(v)); while (c >= '0' && c <= '9') { s1.append(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.toString()); 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 = monthMap.get(s); if (integer == null) { return -1; } return integer; } protected int getDayOfWeekNumber(String s) { Integer integer = dayMap.get(s); if (integer == null) { return -1; } return integer; } //////////////////////////////////////////////////////////////////////////// // // Computation Functions // //////////////////////////////////////////////////////////////////////////// public Date getTimeAfter(Date afterTime) { // Computation is based on Gregorian year only. Calendar cl = new java.util.GregorianCalendar(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(sec); if (st != null && !st.isEmpty()) { sec = st.first(); } else { sec = seconds.first(); 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(min); if (st != null && !st.isEmpty()) { t = min; min = st.first(); } else { min = minutes.first(); 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(hr); if (st != null && !st.isEmpty()) { t = hr; hr = st.first(); } else { hr = hours.first(); 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 Optional smallestDay = findSmallestDay(day, mon, cl.get(Calendar.YEAR), daysOfMonth); Optional smallestDayForWeekday = findSmallestDay(day, mon, cl.get(Calendar.YEAR), nearestWeekdays); t = day; day = -1; if (smallestDayForWeekday.isPresent()) { day = smallestDayForWeekday.get(); 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; } } boolean needAdvance = false; if (smallestDay.isPresent()) { if (day == -1 || smallestDay.get() < day) { day = smallestDay.get(); needAdvance = true; } } else if (day == -1) { day = 1; mon++; needAdvance = true; } if (needAdvance && (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 = daysOfWeek.first(); // 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 occurrence 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 = daysOfWeek.first(); // 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 = daysToAdd > 0; 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 = daysOfWeek.first(); // desired // d-o-w st = daysOfWeek.tailSet(cDow); if (st != null && !st.isEmpty()) { dow = st.first(); } 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 switching 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."); } 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 > MAX_YEAR) { return null; } // get month................................................... st = months.tailSet(mon); if (st != null && !st.isEmpty()) { t = mon; mon = st.first(); } else { mon = months.first(); 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(year); if (st != null && !st.isEmpty()) { t = year; year = st.first(); } 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 the calendar to operate on * @param hour the hour to set */ 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); } } /** * Returns the time before the given time * that the CronExpression matches. * * @param endTime a time for which the previous * matching time is returned * @return the previous matching time before the given end time, * or null if there are no previous matching times */ public Date getTimeBefore(Date endTime) { // the current implementation is not a direct calculation, but rather // uses getTimeAfter with a binary search to find the previous match time long end = endTime.getTime(); long min = 0; // the epoch date is the minimum supported by this class long max = end; // check if it's satisfiable at all Date date = new Date(min); Date after = getTimeAfter(date); if (after == null || after.getTime() >= end) return null; // there are no after-times before end // from this point forward min's time-after is always less than end, // and max's time-after is always equal to or greater than end // so we just need to shrink the interval until they meet. // optimization - perform inverse binary search to find a tighter lower bound long interval = 60 * 60 * 1000; // start with a reasonable interval while (interval < max) { date.setTime(max - interval); after = getTimeAfter(date); if (after != null && after.getTime() < max) { min = date.getTime(); // found a closer min break; } interval *= 2; } // perform a regular binary search to find the earliest moment // whose time-after is equal to or greater than the end time - // this moment is the previous match time itself while (max - min > 1000) { // we can stop at 1 second resolution long mid = (min + max) >>> 1; date.setTime(mid); after = getTimeAfter(date); if (after != null && after.getTime() < end) min = mid; else max = mid; } date.setTime(max - max % 1000); // round to second return date; } /** * NOT YET IMPLEMENTED: Returns the final time that the * CronExpression will match. */ public Date getFinalFireTime() { // FUTURE_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 Optional findSmallestDay(int day, int mon, int year, TreeSet set) { if (set.isEmpty()) { return Optional.empty(); } final int lastDay = getLastDayOfMonth(mon, year); // For "L", "L-1", etc. final int smallestDay = Optional.ofNullable(set.ceiling(LAST_DAY_OFFSET_END - (lastDay - day))) .map(d -> d - LAST_DAY_OFFSET_START + 1) .orElse(Integer.MAX_VALUE); // For "1", "2", etc. SortedSet st = set.subSet(day, LAST_DAY_OFFSET_START); // make sure we don't over-run a short month, such as february if (!st.isEmpty() && st.first() < smallestDay && st.first() <= lastDay) { return Optional.of(st.first()); } if (smallestDay == Integer.MAX_VALUE) { return Optional.empty(); } else { return Optional.of(smallestDay + lastDay - LAST_DAY_OFFSET_START + 1); } } private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { stream.defaultReadObject(); try { buildExpression(cronExpression); } catch (Exception ignore) { } // never happens } @Override @Deprecated public Object clone() { return new CronExpression(this); } } class ValueSet { public int value; public int pos; } ================================================ FILE: quartz/src/main/java/org/quartz/CronScheduleBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.text.ParseException; import java.util.TimeZone; import org.quartz.impl.triggers.CronTriggerImpl; import org.quartz.spi.MutableTrigger; /** * CronScheduleBuilder is a {@link ScheduleBuilder} that defines * {@link CronExpression}-based schedules for Triggers. * *

* Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be utilized * through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey and * the various ScheduleBuilder implementations. *

* *

* Client code can then use the DSL to write code such as this: *

* *
 * JobDetail job = newJob(MyJob.class).withIdentity("myJob").build();
 * 
 * Trigger trigger = newTrigger()
 *         .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *         .withSchedule(dailyAtHourAndMinute(10, 0))
 *         .startAt(futureDate(10, MINUTES)).build();
 * 
 * scheduler.scheduleJob(job, trigger);
 * 
 * 
* * @see CronExpression * @see CronTrigger * @see ScheduleBuilder * @see SimpleScheduleBuilder * @see CalendarIntervalScheduleBuilder * @see TriggerBuilder */ public class CronScheduleBuilder extends ScheduleBuilder { private final CronExpression cronExpression; private int misfireInstruction = CronTrigger.MISFIRE_INSTRUCTION_SMART_POLICY; protected CronScheduleBuilder(CronExpression cronExpression) { if (cronExpression == null) { throw new NullPointerException("cronExpression cannot be null"); } this.cronExpression = cronExpression; } /** * Build the actual Trigger -- NOT intended to be invoked by end users, but * will rather be invoked by a TriggerBuilder which this ScheduleBuilder is * given to. * * @see TriggerBuilder#withSchedule(ScheduleBuilder) */ @Override public MutableTrigger build() { CronTriggerImpl ct = new CronTriggerImpl(); ct.setCronExpression(cronExpression); ct.setTimeZone(cronExpression.getTimeZone()); ct.setMisfireInstruction(misfireInstruction); return ct; } /** * Create a CronScheduleBuilder with the given cron-expression string - * which is presumed to be a valid cron expression (and hence only a * RuntimeException will be thrown if it is not). * * @param cronExpression * the cron expression string to base the schedule on. * @return the new CronScheduleBuilder * @throws RuntimeException * wrapping a ParseException if the expression is invalid * @see CronExpression */ public static CronScheduleBuilder cronSchedule(String cronExpression) { try { return cronSchedule(new CronExpression(cronExpression)); } catch (ParseException e) { // all methods of construction ensure the expression is valid by // this point... throw new RuntimeException("CronExpression '" + cronExpression + "' is invalid.", e); } } /** * Create a CronScheduleBuilder with the given cron-expression string - * which may not be a valid cron expression (and hence a ParseException will * be thrown if it is not). * * @param cronExpression * the cron expression string to base the schedule on. * @return the new CronScheduleBuilder * @throws ParseException * if the expression is invalid * @see CronExpression */ public static CronScheduleBuilder cronScheduleNonvalidatedExpression( String cronExpression) throws ParseException { return cronSchedule(new CronExpression(cronExpression)); } private static CronScheduleBuilder cronScheduleNoParseException( String presumedValidCronExpression) { try { return cronSchedule(new CronExpression(presumedValidCronExpression)); } catch (ParseException e) { // all methods of construction ensure the expression is valid by // this point... throw new RuntimeException( "CronExpression '" + presumedValidCronExpression + "' is invalid, which should not be possible, please report bug to Quartz developers.", e); } } /** * Create a CronScheduleBuilder with the given cron-expression. * * @param cronExpression * the cron expression to base the schedule on. * @return the new CronScheduleBuilder * @see CronExpression */ public static CronScheduleBuilder cronSchedule(CronExpression cronExpression) { return new CronScheduleBuilder(cronExpression); } /** * Create a CronScheduleBuilder with a cron-expression that sets the * schedule to fire every day at the given time (hour and minute). * * @param hour * the hour of day to fire * @param minute * the minute of the given hour to fire * @return the new CronScheduleBuilder * @see CronExpression */ public static CronScheduleBuilder dailyAtHourAndMinute(int hour, int minute) { DateBuilder.validateHour(hour); DateBuilder.validateMinute(minute); String cronExpression = String.format("0 %d %d ? * *", minute, hour); return cronScheduleNoParseException(cronExpression); } /** * Create a CronScheduleBuilder with a cron-expression that sets the * schedule to fire at the given day at the given time (hour and minute) on * the given days of the week. * * @param daysOfWeek * the day of the week to fire * @param hour * the hour of day to fire * @param minute * the minute of the given hour to fire * @return the new CronScheduleBuilder * @see CronExpression * @see DateBuilder#MONDAY * @see DateBuilder#TUESDAY * @see DateBuilder#WEDNESDAY * @see DateBuilder#THURSDAY * @see DateBuilder#FRIDAY * @see DateBuilder#SATURDAY * @see DateBuilder#SUNDAY */ public static CronScheduleBuilder atHourAndMinuteOnGivenDaysOfWeek( int hour, int minute, Integer... daysOfWeek) { if (daysOfWeek == null || daysOfWeek.length == 0) throw new IllegalArgumentException( "You must specify at least one day of week."); for (int dayOfWeek : daysOfWeek) DateBuilder.validateDayOfWeek(dayOfWeek); DateBuilder.validateHour(hour); DateBuilder.validateMinute(minute); StringBuilder cronExpression = new StringBuilder(String.format("0 %d %d ? * %d", minute, hour, daysOfWeek[0])); for (int i = 1; i < daysOfWeek.length; i++) cronExpression.append(",").append(daysOfWeek[i]); return cronScheduleNoParseException(cronExpression.toString()); } /** * Create a CronScheduleBuilder with a cron-expression that sets the * schedule to fire one per week on the given day at the given time (hour * and minute). * * @param dayOfWeek * the day of the week to fire * @param hour * the hour of day to fire * @param minute * the minute of the given hour to fire * @return the new CronScheduleBuilder * @see CronExpression * @see DateBuilder#MONDAY * @see DateBuilder#TUESDAY * @see DateBuilder#WEDNESDAY * @see DateBuilder#THURSDAY * @see DateBuilder#FRIDAY * @see DateBuilder#SATURDAY * @see DateBuilder#SUNDAY */ public static CronScheduleBuilder weeklyOnDayAndHourAndMinute( int dayOfWeek, int hour, int minute) { DateBuilder.validateDayOfWeek(dayOfWeek); DateBuilder.validateHour(hour); DateBuilder.validateMinute(minute); String cronExpression = String.format("0 %d %d ? * %d", minute, hour, dayOfWeek); return cronScheduleNoParseException(cronExpression); } /** * Create a CronScheduleBuilder with a cron-expression that sets the * schedule to fire one per month on the given day of month at the given * time (hour and minute). * * @param dayOfMonth * the day of the month to fire * @param hour * the hour of day to fire * @param minute * the minute of the given hour to fire * @return the new CronScheduleBuilder * @see CronExpression */ public static CronScheduleBuilder monthlyOnDayAndHourAndMinute( int dayOfMonth, int hour, int minute) { DateBuilder.validateDayOfMonth(dayOfMonth); DateBuilder.validateHour(hour); DateBuilder.validateMinute(minute); String cronExpression = String.format("0 %d %d %d * ?", minute, hour, dayOfMonth); return cronScheduleNoParseException(cronExpression); } /** * The TimeZone in which to base the schedule. * * @param timezone * the time-zone for the schedule. * @return the updated CronScheduleBuilder * @see CronExpression#getTimeZone() */ public CronScheduleBuilder inTimeZone(TimeZone timezone) { cronExpression.setTimeZone(timezone); return this; } /** * If the Trigger misfires, use the * {@link Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY} instruction. * * @return the updated CronScheduleBuilder * @see Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY */ public CronScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() { misfireInstruction = Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY; return this; } /** * If the Trigger misfires, use the * {@link CronTrigger#MISFIRE_INSTRUCTION_DO_NOTHING} instruction. * * @return the updated CronScheduleBuilder * @see CronTrigger#MISFIRE_INSTRUCTION_DO_NOTHING */ public CronScheduleBuilder withMisfireHandlingInstructionDoNothing() { misfireInstruction = CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING; return this; } /** * If the Trigger misfires, use the * {@link CronTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW} instruction. * * @return the updated CronScheduleBuilder * @see CronTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW */ public CronScheduleBuilder withMisfireHandlingInstructionFireAndProceed() { misfireInstruction = CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; return this; } } ================================================ FILE: quartz/src/main/java/org/quartz/CronTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import java.util.TimeZone; /** * The public interface for inspecting settings specific to a CronTrigger, . * which is used to fire a {@link org.quartz.Job} * at given moments in time, defined with Unix 'cron-like' schedule 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 org.quartz.CronExpression} class. *

* *

* Here are some full examples:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Examples of cron expressions and their meanings.
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: *

*
    *
  • Support for specifying both a day-of-week and a day-of-month value is * not complete (you'll need to use the '?' character in on of these fields). *
  • *
  • Be careful when setting fire times between mid-night and 1:00 AM - * "daylight savings" can cause a skip or a repeat depending on whether the * time moves back or jumps forward.
  • *
* * @see CronScheduleBuilder * @see TriggerBuilder * * @author jhouse * @author Contributions from Mads Henderson */ public interface CronTrigger extends Trigger { long serialVersionUID = -8644953146451592766L; /** *

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

*/ 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. *

*/ int MISFIRE_INSTRUCTION_DO_NOTHING = 2; String getCronExpression(); /** *

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

*/ TimeZone getTimeZone(); String getExpressionSummary(); TriggerBuilder getTriggerBuilder(); } ================================================ FILE: quartz/src/main/java/org/quartz/DailyTimeIntervalScheduleBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Set; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.triggers.DailyTimeIntervalTriggerImpl; import org.quartz.spi.MutableTrigger; /** * A {@link ScheduleBuilder} implementation that build schedule for DailyTimeIntervalTrigger. * *

This builder provide an extra convenient method for you to set the trigger's endTimeOfDay. You may * use either endingDailyAt() or endingDailyAfterCount() to set the value. The later will auto calculate * your endTimeOfDay by using the interval, intervalUnit and startTimeOfDay to perform the calculation. * *

When using endingDailyAfterCount(), you should note that it is used to calculating endTimeOfDay. So * if your startTime on the first day is already pass by a time that would not add up to the count you * expected, until the next day comes. Remember that DailyTimeIntervalTrigger will use startTimeOfDay * and endTimeOfDay as fresh per each day! * *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(onDaysOfTheWeek(MONDAY, THURSDAY))
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * @since 2.1.0 * * @author James House * @author Zemian Deng <saltnlight5@gmail.com> */ public class DailyTimeIntervalScheduleBuilder extends ScheduleBuilder { private int interval = 1; private IntervalUnit intervalUnit = IntervalUnit.MINUTE; private Set daysOfWeek; private TimeOfDay startTimeOfDay; private TimeOfDay endTimeOfDay; private int repeatCount = DailyTimeIntervalTrigger.REPEAT_INDEFINITELY; private int misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_SMART_POLICY; /** * A set of all days of the week. * * The set contains all values between {@link java.util.Calendar#SUNDAY} and {@link java.util.Calendar#SATURDAY} * (the integers from 1 through 7). */ public static final Set ALL_DAYS_OF_THE_WEEK; /** * A set of the business days of the week (for locales similar to the USA). * * The set contains all values between {@link java.util.Calendar#MONDAY} and {@link java.util.Calendar#FRIDAY} * (the integers from 2 through 6). */ public static final Set MONDAY_THROUGH_FRIDAY; /** * A set of the weekend days of the week (for locales similar to the USA). * * The set contains {@link java.util.Calendar#SATURDAY} and {@link java.util.Calendar#SUNDAY} */ public static final Set SATURDAY_AND_SUNDAY; static { Set t = new HashSet<>(7); for(int i=Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) t.add(i); ALL_DAYS_OF_THE_WEEK = Collections.unmodifiableSet(t); t = new HashSet<>(5); for(int i=Calendar.MONDAY; i <= Calendar.FRIDAY; i++) t.add(i); MONDAY_THROUGH_FRIDAY = Collections.unmodifiableSet(t); t = new HashSet<>(2); t.add(Calendar.SUNDAY); t.add(Calendar.SATURDAY); SATURDAY_AND_SUNDAY = Collections.unmodifiableSet(t); } protected DailyTimeIntervalScheduleBuilder() { } /** * Create a DailyTimeIntervalScheduleBuilder. * * @return the new DailyTimeIntervalScheduleBuilder */ public static DailyTimeIntervalScheduleBuilder dailyTimeIntervalSchedule() { return new DailyTimeIntervalScheduleBuilder(); } /** * Build the actual Trigger -- NOT intended to be invoked by end users, * but will rather be invoked by a TriggerBuilder which this * ScheduleBuilder is given to. * * @see TriggerBuilder#withSchedule(ScheduleBuilder) */ @Override public MutableTrigger build() { DailyTimeIntervalTriggerImpl st = new DailyTimeIntervalTriggerImpl(); st.setRepeatInterval(interval); st.setRepeatIntervalUnit(intervalUnit); st.setMisfireInstruction(misfireInstruction); st.setRepeatCount(repeatCount); if(daysOfWeek != null) st.setDaysOfWeek(daysOfWeek); else st.setDaysOfWeek(ALL_DAYS_OF_THE_WEEK); if(startTimeOfDay != null) st.setStartTimeOfDay(startTimeOfDay); else st.setStartTimeOfDay(TimeOfDay.hourAndMinuteOfDay(0, 0)); if(endTimeOfDay != null) st.setEndTimeOfDay(endTimeOfDay); else st.setEndTimeOfDay(TimeOfDay.hourMinuteAndSecondOfDay(23, 59, 59)); return st; } /** * Specify the time unit and interval for the Trigger to be produced. * * @param timeInterval the interval at which the trigger should repeat. * @param unit the time unit (IntervalUnit) of the interval. The only intervals that are valid for this type of * trigger are {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. * @return the updated DailyTimeIntervalScheduleBuilder * @see DailyTimeIntervalTrigger#getRepeatInterval() * @see DailyTimeIntervalTrigger#getRepeatIntervalUnit() */ public DailyTimeIntervalScheduleBuilder withInterval(int timeInterval, IntervalUnit unit) { if (unit == null || !(unit.equals(IntervalUnit.SECOND) || unit.equals(IntervalUnit.MINUTE) ||unit.equals(IntervalUnit.HOUR))) throw new IllegalArgumentException("Invalid repeat IntervalUnit (must be SECOND, MINUTE or HOUR)."); validateInterval(timeInterval); this.interval = timeInterval; this.intervalUnit = unit; return this; } /** * Specify an interval in the IntervalUnit.SECOND that the produced * Trigger will repeat at. * * @param intervalInSeconds the number of seconds at which the trigger should repeat. * @return the updated DailyTimeIntervalScheduleBuilder * @see DailyTimeIntervalTrigger#getRepeatInterval() * @see DailyTimeIntervalTrigger#getRepeatIntervalUnit() */ public DailyTimeIntervalScheduleBuilder withIntervalInSeconds(int intervalInSeconds) { withInterval(intervalInSeconds, IntervalUnit.SECOND); return this; } /** * Specify an interval in the IntervalUnit.MINUTE that the produced * Trigger will repeat at. * * @param intervalInMinutes the number of minutes at which the trigger should repeat. * @return the updated CalendarIntervalScheduleBuilder * @see DailyTimeIntervalTrigger#getRepeatInterval() * @see DailyTimeIntervalTrigger#getRepeatIntervalUnit() */ public DailyTimeIntervalScheduleBuilder withIntervalInMinutes(int intervalInMinutes) { withInterval(intervalInMinutes, IntervalUnit.MINUTE); return this; } /** * Specify an interval in the IntervalUnit.HOUR that the produced * Trigger will repeat at. * * @param intervalInHours the number of hours at which the trigger should repeat. * @return the updated DailyTimeIntervalScheduleBuilder * @see DailyTimeIntervalTrigger#getRepeatInterval() * @see DailyTimeIntervalTrigger#getRepeatIntervalUnit() */ public DailyTimeIntervalScheduleBuilder withIntervalInHours(int intervalInHours) { withInterval(intervalInHours, IntervalUnit.HOUR); return this; } /** * Set the trigger to fire on the given days of the week. * * @param onDaysOfWeek a Set containing the integers representing the days of the week, per the values 1-7 as defined by * {@link java.util.Calendar#SUNDAY} - {@link java.util.Calendar#SATURDAY}. * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder onDaysOfTheWeek(Set onDaysOfWeek) { if(onDaysOfWeek == null || onDaysOfWeek.isEmpty()) throw new IllegalArgumentException("Days of week must be an non-empty set."); for (Integer day : onDaysOfWeek) if (!ALL_DAYS_OF_THE_WEEK.contains(day)) throw new IllegalArgumentException("Invalid value for day of week: " + day); this.daysOfWeek = onDaysOfWeek; return this; } /** * Set the trigger to fire on the given days of the week. * * @param onDaysOfWeek a variable length list of Integers representing the days of the week, per the values 1-7 as * defined by {@link java.util.Calendar#SUNDAY} - {@link java.util.Calendar#SATURDAY}. * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder onDaysOfTheWeek(Integer ... onDaysOfWeek) { Set daysAsSet = new HashSet<>(12); Collections.addAll(daysAsSet, onDaysOfWeek); return onDaysOfTheWeek(daysAsSet); } /** * Set the trigger to fire on the days from Monday through Friday. * * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder onMondayThroughFriday() { this.daysOfWeek = MONDAY_THROUGH_FRIDAY; return this; } /** * Set the trigger to fire on the days Saturday and Sunday. * * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder onSaturdayAndSunday() { this.daysOfWeek = SATURDAY_AND_SUNDAY; return this; } /** * Set the trigger to fire on all days of the week. * * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder onEveryDay() { this.daysOfWeek = ALL_DAYS_OF_THE_WEEK; return this; } /** * Set the trigger to begin firing each day at the given time. * * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder startingDailyAt(TimeOfDay timeOfDay) { if(timeOfDay == null) throw new IllegalArgumentException("Start time of day cannot be null!"); this.startTimeOfDay = timeOfDay; return this; } /** * Set the startTimeOfDay for this trigger to end firing each day at the given time. * * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder endingDailyAt(TimeOfDay timeOfDay) { this.endTimeOfDay = timeOfDay; return this; } /** * Calculate and set the endTimeOfDay using count, interval and starTimeOfDay. This means * that these must be set before this method is call. * * @return the updated DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder endingDailyAfterCount(int count) { if(count <=0) throw new IllegalArgumentException("Ending daily after count must be a positive number!"); if(startTimeOfDay == null) throw new IllegalArgumentException("You must set the startDailyAt() before calling this endingDailyAfterCount()!"); Date today = new Date(); Date startTimeOfDayDate = startTimeOfDay.getTimeOfDayForDate(today); Date maxEndTimeOfDayDate = TimeOfDay.hourMinuteAndSecondOfDay(23, 59, 59).getTimeOfDayForDate(today); long remainingMillisInDay = maxEndTimeOfDayDate.getTime() - startTimeOfDayDate.getTime(); long intervalInMillis; if (intervalUnit == IntervalUnit.SECOND) intervalInMillis = interval * 1000L; else if (intervalUnit == IntervalUnit.MINUTE) intervalInMillis = interval * 1000L * 60; else if (intervalUnit == IntervalUnit.HOUR) intervalInMillis = interval * 1000L * 60 * 60; else throw new IllegalArgumentException("The IntervalUnit: " + intervalUnit + " is invalid for this trigger."); if (remainingMillisInDay - intervalInMillis <= 0) throw new IllegalArgumentException("The startTimeOfDay is too late with given Interval and IntervalUnit values."); long maxNumOfCount = (remainingMillisInDay / intervalInMillis); if (count > maxNumOfCount) throw new IllegalArgumentException("The given count " + count + " is too large! The max you can set is " + maxNumOfCount); long incrementInMillis = (count - 1) * intervalInMillis; Date endTimeOfDayDate = new Date(startTimeOfDayDate.getTime() + incrementInMillis); if (endTimeOfDayDate.getTime() > maxEndTimeOfDayDate.getTime()) throw new IllegalArgumentException("The given count " + count + " is too large! The max you can set is " + maxNumOfCount); Calendar cal = Calendar.getInstance(); cal.setTime(endTimeOfDayDate); int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); int second = cal.get(Calendar.SECOND); endTimeOfDay = TimeOfDay.hourMinuteAndSecondOfDay(hour, minute, second); return this; } /** * If the Trigger misfires, use the * {@link Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY} instruction. * * @return the updated DailyTimeIntervalScheduleBuilder * @see Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY */ public DailyTimeIntervalScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() { misfireInstruction = Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY; return this; } /** * If the Trigger misfires, use the * {@link DailyTimeIntervalTrigger#MISFIRE_INSTRUCTION_DO_NOTHING} instruction. * * @return the updated DailyTimeIntervalScheduleBuilder * @see DailyTimeIntervalTrigger#MISFIRE_INSTRUCTION_DO_NOTHING */ public DailyTimeIntervalScheduleBuilder withMisfireHandlingInstructionDoNothing() { misfireInstruction = DailyTimeIntervalTrigger.MISFIRE_INSTRUCTION_DO_NOTHING; return this; } /** * If the Trigger misfires, use the * {@link DailyTimeIntervalTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW} instruction. * * @return the updated DailyTimeIntervalScheduleBuilder * @see DailyTimeIntervalTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW */ public DailyTimeIntervalScheduleBuilder withMisfireHandlingInstructionFireAndProceed() { misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; return this; } /** * Set number of times for interval to repeat. * *

Note: if you want total count = 1 (at start time) + repeatCount

* * @return the new DailyTimeIntervalScheduleBuilder */ public DailyTimeIntervalScheduleBuilder withRepeatCount(int repeatCount) { this.repeatCount = repeatCount; return this; } private void validateInterval(int timeInterval) { if(timeInterval <= 0) throw new IllegalArgumentException("Interval must be a positive value."); } } ================================================ FILE: quartz/src/main/java/org/quartz/DailyTimeIntervalTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import java.util.Set; import org.quartz.DateBuilder.IntervalUnit; /** * A {@link Trigger} that is used to fire a {@link org.quartz.JobDetail} * based upon daily repeating time intervals. * *

The trigger will fire every N (see {@link #getRepeatInterval()} ) seconds, minutes or hours * (see {@link #getRepeatIntervalUnit()}) during a given time window on specified days of the week.

* *

For example#1, a trigger can be set to fire every 72 minutes between 8:00 and 11:00 everyday. It's fire times would * be 8:00, 9:12, 10:24, then next day would repeat: 8:00, 9:12, 10:24 again.

* *

For example#2, a trigger can be set to fire every 23 minutes between 9:20 and 16:47 Monday through Friday.

* *

On each day, the starting fire time is reset to startTimeOfDay value, and then it will add repeatInterval value to it until * the endTimeOfDay is reached. If you set daysOfWeek values, then fire time will only occur during those week days period.

* *

The default values for fields if not set are: startTimeOfDay defaults to 00:00:00, the endTimeOfDay default to 23:59:59, * and daysOfWeek is default to every day. The startTime default to current time-stamp now, while endTime has not value.

* *

If startTime is before startTimeOfDay, then it has no affect. Else if startTime after startTimeOfDay, then the first fire time * for that day will be normal startTimeOfDay incremental values after startTime value. Same reversal logic is applied to endTime * with endTimeOfDay.

* * @see DailyTimeIntervalScheduleBuilder * * @since 2.1.0 * * @author James House * @author Zemian Deng <saltnlight5@gmail.com> */ public interface DailyTimeIntervalTrigger extends Trigger { /** *

* 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. *

*/ int REPEAT_INDEFINITELY = -1; /** *

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

*/ int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1; /** *

* Instructs the {@link Scheduler} that upon a mis-fire * situation, the {@link DailyTimeIntervalTrigger} 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. *

*/ int MISFIRE_INSTRUCTION_DO_NOTHING = 2; /** *

Get the interval unit - the time unit on with the interval applies.

* *

The only intervals that are valid for this type of trigger are {@link IntervalUnit#SECOND}, * {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}.

*/ IntervalUnit getRepeatIntervalUnit(); /** *

* Get the number of times for interval this trigger should * repeat, after which it will be automatically deleted. *

* * @see #REPEAT_INDEFINITELY */ int getRepeatCount(); /** *

* Get the time interval that will be added to the DateIntervalTrigger's * fire time (in the set repeat interval unit) in order to calculate the time of the * next trigger repeat. *

*/ int getRepeatInterval(); /** * The time of day to start firing at the given interval. */ TimeOfDay getStartTimeOfDay(); /** * The time of day to complete firing at the given interval. */ TimeOfDay getEndTimeOfDay(); /** * The days of the week upon which to fire. * * @return a Set containing the integers representing the days of the week, per the values 1-7 as defined by * {@link java.util.Calendar#SUNDAY} - {@link java.util.Calendar#SATURDAY}. */ Set getDaysOfWeek(); /** *

* Get the number of times the DateIntervalTrigger has already * fired. *

*/ int getTimesTriggered(); TriggerBuilder getTriggerBuilder(); } ================================================ FILE: quartz/src/main/java/org/quartz/DateBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.time.Clock; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Year; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; /** * DateBuilder is used to conveniently create * java.util.Date instances that meet particular criteria. * *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *
 *         Trigger trigger = newTrigger()
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(simpleSchedule()
 *                 .withIntervalInHours(1)
 *                 .repeatForever())
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *
 *         scheduler.scheduleJob(job, trigger);
 * 
* * @see TriggerBuilder * @see JobBuilder */ public class DateBuilder { public enum IntervalUnit { MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } 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 JANUARY = 1; public static final int FEBRUARY = 2; public static final int MARCH = 3; public static final int APRIL = 4; public static final int MAY = 5; public static final int JUNE = 6; public static final int JULY = 7; public static final int AUGUST = 8; public static final int SEPTEMBER = 9; public static final int OCTOBER = 10; public static final int NOVEMBER = 11; public static final int DECEMBER = 12; 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_MOST_DAYS = 24L * 60L * 60L; public static final long MILLISECONDS_IN_DAY = SECONDS_IN_MOST_DAYS * 1000L; private int month = -1; private int day = -1; private int year = -1; private int hour = -1; private int minute = -1; private int second = -1; private ZoneId zoneId; private Locale lc; private Clock clock = Clock.systemDefaultZone(); /** * Create a DateBuilder, with initial settings for the current date and time in the system default timezone. */ private DateBuilder() { this(TimeZone.getDefault()); } /** * Create a DateBuilder, with initial settings for the current date and time in the given timezone. */ private DateBuilder(TimeZone tz) { this.zoneId = tz.toZoneId(); } /** * Create a DateBuilder, with initial settings for the current date and time in the given locale. */ private DateBuilder(Locale lc) { this(Calendar.getInstance(lc).getTimeZone()); this.lc = lc; } /** * Create a DateBuilder, with initial settings for the current date and time in the given timezone and locale. */ private DateBuilder(TimeZone tz, Locale lc) { this(Calendar.getInstance(tz, lc).getTimeZone()); this.zoneId = tz.toZoneId(); this.lc = lc; } void setClock(Clock clock) { this.clock = clock; } /** * Create a DateBuilder, with initial settings for the current date and time in the system default timezone. */ public static DateBuilder newDate() { return new DateBuilder(); } /** * Create a DateBuilder, with initial settings for the current date and time in the given timezone. */ public static DateBuilder newDateInTimezone(TimeZone tz) { return new DateBuilder(tz); } /** * Create a DateBuilder, with initial settings for the current date and time in the given locale. */ public static DateBuilder newDateInLocale(Locale lc) { return new DateBuilder(lc); } /** * Create a DateBuilder, with initial settings for the current date and time in the given timezone and locale. */ public static DateBuilder newDateInTimeZoneAndLocale(TimeZone tz, Locale lc) { return new DateBuilder(tz, lc); } /** * Build the Date defined by this builder instance. */ public Date build() { var useZoneId = (zoneId != null) ? zoneId : ZoneId.systemDefault(); if (lc != null && useZoneId == null) { useZoneId = Calendar.getInstance(lc).getTimeZone().toZoneId(); } if (year == -1 || month == -1 || day == -1 || hour == -1 || minute == -1 || second == -1) { var zdt = ZonedDateTime.now(clock).withZoneSameInstant(useZoneId); year = zdt.getYear(); month = zdt.getMonthValue(); day = zdt.getDayOfMonth(); hour = zdt.getHour(); minute = zdt.getMinute(); second = zdt.getSecond(); } var zdt = ZonedDateTime.of(year, month, day, hour, minute, second, 0, useZoneId); return Date.from(zdt.toInstant()); } /** * Set the hour (0-23) for the Date that will be built by this builder. */ public DateBuilder atHourOfDay(int atHour) { validateHour(atHour); this.hour = atHour; return this; } /** * Set the minute (0-59) for the Date that will be built by this builder. */ public DateBuilder atMinute(int atMinute) { validateMinute(atMinute); this.minute = atMinute; return this; } /** * Set the second (0-59) for the Date that will be built by this builder, and truncate the milliseconds to 000. */ public DateBuilder atSecond(int atSecond) { validateSecond(atSecond); this.second = atSecond; return this; } public DateBuilder atHourMinuteAndSecond(int atHour, int atMinute, int atSecond) { validateHour(atHour); validateMinute(atMinute); validateSecond(atSecond); this.hour = atHour; this.second = atSecond; this.minute = atMinute; return this; } /** * Set the day of month (1-31) for the Date that will be built by this builder. */ public DateBuilder onDay(int onDay) { validateDayOfMonth(onDay); this.day = onDay; return this; } /** * Set the month (1-12) for the Date that will be built by this builder. */ public DateBuilder inMonth(int inMonth) { validateMonth(inMonth); this.month = inMonth; return this; } public DateBuilder inMonthOnDay(int inMonth, int onDay) { validateMonth(inMonth); validateDayOfMonth(onDay); this.month = inMonth; this.day = onDay; return this; } /** * Set the year for the Date that will be built by this builder. */ public DateBuilder inYear(int inYear) { validateYear(inYear); this.year = inYear; return this; } /** * Set the TimeZone for the Date that will be built by this builder (if "null", system default will be used) */ public DateBuilder inTimeZone(TimeZone timezone) { this.zoneId = timezone.toZoneId(); return this; } /** * Set the Locale for the Date that will be built by this builder (if "null", system default will be used) */ public DateBuilder inLocale(Locale locale) { this.lc = locale; return this; } public static Date futureDate(int interval, IntervalUnit unit) { return futureDate(interval, unit, Clock.systemDefaultZone()); } static Date futureDate(int interval, IntervalUnit unit, Clock clock) { return Date.from(ZonedDateTime.now(clock).plus(interval, translate(unit)).toInstant()); } private static ChronoUnit translate(IntervalUnit unit) { switch (unit) { case DAY : return ChronoUnit.DAYS; case HOUR : return ChronoUnit.HOURS; case MINUTE : return ChronoUnit.MINUTES; case MONTH : return ChronoUnit.MONTHS; case SECOND : return ChronoUnit.SECONDS; case MILLISECOND : return ChronoUnit.MILLIS; case WEEK : return ChronoUnit.WEEKS; case YEAR : return ChronoUnit.YEARS; default : throw new IllegalArgumentException("Unknown IntervalUnit"); } } /** *

* Get a Date object that represents the given time, on * tomorrow's date. *

* * @param hour * The value (0-23) to give the hours field of the date * @param minute * The value (0-59) to give the minutes field of the date * @param second * The value (0-59) to give the seconds field of the date * @return the new date */ public static Date tomorrowAt(int hour, int minute, int second) { return tomorrowAt(hour, minute, second, Clock.systemDefaultZone()); } static Date tomorrowAt(int hour, int minute, int second, Clock clock) { return Date.from( ZonedDateTime.now(clock) .truncatedTo(ChronoUnit.DAYS) .plusHours(24) .with(LocalTime.of(hour, minute, second, 0)) .toInstant()); } /** *

* Get a Date object that represents the given time, on * today's date (equivalent to {@link #dateOf(int, int, int)}). *

* * @param hour * The value (0-23) to give the hours field of the date * @param minute * The value (0-59) to give the minutes field of the date * @param second * The value (0-59) to give the seconds field of the date * @return the new date */ public static Date todayAt(int hour, int minute, int second) { return todayAt(hour, minute, second, Clock.systemDefaultZone()); } static Date todayAt(int hour, int minute, int second, Clock clock) { return dateOf(hour, minute, second, clock); } /** *

* Get a Date object that represents the given time, on * today's date (equivalent to {@link #todayAt(int, int, int)}). *

* * @param hour * The value (0-23) to give the hours field of the date * @param minute * The value (0-59) to give the minutes field of the date * @param second * The value (0-59) to give the seconds field of the date * @return the new date */ public static Date dateOf(int hour, int minute, int second) { return dateOf(hour, minute, second, Clock.systemDefaultZone()); } static Date dateOf(int hour, int minute, int second, Clock clock) { return Date.from( ZonedDateTime.now(clock) .with(LocalTime.of(hour, minute, second, 0)) .toInstant()); } /** *

* Get a Date object that represents the given time, on the * given date. *

* * @param hour * The value (0-23) to give the hours field of the date * @param minute * The value (0-59) to give the minutes field of the date * @param second * The value (0-59) to give the seconds 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 dateOf(int hour, int minute, int second, int dayOfMonth, int month) { return dateOf(hour, minute, second, dayOfMonth, month, Clock.systemDefaultZone()); } static Date dateOf(int hour, int minute, int second, int dayOfMonth, int month, Clock clock) { var zdt = ZonedDateTime.now(clock); return Date.from( zdt.with(LocalDateTime.of(zdt.getYear(), month, dayOfMonth, hour, minute, second, 0)) .toInstant()); } /** *

* Get a Date object that represents the given time, on the * given date. *

* * @param hour * The value (0-23) to give the hours field of the date * @param minute * The value (0-59) to give the minutes field of the date * @param second * The value (0-59) to give the seconds 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-999999999) to give the year field of the date * @return the new date */ public static Date dateOf(int hour, int minute, int second, int dayOfMonth, int month, int year) { return dateOf(hour, minute, second, dayOfMonth, month, year, Clock.systemDefaultZone()); } static Date dateOf(int hour, int minute, int second, int dayOfMonth, int month, int year, Clock clock) { return Date.from( LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, 0) .atZone(clock.getZone()) .toInstant()); } /** *

* Returns a date that is rounded to the next even hour after the current time. *

* *

* For example a current 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. *

* * @return the new rounded date */ public static Date evenHourDateAfterNow() { return evenHourDateAfterNow(Clock.systemDefaultZone()); } static Date evenHourDateAfterNow(Clock clock) { return evenHourDate(null, clock); } /** *

* 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 evenHourDate(Date date) { return evenHourDate(date, Clock.systemDefaultZone()); } static Date evenHourDate(Date date, Clock clock) { var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); zdt = zdt.plusHours(1); return Date.from( zdt.truncatedTo(ChronoUnit.HOURS) .toInstant()); } /** *

* 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 evenHourDateBefore(Date date) { return evenHourDateBefore(date, Clock.systemDefaultZone()); } static Date evenHourDateBefore(Date date, Clock clock) { var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); return Date.from( zdt.truncatedTo(ChronoUnit.HOURS) .toInstant()); } /** *

* Returns a date that is rounded to the next even minute after the current time. *

* *

* For example a current 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. *

* * @return the new rounded date */ public static Date evenMinuteDateAfterNow() { return evenMinuteDateAfterNow(Clock.systemDefaultZone()); } static Date evenMinuteDateAfterNow(Clock clock) { return evenMinuteDate(null, clock); } /** *

* 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 evenMinuteDate(Date date) { return evenMinuteDate(date, Clock.systemDefaultZone()); } public static Date evenMinuteDate(Date date, Clock clock) { var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); zdt = zdt.plusMinutes(1); return Date.from(zdt.truncatedTo(ChronoUnit.MINUTES).toInstant()); } /** *

* 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 evenMinuteDateBefore(Date date) { return evenMinuteDateBefore(date, Clock.systemDefaultZone()); } static Date evenMinuteDateBefore(Date date, Clock clock) { var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); return Date.from(zdt.truncatedTo(ChronoUnit.MINUTES).toInstant()); } /** *

* Returns a date that is rounded to the next even second after the current time. *

* * @return the new rounded date */ public static Date evenSecondDateAfterNow() { return evenSecondDateAfterNow(Clock.systemDefaultZone()); } static Date evenSecondDateAfterNow(Clock clock) { return evenSecondDate(null, clock); } /** *

* 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 evenSecondDate(Date date) { return evenSecondDate(date, Clock.systemDefaultZone()); } static Date evenSecondDate(Date date, Clock clock) { var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); zdt = zdt.plusSeconds(1); return Date.from(zdt.truncatedTo(ChronoUnit.SECONDS).toInstant()); } /** *

* 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:54.000. *

* * @param date * the Date to round, if null the current time will * be used * @return the new rounded date */ public static Date evenSecondDateBefore(Date date) { return evenSecondDateBefore(date, Clock.systemDefaultZone()); } static Date evenSecondDateBefore(Date date, Clock clock) { var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); return Date.from(zdt.truncatedTo(ChronoUnit.SECONDS).toInstant()); } /** *

* 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: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Examples of inputs and corresponding outputs.
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 #nextGivenSecondDate(Date, int) */ public static Date nextGivenMinuteDate(Date date, int minuteBase) { return nextGivenMinuteDate(date, minuteBase, Clock.systemDefaultZone()); } static Date nextGivenMinuteDate(Date date, int minuteBase, Clock clock) { if (minuteBase < 0 || minuteBase > 59) { throw new IllegalArgumentException( "minuteBase must be >=0 and <= 59"); } var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); if (minuteBase == 0) { zdt = zdt.truncatedTo(ChronoUnit.HOURS).plusHours(1); return Date.from(zdt.toInstant()); } zdt = zdt.truncatedTo(ChronoUnit.MINUTES); int minute = zdt.getMinute(); int nextminute = minute + minuteBase - (minute % minuteBase); if (nextminute >= 60) { zdt = zdt.truncatedTo(ChronoUnit.HOURS).plusHours(1); } else { zdt = zdt.withMinute(nextminute); } return Date.from(zdt.toInstant()); } /** *

* Returns a date that is rounded to the next even multiple of the given * second. *

* *

* 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 #nextGivenMinuteDate(Date, int) */ public static Date nextGivenSecondDate(Date date, int secondBase) { return nextGivenSecondDate(date, secondBase, Clock.systemDefaultZone()); } static Date nextGivenSecondDate(Date date, int secondBase, Clock clock) { if (secondBase < 0 || secondBase > 59) { throw new IllegalArgumentException( "secondBase must be >=0 and <= 59"); } var zdt = (date == null) ? ZonedDateTime.now(clock) : ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); if (secondBase == 0) { zdt = zdt.truncatedTo(ChronoUnit.MINUTES).plusMinutes(1); return Date.from(zdt.toInstant()); } zdt = zdt.truncatedTo(ChronoUnit.SECONDS); int second = zdt.getSecond(); int nextSecond = second + secondBase - (second % secondBase); if (nextSecond >= 60) { zdt = zdt.truncatedTo(ChronoUnit.MINUTES).plusMinutes(1); } else { zdt = zdt.withSecond(nextSecond); } return Date.from(zdt.toInstant()); } /** * Translate a date and time from a users time zone to the another * (probably server) time zone to assist in creating a simple trigger with * the right date and 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 = (dest.getOffset(date.getTime()) - src.getOffset(date.getTime())); newDate.setTime(date.getTime() - offset); return newDate; } //////////////////////////////////////////////////////////////////////////////////////////////////// public static void validateDayOfWeek(int dayOfWeek) { if (dayOfWeek < SUNDAY || dayOfWeek > SATURDAY) { throw new IllegalArgumentException("Invalid day of week."); } } public static void validateHour(int hour) { if (hour < 0 || hour > 23) { throw new IllegalArgumentException( "Invalid hour (must be >= 0 and <= 23)."); } } public static void validateMinute(int minute) { if (minute < 0 || minute > 59) { throw new IllegalArgumentException( "Invalid minute (must be >= 0 and <= 59)."); } } public static void validateSecond(int second) { if (second < 0 || second > 59) { throw new IllegalArgumentException( "Invalid second (must be >= 0 and <= 59)."); } } public static void validateDayOfMonth(int day) { if (day < 1 || day > 31) { throw new IllegalArgumentException("Invalid day of month."); } } public static void validateMonth(int month) { if (month < 1 || month > 12) { throw new IllegalArgumentException( "Invalid month (must be >= 1 and <= 12."); } } public static void validateYear(int year) { if (year < 1970 || year > Year.MAX_VALUE) { throw new IllegalArgumentException( "Invalid year (must be >= 0 and <= " + Year.MAX_VALUE); } } } ================================================ FILE: quartz/src/main/java/org/quartz/DisallowConcurrentExecution.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * An annotation that marks a {@link Job} class as one that must not have multiple * instances executed concurrently (where instance is based-upon a {@link JobDetail} * definition - or in other words based upon a {@link JobKey}). * * @see PersistJobDataAfterExecution * * @author jhouse */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DisallowConcurrentExecution { } ================================================ FILE: quartz/src/main/java/org/quartz/ExecuteInJTATransaction.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import jakarta.transaction.UserTransaction; /** * An annotation that marks a {@link Job} class as one that will have its * execution wrapped by a JTA Transaction. * *

If this annotation is present, Quartz will begin a JTA transaction * before calling the execute() method, and will commit * the transaction if the method does not throw an exception and the * transaction has not had setRollbackOnly() called on it * (otherwise the transaction will be rolled-back by Quartz).

* *

This is essentially the same behavior as setting the configuration * property org.quartz.scheduler.wrapJobExecutionInUserTransaction * to true - except that it only affects the job that has * the annotation, rather than all jobs (as the property does). If the * property is set to true and the annotation is also set, * then of course the annotation becomes redundant.

* * @author jhouse */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ExecuteInJTATransaction { /** * The JTA transaction timeout. *

* If set then the {@code UserTransaction} timeout will be set to this * value before beginning the transaction. * * @see UserTransaction#setTransactionTimeout(int) * @return the transaction timeout. */ int timeout() default -1; } ================================================ FILE: quartz/src/main/java/org/quartz/InterruptableJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * The interface to be implemented by {@link Job}s that provide a * mechanism for having their execution interrupted. It is NOT a requirement * for jobs to implement this interface - in fact, for most people, none of * their jobs will. * *

Interrupting a Job is very analogous in concept and * challenge to normal interruption of a Thread in Java. * *

* 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 * 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 * Implementation 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 (preferably in a * finally block. *

* *

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

* @see Job * @see StatefulJob * @see Scheduler#interrupt(JobKey) * @see Scheduler#interrupt(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; } ================================================ FILE: quartz/src/main/java/org/quartz/Job.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 JobBuilder * @see ExecuteInJTATransaction * @see DisallowConcurrentExecution * @see PersistJobDataAfterExecution * @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; } ================================================ FILE: quartz/src/main/java/org/quartz/JobBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.quartz.impl.JobDetailImpl; import org.quartz.utils.Key; /** * JobBuilder is used to instantiate {@link JobDetail}s. * *

The builder will always try to keep itself in a valid state, with * reasonable defaults set for calling build() at any point. For instance * if you do not invoke withIdentity(..) a job name will be generated * for you.

* *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(simpleSchedule()
 *                 .withIntervalInHours(1)
 *                 .repeatForever())
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * @see TriggerBuilder * @see DateBuilder * @see JobDetail */ public class JobBuilder { private JobKey key; private String description; private Class jobClass; private boolean durability; private boolean shouldRecover; private JobDataMap jobDataMap = new JobDataMap(); protected JobBuilder() { } /** * Create a JobBuilder with which to define a JobDetail. * * @return a new JobBuilder */ public static JobBuilder newJob() { return new JobBuilder(); } /** * Create a JobBuilder with which to define a JobDetail, * and set the class name of the Job to be executed. * * @return a new JobBuilder */ public static JobBuilder newJob(Class jobClass) { JobBuilder b = new JobBuilder(); b.ofType(jobClass); return b; } /** * Produce the JobDetail instance defined by this * JobBuilder. * * @return the defined JobDetail. */ public JobDetail build() { JobDetailImpl job = new JobDetailImpl(); job.setJobClass(jobClass); job.setDescription(description); if(key == null) key = new JobKey(Key.createUniqueName(null), null); job.setKey(key); job.setDurability(durability); job.setRequestsRecovery(shouldRecover); if(!jobDataMap.isEmpty()) job.setJobDataMap(jobDataMap); return job; } /** * Use a JobKey with the given name and default group to * identify the JobDetail. * *

If none of the 'withIdentity' methods are set on the JobBuilder, * then a random, unique JobKey will be generated.

* * @param name the name element for the Job's JobKey * @return the updated JobBuilder * @see JobKey * @see JobDetail#getKey() */ public JobBuilder withIdentity(String name) { key = new JobKey(name, null); return this; } /** * Use a JobKey with the given name and group to * identify the JobDetail. * *

If none of the 'withIdentity' methods are set on the JobBuilder, * then a random, unique JobKey will be generated.

* * @param name the name element for the Job's JobKey * @param group the group element for the Job's JobKey * @return the updated JobBuilder * @see JobKey * @see JobDetail#getKey() */ public JobBuilder withIdentity(String name, String group) { key = new JobKey(name, group); return this; } /** * Use a JobKey to identify the JobDetail. * *

If none of the 'withIdentity' methods are set on the JobBuilder, * then a random, unique JobKey will be generated.

* * @param jobKey the Job's JobKey * @return the updated JobBuilder * @see JobKey * @see JobDetail#getKey() */ public JobBuilder withIdentity(JobKey jobKey) { this.key = jobKey; return this; } /** * Set the given (human-meaningful) description of the Job. * * @param jobDescription the description for the Job * @return the updated JobBuilder * @see JobDetail#getDescription() */ public JobBuilder withDescription(String jobDescription) { this.description = jobDescription; return this; } /** * Set the class which will be instantiated and executed when a * Trigger fires that is associated with this JobDetail. * * @param jobClazz a class implementing the Job interface. * @return the updated JobBuilder * @see JobDetail#getJobClass() */ public JobBuilder ofType(Class jobClazz) { this.jobClass = jobClazz; return this; } /** * 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. * - this method sets the value to true. *

* * @return the updated JobBuilder * @see JobDetail#requestsRecovery() */ public JobBuilder requestRecovery() { this.shouldRecover = true; return this; } /** * 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. *

* * @param jobShouldRecover the desired setting * @return the updated JobBuilder */ public JobBuilder requestRecovery(boolean jobShouldRecover) { this.shouldRecover = jobShouldRecover; return this; } /** * 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 * - this method sets the value to true. *

* * @return the updated JobBuilder * @see JobDetail#isDurable() */ public JobBuilder storeDurably() { return storeDurably(true); } /** * 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. *

* * @param jobDurability the value to set for the durability property. * @return the updated JobBuilder * @see JobDetail#isDurable() */ public JobBuilder storeDurably(boolean jobDurability) { this.durability = jobDurability; return this; } /** * Add the given key-value pair to the JobDetail's {@link JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(String dataKey, String value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the JobDetail's {@link JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(String dataKey, Integer value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the JobDetail's {@link JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(String dataKey, Long value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the JobDetail's {@link JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(String dataKey, Float value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the JobDetail's {@link JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(String dataKey, Double value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the JobDetail's {@link JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(String dataKey, Boolean value) { jobDataMap.put(dataKey, value); return this; } /** * Add all the data from the given {@link JobDataMap} to the * {@code JobDetail}'s {@code JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder usingJobData(JobDataMap newJobDataMap) { jobDataMap.putAll(newJobDataMap); return this; } /** * Replace the {@code JobDetail}'s {@link JobDataMap} with the * given {@code JobDataMap}. * * @return the updated JobBuilder * @see JobDetail#getJobDataMap() */ public JobBuilder setJobData(JobDataMap newJobDataMap) { jobDataMap = newJobDataMap; return this; } } ================================================ FILE: quartz/src/main/java/org/quartz/JobDataMap.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.Serializable; import java.util.Map; import 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 * jobs annotated with @PersistJobDataAfterExecution. *

* *

* 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). *

* *

* Update since 2.2.4 - We keep an dirty flag for this map so that whenever you modify(add/delete) any of the entries, * it will set to "true". However if you create new instance using an existing map with {@link #JobDataMap(Map)}, then * the dirty flag will NOT be set to "true" until you modify the instance. *

* * @see Job * @see PersistJobDataAfterExecution * @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(); @SuppressWarnings("unchecked") // casting to keep API compatible and avoid compiler errors/warnings. Map mapTyped = (Map)map; putAll(mapTyped); // When constructing a new data map from another existing map, we should NOT mark dirty flag as true // Use case: loading JobDataMap from DB clearDirtyFlag(); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 = Boolean.valueOf(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 = Character.valueOf(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 = Double.toString(value); 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 = Float.toString(value); 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 = Integer.valueOf(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 = Long.valueOf(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 Integer.parseInt((String) obj); } /** *

* 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 Integer.valueOf((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 Boolean.valueOf((String) obj); } /** *

* 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 Boolean.valueOf((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 ((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 Double.valueOf((String) obj); } /** *

* 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 Double.valueOf((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 Float.parseFloat((String) obj); } /** *

* 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 Float.valueOf((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 Long.parseLong((String) obj); } /** *

* 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 Long.valueOf((String) obj); } } ================================================ FILE: quartz/src/main/java/org/quartz/JobDetail.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.Serializable; /** * Conveys the detail properties of a given Job instance. JobDetails are * to be created/defined with {@link JobBuilder}. * *

* 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 JobBuilder * @see Job * @see JobDataMap * @see Trigger * * @author James House */ public interface JobDetail extends Serializable, Cloneable { JobKey getKey(); /** *

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

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

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

*/ Class getJobClass(); /** *

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

*/ JobDataMap getJobDataMap(); /** *

* 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. */ boolean isDurable(); /** * @see PersistJobDataAfterExecution * @return whether the associated Job class carries the {@link PersistJobDataAfterExecution} annotation. */ boolean isPersistJobDataAfterExecution(); /** * @see DisallowConcurrentExecution * @return whether the associated Job class carries the {@link DisallowConcurrentExecution} annotation. */ boolean isConcurrentExecutionDisallowed(); /** *

* 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() */ boolean requestsRecovery(); Object clone(); /** * Get a {@link JobBuilder} that is configured to produce a * JobDetail identical to this one. */ JobBuilder getJobBuilder(); } ================================================ FILE: quartz/src/main/java/org/quartz/JobExecutionContext.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Date; /** * A context bundle containing handles to various environment information, that * is given to a {@link 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 * job's own JobDataMap - even if it has the * @PersistJobDataAfterExecution annotation. *

* *

* 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 #getScheduler() * @see #getMergedJobDataMap() * @see #getJobDetail() * * @see Job * @see Trigger * @see JobDataMap * * @author James House */ public interface JobExecutionContext { /** *

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

*/ Scheduler getScheduler(); /** *

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

*/ Trigger getTrigger(); /** *

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

*/ Calendar getCalendar(); /** *

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

*/ boolean isRecovering(); /** * Return the {@code TriggerKey} of the originally scheduled and now recovering job. *

* When recovering a previously failed job execution this method returns the identity * of the originally firing trigger. This recovering job will have been scheduled for * the same firing time as the original job, and so is available via the * {@link #getScheduledFireTime()} method. The original firing time of the job can be * accessed via the {@link Scheduler#FAILED_JOB_ORIGINAL_TRIGGER_FIRETIME_IN_MILLISECONDS} * element of this job's {@code JobDataMap}. * * @return the recovering trigger details * @throws IllegalStateException if this is not a recovering job. */ TriggerKey getRecoveringTriggerKey() throws IllegalStateException; int getRefireCount(); /** *

* 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 * or persisted back onto a job's own JobDataMap - even if it has the * @PersistJobDataAfterExecution annotation. *

* *

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

* */ JobDataMap getMergedJobDataMap(); /** *

* Get the JobDetail associated with the Job. *

*/ JobDetail getJobDetail(); /** *

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

* *

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

*/ Job getJobInstance(); /** * 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() */ Date getFireTime(); /** * 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() */ Date getScheduledFireTime(); Date getPreviousFireTime(); Date getNextFireTime(); /** * Get the unique Id that identifies this particular firing instance of the * trigger that triggered this job execution. It is unique to this * JobExecutionContext instance as well. * * @return the unique fire instance id * @see Scheduler#interrupt(String) */ String getFireInstanceId(); /** * 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. */ Object getResult(); /** * 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. *

*/ void setResult(Object 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. */ long getJobRunTime(); /** * 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 the key for the associated value * @param value the value to store */ void put(Object key, Object value); /** * Get the value with the given key from the context's data map. * * @param key the key for the desired value */ Object get(Object key); } ================================================ FILE: quartz/src/main/java/org/quartz/JobExecutionException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * An exception that can be thrown by a {@link org.quartz.Job} * to indicate to the Quartz {@link Scheduler} that an error * occurred 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 { private static final long serialVersionUID = 1326342535829043325L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private boolean refire = false; private boolean unscheduleTrigg = false; private boolean unscheduleAllTriggs = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

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

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

* Create a JobExecutionException, with the given cause. *

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

* Create a JobExecutionException, with the given message. *

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

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

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

* Create a JobExecutionException 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 JobExecutionException with the given message, and underlying * exception. *

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

* Create a JobExecutionException 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 JobExecutionException 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; } } ================================================ FILE: quartz/src/main/java/org/quartz/JobKey.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.quartz.utils.Key; /** * Uniquely identifies a {@link JobDetail}. * *

Keys are composed of both a name and group, and the name must be unique * within the group. If only a name is specified then the default group * name will be used.

* *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(simpleSchedule()
 *                 .withIntervalInHours(1)
 *                 .repeatForever())
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * * @see Job * @see Key#DEFAULT_GROUP */ public final class JobKey extends Key { private static final long serialVersionUID = -6073883950062574010L; public JobKey(String name) { super(name, null); } public JobKey(String name, String group) { super(name, group); } public static JobKey jobKey(String name) { return new JobKey(name, null); } public static JobKey jobKey(String name, String group) { return new JobKey(name, group); } } ================================================ FILE: quartz/src/main/java/org/quartz/JobListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * The interface to be implemented by classes that want to be informed when a * {@link org.quartz.JobDetail} executes. In general, * applications that use a Scheduler will not have use for this * mechanism. * * @see ListenerManager#addJobListener(JobListener, Matcher) * @see Matcher * @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 org.quartz.JobDetail} * is about to be executed (an associated {@link Trigger} * has occurred). *

* *

* 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 org.quartz.JobDetail} * was about to be executed (an associated {@link Trigger} * has occurred), but a {@link TriggerListener} vetoed it's * execution. *

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

* Called by the {@link Scheduler} after a {@link 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); } ================================================ FILE: quartz/src/main/java/org/quartz/JobPersistenceException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 { private static final long serialVersionUID = -8924958757341995694L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a JobPersistenceException with the given message. *

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

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

*/ public JobPersistenceException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: quartz/src/main/java/org/quartz/ListenerManager.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.List; /** * Client programs may 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. Listeners can be associated with local schedulers through the * {@link ListenerManager} interface. * *

Listener registration order is preserved, and hence notification of listeners * will be in the order in which they were registered.

* * @author jhouse * @since 2.0 - previously listeners were managed directly on the Scheduler interface. */ public interface ListenerManager { /** * Add the given {@link JobListener} to the Scheduler, * and register it to receive events for all Jobs. * * Because no matchers are provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addJobListener(JobListener jobListener); /** * Add the given {@link JobListener} to the Scheduler, * and register it to receive events for Jobs that are matched by the * given Matcher. * * If no matchers are provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addJobListener(JobListener jobListener, Matcher matcher); /** * Add the given {@link JobListener} to the Scheduler, * and register it to receive events for Jobs that are matched by ANY of the * given Matchers. * * If no matchers are provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addJobListener(JobListener jobListener, Matcher... matchers); /** * Add the given {@link JobListener} to the Scheduler, * and register it to receive events for Jobs that are matched by ANY of the * given Matchers. * * If no matchers are provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addJobListener(JobListener jobListener, List> matchers); /** * Add the given Matcher to the set of matchers for which the listener * will receive events if ANY of the matchers match. * * @param listenerName the name of the listener to add the matcher to * @param matcher the additional matcher to apply for selecting events * @return true if the identified listener was found and updated */ boolean addJobListenerMatcher(String listenerName, Matcher matcher); /** * Remove the given Matcher to the set of matchers for which the listener * will receive events if ANY of the matchers match. * * @param listenerName the name of the listener to add the matcher to * @param matcher the additional matcher to apply for selecting events * @return true if the given matcher was found and removed from the listener's list of matchers */ boolean removeJobListenerMatcher(String listenerName, Matcher matcher); /** * Set the set of Matchers for which the listener * will receive events if ANY of the matchers match. * *

Removes any existing matchers for the identified listener!

* * @param listenerName the name of the listener to add the matcher to * @param matchers the matchers to apply for selecting events * @return true if the given matcher was found and removed from the listener's list of matchers */ boolean setJobListenerMatchers(String listenerName, List> matchers); /** * Get the set of Matchers for which the listener * will receive events if ANY of the matchers match. * * * @param listenerName the name of the listener to add the matcher to * @return the matchers registered for selecting events for the identified listener */ List> getJobListenerMatchers(String listenerName); /** * Remove the identified {@link JobListener} from the Scheduler. * * @return true if the identified listener was found in the list, and * removed. */ boolean removeJobListener(String name); /** * Get a List containing all of the {@link JobListener}s in * the Scheduler, in the order in which they were registered. */ List getJobListeners(); /** * Get the {@link JobListener} that has the given name. */ JobListener getJobListener(String name); /** * Add the given {@link TriggerListener} to the Scheduler, * and register it to receive events for all Triggers. * * Because no matcher is provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addTriggerListener(TriggerListener triggerListener); /** * Add the given {@link TriggerListener} to the Scheduler, * and register it to receive events for Triggers that are matched by the * given Matcher. * * If no matcher is provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addTriggerListener(TriggerListener triggerListener, Matcher matcher); /** * Add the given {@link TriggerListener} to the Scheduler, * and register it to receive events for Triggers that are matched by ANY of the * given Matchers. * * If no matcher is provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addTriggerListener(TriggerListener triggerListener, Matcher... matchers); /** * Add the given {@link TriggerListener} to the Scheduler, * and register it to receive events for Triggers that are matched by ANY of the * given Matchers. * * If no matcher is provided, the EverythingMatcher will be used. * * @see Matcher * @see org.quartz.impl.matchers.EverythingMatcher */ void addTriggerListener(TriggerListener triggerListener, List> matchers); /** * Add the given Matcher to the set of matchers for which the listener * will receive events if ANY of the matchers match. * * @param listenerName the name of the listener to add the matcher to * @param matcher the additional matcher to apply for selecting events * @return true if the identified listener was found and updated */ boolean addTriggerListenerMatcher(String listenerName, Matcher matcher); /** * Remove the given Matcher to the set of matchers for which the listener * will receive events if ANY of the matchers match. * * @param listenerName the name of the listener to add the matcher to * @param matcher the additional matcher to apply for selecting events * @return true if the given matcher was found and removed from the listener's list of matchers */ boolean removeTriggerListenerMatcher(String listenerName, Matcher matcher); /** * Set the set of Matchers for which the listener * will receive events if ANY of the matchers match. * *

Removes any existing matchers for the identified listener!

* * @param listenerName the name of the listener to add the matcher to * @param matchers the matchers to apply for selecting events * @return true if the given matcher was found and removed from the listener's list of matchers */ boolean setTriggerListenerMatchers(String listenerName, List> matchers); /** * Get the set of Matchers for which the listener * will receive events if ANY of the matchers match. * * * @param listenerName the name of the listener to add the matcher to * @return the matchers registered for selecting events for the identified listener */ List> getTriggerListenerMatchers(String listenerName); /** * Remove the identified {@link TriggerListener} from the Scheduler. * * @return true if the identified listener was found in the list, and * removed. */ boolean removeTriggerListener(String name); /** * Get a List containing all of the {@link TriggerListener}s * in the Scheduler, in the order in which they were registered. */ List getTriggerListeners(); /** * Get the {@link TriggerListener} that has the given name. */ TriggerListener getTriggerListener(String name); /** * Register the given {@link SchedulerListener} with the * Scheduler. */ void addSchedulerListener(SchedulerListener schedulerListener); /** * Remove the given {@link SchedulerListener} from the * Scheduler. * * @return true if the identified listener was found in the list, and * removed. */ boolean removeSchedulerListener(SchedulerListener schedulerListener); /** * Get a List containing all of the {@link SchedulerListener}s * registered with the Scheduler, in the order in which they were registered. */ List getSchedulerListeners(); } ================================================ FILE: quartz/src/main/java/org/quartz/Matcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.Serializable; import org.quartz.utils.Key; /** * Matchers can be used in various {@link Scheduler} API methods to * select the entities that should be operated upon. * * @author jhouse * @since 2.0 */ public interface Matcher> extends Serializable { boolean isMatch(T key); int hashCode(); boolean equals(Object obj); } ================================================ FILE: quartz/src/main/java/org/quartz/ObjectAlreadyExistsException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * An exception that is thrown to indicate that an attempt to store a new * object (i.e. {@link org.quartz.JobDetail},{@link Trigger} * or {@link Calendar}) in a {@link Scheduler} * failed, because one with the same name and group already exists. * * @author James House */ public class ObjectAlreadyExistsException extends JobPersistenceException { private static final long serialVersionUID = -558301282071659896L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 : '" + offendingJob.getKey() + "', 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.getKey().getName() + "' and group: '" + offendingTrigger.getKey().getGroup() + "', because one already exists with this identification."); } } ================================================ FILE: quartz/src/main/java/org/quartz/PersistJobDataAfterExecution.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * An annotation that marks a {@link Job} class as one that makes updates to its * {@link JobDataMap} during execution, and wishes the scheduler to re-store the * JobDataMap when execution completes. * *

Jobs that are marked with this annotation should also seriously consider * using the {@link DisallowConcurrentExecution} annotation, to avoid data * storage race conditions with concurrently executing job instances.

* * @see DisallowConcurrentExecution * * @author jhouse */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PersistJobDataAfterExecution { } ================================================ FILE: quartz/src/main/java/org/quartz/ScheduleBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.quartz.spi.MutableTrigger; public abstract class ScheduleBuilder { protected abstract MutableTrigger build(); } ================================================ FILE: quartz/src/main/java/org/quartz/Scheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.quartz.Trigger.TriggerState; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.spi.JobFactory; import org.quartz.utils.Key; /** * This is the main interface of a Quartz Scheduler. * *

* A Scheduler maintains a registry of {@link org.quartz.JobDetail}s * and {@link Trigger}s. Once registered, the Scheduler * is responsible 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 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. Listeners can be associated with * local schedulers through the {@link ListenerManager} interface. *

* *

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

* * @see Job * @see JobDetail * @see JobBuilder * @see Trigger * @see TriggerBuilder * @see JobListener * @see TriggerListener * @see SchedulerListener * * @author James House * @author Sharada Jambula */ public interface Scheduler { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * A (possibly) useful constant that can be used for specifying the group * that Job and Trigger instances belong to. */ String DEFAULT_GROUP = Key.DEFAULT_GROUP; /** * 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 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 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 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 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 * 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. * *

Note that this is the time the original firing actually occurred, * which may be different from the scheduled fire time - as a trigger doesn't * always fire exactly on time.

* * @see org.quartz.JobDetail#requestsRecovery() */ String FAILED_JOB_ORIGINAL_TRIGGER_FIRETIME_IN_MILLISECONDS = "QRTZ_FAILED_JOB_ORIG_TRIGGER_FIRETIME_IN_MILLISECONDS_AS_STRING"; /** * 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. * *

Note that this is the time the original firing was scheduled for, * which may be different from the actual firing time - as a trigger doesn't * always fire exactly on time.

* * @see org.quartz.JobDetail#requestsRecovery() */ String FAILED_JOB_ORIGINAL_TRIGGER_SCHEDULED_FIRETIME_IN_MILLISECONDS = "QRTZ_FAILED_JOB_ORIG_TRIGGER_SCHEDULED_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; /////////////////////////////////////////////////////////////////////////// /// /// Scheduler 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; /** * Reports whether the Scheduler is in stand-by mode. * * @see #standby() * @see #start() */ boolean isInStandbyMode() 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 describing 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 opportunity for dependency injection. *

* * @see org.quartz.spi.JobFactory */ void setJobFactory(JobFactory factory) throws SchedulerException; /** * Get a reference to the scheduler's ListenerManager, * through which listeners may be registered. * * @return the scheduler's ListenerManager * @throws SchedulerException if the scheduler is not local * @see ListenerManager * @see JobListener * @see TriggerListener * @see SchedulerListener */ ListenerManager getListenerManager() throws SchedulerException; /////////////////////////////////////////////////////////////////////////// /// /// Scheduling-related Methods /// /////////////////////////////////////////////////////////////////////////// /** * Add the given {@link 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 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; /** * Schedule all of the given jobs with the related set of triggers. * *

If any of the given jobs or triggers already exist (or more * specifically, if the keys are not unique) and the replace * parameter is not set to true then an exception will be thrown.

* * @throws ObjectAlreadyExistsException if the job/trigger keys * are not unique and the replace flag is not set to true. */ void scheduleJobs(Map> triggersAndJobs, boolean replace) throws SchedulerException; /** * Schedule the given job with the related set of triggers. * *

If any of the given job or triggers already exist (or more * specifically, if the keys are not unique) and the replace * parameter is not set to true then an exception will be thrown.

* * @throws ObjectAlreadyExistsException if the job/trigger keys * are not unique and the replace flag is not set to true. */ void scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace) throws SchedulerException; /** * Remove the indicated {@link Trigger} from the scheduler. * *

If the related job does not have any other triggers, and the job is * not durable, then the job will also be deleted.

*/ boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException; /** * Remove all of the indicated {@link Trigger}s from the scheduler. * *

If the related job does not have any other triggers, and the job is * not durable, then the job will also be deleted.

* *

Note that while this bulk operation is likely more efficient than * invoking unscheduleJob(TriggerKey triggerKey) several * times, it may have the adverse affect of holding data locks for a * single long duration of time (rather than lots of small durations * of time).

*/ boolean unscheduleJobs(List triggerKeys) throws SchedulerException; /** * Remove (delete) the {@link org.quartz.Trigger} with the * given key, and store the new given one - which must be associated * with the same job (the new trigger must have the job name and group specified) * - however, the new trigger need not have the same name as the old trigger. * * @param triggerKey identity of the trigger to replace * @param newTrigger * The new Trigger to be stored. * * @return null if a Trigger with the given * name and group was not found and removed from the store (and the * new trigger is therefore not stored), otherwise * the first fire time of the newly scheduled trigger is returned. */ Date rescheduleJob(TriggerKey triggerKey, 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. *

* * @see #addJob(JobDetail, boolean, boolean) * * @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; /** * 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. * *

* With the storeNonDurableWhileAwaitingScheduling parameter * set to true, a non-durable job can be stored. Once it is * scheduled, it will resume normal non-durable behavior (i.e. be deleted * once there are no remaining associated triggers). *

* * @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, boolean storeNonDurableWhileAwaitingScheduling) 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(JobKey jobKey) throws SchedulerException; /** * Delete the identified Jobs from the Scheduler - and any * associated Triggers. * *

Note that while this bulk operation is likely more efficient than * invoking deleteJob(JobKey jobKey) several * times, it may have the adverse affect of holding data locks for a * single long duration of time (rather than lots of small durations * of time).

* * @return true if all of the Jobs were found and deleted, false if * one or more were not deleted. * @throws SchedulerException * if there is an internal Scheduler error. */ boolean deleteJobs(List jobKeys) throws SchedulerException; /** * Trigger the identified {@link org.quartz.JobDetail} * (execute it now). */ void triggerJob(JobKey jobKey) throws SchedulerException; /** * Trigger the identified {@link org.quartz.JobDetail} * (execute it now). * * @param data the (possibly null) JobDataMap to be * associated with the trigger that fires the job immediately. */ void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException; /** * Pause the {@link org.quartz.JobDetail} with the given * key - by pausing all of its current Triggers. * * @see #resumeJob(JobKey) */ void pauseJob(JobKey jobKey) throws SchedulerException; /** * Pause all of the {@link org.quartz.JobDetail}s in the * matching groups - by pausing all of their Triggers. * *

* The Scheduler will "remember" the groups paused, and impose the * pause on any new jobs that are added to any of those groups * until it is resumed. *

* *

NOTE: There is a limitation that only exactly matched groups * can be remembered as paused. For example, if there are preexisting * job in groups "aaa" and "bbb" and a matcher is given to pause * groups that start with "a" then the group "aaa" will be remembered * as paused and any subsequently added jobs in group "aaa" will be paused, * however if a job is added to group "axx" it will not be paused, * as "axx" wasn't known at the time the "group starts with a" matcher * was applied. HOWEVER, if there are preexisting groups "aaa" and * "bbb" and a matcher is given to pause the group "axx" (with a * group equals matcher) then no jobs will be paused, but it will be * remembered that group "axx" is paused and later when a job is added * in that group, it will become paused.

* * @param matcher The matcher to evaluate against know groups * @throws SchedulerException On error * @see #resumeJobs(org.quartz.impl.matchers.GroupMatcher) */ void pauseJobs(GroupMatcher matcher) throws SchedulerException; /** * Pause the {@link Trigger} with the given key. * * @see #resumeTrigger(TriggerKey) */ void pauseTrigger(TriggerKey triggerKey) throws SchedulerException; /** * Pause all of the {@link Trigger}s in the groups matching. * *

* The Scheduler will "remember" all the groups paused, and impose the * pause on any new triggers that are added to any of those groups * until it is resumed. *

* *

NOTE: There is a limitation that only exactly matched groups * can be remembered as paused. For example, if there are preexisting * triggers in groups "aaa" and "bbb" and a matcher is given to pause * groups that start with "a" then the group "aaa" will be remembered as * paused and any subsequently added triggers in that group be paused, * however if a trigger is added to group "axx" it will not be paused, * as "axx" wasn't known at the time the "group starts with a" matcher * was applied. HOWEVER, if there are preexisting groups "aaa" and * "bbb" and a matcher is given to pause the group "axx" (with a * group equals matcher) then no triggers will be paused, but it will be * remembered that group "axx" is paused and later when a trigger is added * in that group, it will become paused.

* * @param matcher The matcher to evaluate against know groups * @throws SchedulerException * @see #resumeTriggers(org.quartz.impl.matchers.GroupMatcher) */ void pauseTriggers(GroupMatcher matcher) throws SchedulerException; /** * Resume (un-pause) the {@link org.quartz.JobDetail} with * the given key. * *

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

* * @see #pauseJob(JobKey) */ void resumeJob(JobKey jobKey) throws SchedulerException; /** * Resume (un-pause) all of the {@link org.quartz.JobDetail}s * in matching groups. * *

* 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. *

* * @param matcher The matcher to evaluate against known paused groups * @throws SchedulerException On error * @see #pauseJobs(GroupMatcher) */ void resumeJobs(GroupMatcher matcher) throws SchedulerException; /** * Resume (un-pause) the {@link Trigger} with the given * key. * *

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

* * @see #pauseTrigger(TriggerKey) */ void resumeTrigger(TriggerKey triggerKey) throws SchedulerException; /** * Resume (un-pause) all of the {@link Trigger}s in matching groups. * *

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

* * @param matcher The matcher to evaluate against know paused groups * @throws SchedulerException On error * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) */ void resumeTriggers(GroupMatcher matcher) 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 #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) * @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 org.quartz.JobDetail} * groups. */ List getJobGroupNames() throws SchedulerException; /** * Get the keys of all the {@link org.quartz.JobDetail}s * in the matching groups. * @param matcher Matcher to evaluate against known groups * @return Set of all keys matching * @throws SchedulerException On error */ Set getJobKeys(GroupMatcher matcher) throws SchedulerException; /** * Get all {@link Trigger} s that are associated with the * identified {@link org.quartz.JobDetail}. * *

The returned Trigger objects will be snap-shots of the actual stored * triggers. If you wish to modify a trigger, you must re-store the * trigger afterward (e.g. see {@link #rescheduleJob(TriggerKey, Trigger)}). *

* */ List getTriggersOfJob(JobKey jobKey) throws SchedulerException; /** * Get the names of all known {@link Trigger} groups. */ List getTriggerGroupNames() throws SchedulerException; /** * Get the names of all the {@link Trigger}s in the given * group. * @param matcher Matcher to evaluate against known groups * @return List of all keys matching * @throws SchedulerException On error */ Set getTriggerKeys(GroupMatcher matcher) 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 key. * *

The returned JobDetail object will be a snap-shot of the actual stored * JobDetail. If you wish to modify the JobDetail, you must re-store the * JobDetail afterward (e.g. see {@link #addJob(JobDetail, boolean)}). *

* */ JobDetail getJobDetail(JobKey jobKey) throws SchedulerException; /** * Gets all the {@link org.quartz.JobDetail Jobdetails} * in the matching groups. * *

The returned JobDetail objects will be a snap-shot of the actual stored * JobDetail. If you wish to modify the JobDetail, you must re-store the * JobDetail afterward (e.g. see {@link #addJob(JobDetail, boolean)}). *

* @param matcher Matcher to evaluate against known groups * @return List of all JobDetail matching * @throws SchedulerException On error */ List getJobDetails(GroupMatcher matcher) throws SchedulerException; /** * Get the {@link Trigger} instance with the given key. * *

The returned Trigger object will be a snap-shot of the actual stored * trigger. If you wish to modify the trigger, you must re-store the * trigger afterward (e.g. see {@link #rescheduleJob(TriggerKey, Trigger)}). *

*/ Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException; /** * Get the current state of the identified {@link Trigger}. * * @see Trigger.TriggerState */ TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException; /** * Reset the current state of the identified {@link Trigger} * from {@link TriggerState#ERROR} to {@link TriggerState#NORMAL} or * {@link TriggerState#PAUSED} as appropriate. * *

Only affects triggers that are in ERROR state - if identified trigger is not * in that state then the result is a no-op.

* *

The result will be the trigger returning to the normal, waiting to * be fired state, unless the trigger's group has been paused, in which * case it will go into the PAUSED state.

* * @see Trigger.TriggerState */ void resetTriggerFromErrorState(TriggerKey triggerKey) 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. * *

* If removal of the Calendar would result in * Triggers pointing to nonexistent calendars, then a * SchedulerException will be thrown. *

* * @return true if the Calendar was found and deleted. * @throws SchedulerException * if there is an internal Scheduler error, or one or more * triggers reference the calendar */ 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. */ List 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. *

* *

* 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. *

* * @return true if 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() * @see #interrupt(String) */ boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException; /** * Request the interruption, within this Scheduler instance, of the * identified executing Job instance, which * must be an implementor of the InterruptableJob interface. * *

* 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 fireInstanceId the unique identifier of the job instance to * be interrupted (see {@link JobExecutionContext#getFireInstanceId()} * @return true if the identified job instance 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() * @see JobExecutionContext#getFireInstanceId() * @see #interrupt(JobKey) */ boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException; /** * Determine whether a {@link Job} with the given identifier already * exists within the scheduler. * * @param jobKey the identifier to check for * @return true if a Job exists with the given identifier * @throws SchedulerException */ boolean checkExists(JobKey jobKey) throws SchedulerException; /** * Determine whether a {@link Trigger} with the given identifier already * exists within the scheduler. * * @param triggerKey the identifier to check for * @return true if a Trigger exists with the given identifier * @throws SchedulerException */ boolean checkExists(TriggerKey triggerKey) throws SchedulerException; /** * Clears (deletes!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws SchedulerException */ void clear() throws SchedulerException; } ================================================ FILE: quartz/src/main/java/org/quartz/SchedulerConfigException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 { private static final long serialVersionUID = -5921239824646083098L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a JobPersistenceException with the given message. *

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

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

*/ public SchedulerConfigException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: quartz/src/main/java/org/quartz/SchedulerContext.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.Serializable; import java.util.Map; import 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 propagates * 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 { private static final long serialVersionUID = -6659641334616491764L; /** * Create an empty SchedulerContext. */ public SchedulerContext() { super(15); } /** * Create a SchedulerContext with the given data. */ public SchedulerContext(Map map) { this(); @SuppressWarnings("unchecked") // param must be a String key map. Map mapTyped = (Map)map; putAll(mapTyped); } } ================================================ FILE: quartz/src/main/java/org/quartz/SchedulerException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * Base class for exceptions thrown by the Quartz {@link Scheduler}. * *

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

* * @author James House */ public class SchedulerException extends Exception { private static final long serialVersionUID = 174841398690789156L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public SchedulerException() { super(); } public SchedulerException(String msg) { super(msg); } public SchedulerException(Throwable cause) { super(cause); } public SchedulerException(String msg, Throwable cause) { super(msg, 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 super.getCause(); } @Override public String toString() { Throwable cause = getUnderlyingException(); if (cause == null || cause == this) { return super.toString(); } else { return super.toString() + " [See nested exception: " + cause + "]"; } } } ================================================ FILE: quartz/src/main/java/org/quartz/SchedulerFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Collection; /** * Provides a mechanism for obtaining client-usable handles to Scheduler * instances. * * @see Scheduler * @see 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; } ================================================ FILE: quartz/src/main/java/org/quartz/SchedulerListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 org.quartz.JobDetail} * is scheduled. *

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

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

* * @see SchedulerListener#schedulingDataCleared() */ void jobUnscheduled(TriggerKey triggerKey); /** *

* 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} * has been paused. *

*/ void triggerPaused(TriggerKey triggerKey); /** *

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

* *

If all groups were paused then triggerGroup will be null

* * @param triggerGroup the paused group, or null if all were paused */ void triggersPaused(String triggerGroup); /** *

* Called by the {@link Scheduler} when a {@link Trigger} * has been un-paused. *

*/ void triggerResumed(TriggerKey triggerKey); /** *

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

*/ void triggersResumed(String triggerGroup); /** *

* Called by the {@link Scheduler} when a {@link org.quartz.JobDetail} * has been added. *

*/ void jobAdded(JobDetail jobDetail); /** *

* Called by the {@link Scheduler} when a {@link org.quartz.JobDetail} * has been deleted. *

*/ void jobDeleted(JobKey jobKey); /** *

* Called by the {@link Scheduler} when a {@link org.quartz.JobDetail} * has been paused. *

*/ void jobPaused(JobKey jobKey); /** *

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

* * @param jobGroup the paused group, or null if all were paused */ void jobsPaused(String jobGroup); /** *

* Called by the {@link Scheduler} when a {@link org.quartz.JobDetail} * has been un-paused. *

*/ void jobResumed(JobKey jobKey); /** *

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

*/ void jobsResumed(String jobGroup); /** *

* Called by the {@link Scheduler} when a serious error has * occurred 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 move to standby mode. *

*/ void schedulerInStandbyMode(); /** *

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

*/ void schedulerStarted(); /** *

* Called by the {@link Scheduler} to inform the listener * that it is starting. *

*/ void schedulerStarting(); /** *

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

*/ void schedulerShutdown(); /** *

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

*/ void schedulerShuttingdown(); /** * Called by the {@link Scheduler} to inform the listener * that all jobs, triggers and calendars were deleted. */ void schedulingDataCleared(); } ================================================ FILE: quartz/src/main/java/org/quartz/SchedulerMetaData.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 { private static final long serialVersionUID = 4203690002633917647L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final String schedName; private final String schedInst; private final Class schedClass; private final boolean isRemote; private final boolean started; private final boolean isInStandbyMode; private final boolean shutdown; private final Date startTime; private final int numJobsExec; private final Class jsClass; private final boolean jsPersistent; private final boolean jsClustered; private final Class tpClass; private final int tpSize; private final 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, boolean jsClustered, 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.jsClustered = jsClustered; 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 getRunningSince() { return startTime; } /** *

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

*/ public int getNumberOfJobsExecuted() { 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 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 isJobStoreSupportsPersistence() { return jsPersistent; } /** *

* Returns whether or not the Scheduler'sJobStore * is clustered. *

*/ public boolean isJobStoreClustered() { return jsClustered; } /** *

* 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. *

*/ @Override 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: '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 'org.quartz.simpl.SimpleThreadPool' - with '8' threads Using job-store 'org.quartz.impl.JDBCJobStore' - which supports persistence.
     * 
*/ public String getSummary() throws SchedulerException { StringBuilder str = new StringBuilder("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 (getRunningSince() != null) { str.append(" Running since: "); str.append(getRunningSince()); } 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(getNumberOfJobsExecuted()); 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 (isJobStoreSupportsPersistence()) { str.append("supports persistence."); } else { str.append("does not support persistence."); } if (isJobStoreClustered()) { str.append(" and is clustered."); } else { str.append(" and is not clustered."); } str.append("\n"); return str.toString(); } } ================================================ FILE: quartz/src/main/java/org/quartz/SimpleScheduleBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.MutableTrigger; /** * SimpleScheduleBuilder is a {@link ScheduleBuilder} * that defines strict/literal interval-based schedules for * Triggers. * *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(simpleSchedule()
 *                 .withIntervalInHours(1)
 *                 .repeatForever())
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * @see SimpleTrigger * @see CalendarIntervalScheduleBuilder * @see CronScheduleBuilder * @see ScheduleBuilder * @see TriggerBuilder */ public class SimpleScheduleBuilder extends ScheduleBuilder { private long interval = 0; private int repeatCount = 0; private int misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_SMART_POLICY; protected SimpleScheduleBuilder() { } /** * Create a SimpleScheduleBuilder. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder simpleSchedule() { return new SimpleScheduleBuilder(); } /** * Create a SimpleScheduleBuilder set to repeat forever with a 1 minute interval. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatMinutelyForever() { return simpleSchedule() .withIntervalInMinutes(1) .repeatForever(); } /** * Create a SimpleScheduleBuilder set to repeat forever with an interval * of the given number of minutes. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatMinutelyForever(int minutes) { return simpleSchedule() .withIntervalInMinutes(minutes) .repeatForever(); } /** * Create a SimpleScheduleBuilder set to repeat forever with a 1 second interval. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatSecondlyForever() { return simpleSchedule() .withIntervalInSeconds(1) .repeatForever(); } /** * Create a SimpleScheduleBuilder set to repeat forever with an interval * of the given number of seconds. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatSecondlyForever(int seconds) { return simpleSchedule() .withIntervalInSeconds(seconds) .repeatForever(); } /** * Create a SimpleScheduleBuilder set to repeat forever with a 1 hour interval. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatHourlyForever() { return simpleSchedule() .withIntervalInHours(1) .repeatForever(); } /** * Create a SimpleScheduleBuilder set to repeat forever with an interval * of the given number of hours. * * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatHourlyForever(int hours) { return simpleSchedule() .withIntervalInHours(hours) .repeatForever(); } /** * Create a SimpleScheduleBuilder set to repeat the given number * of times - 1 with a 1 minute interval. * *

Note: Total count = 1 (at start time) + repeat count

* * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count) { if(count < 1) throw new IllegalArgumentException("Total count of firings must be at least one! Given count: " + count); return simpleSchedule() .withIntervalInMinutes(1) .withRepeatCount(count - 1); } /** * Create a SimpleScheduleBuilder set to repeat the given number * of times - 1 with an interval of the given number of minutes. * *

Note: Total count = 1 (at start time) + repeat count

* * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count, int minutes) { if(count < 1) throw new IllegalArgumentException("Total count of firings must be at least one! Given count: " + count); return simpleSchedule() .withIntervalInMinutes(minutes) .withRepeatCount(count - 1); } /** * Create a SimpleScheduleBuilder set to repeat the given number * of times - 1 with a 1 second interval. * *

Note: Total count = 1 (at start time) + repeat count

* * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatSecondlyForTotalCount(int count) { if(count < 1) throw new IllegalArgumentException("Total count of firings must be at least one! Given count: " + count); return simpleSchedule() .withIntervalInSeconds(1) .withRepeatCount(count - 1); } /** * Create a SimpleScheduleBuilder set to repeat the given number * of times - 1 with an interval of the given number of seconds. * *

Note: Total count = 1 (at start time) + repeat count

* * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatSecondlyForTotalCount(int count, int seconds) { if(count < 1) throw new IllegalArgumentException("Total count of firings must be at least one! Given count: " + count); return simpleSchedule() .withIntervalInSeconds(seconds) .withRepeatCount(count - 1); } /** * Create a SimpleScheduleBuilder set to repeat the given number * of times - 1 with a 1 hour interval. * *

Note: Total count = 1 (at start time) + repeat count

* * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatHourlyForTotalCount(int count) { if(count < 1) throw new IllegalArgumentException("Total count of firings must be at least one! Given count: " + count); return simpleSchedule() .withIntervalInHours(1) .withRepeatCount(count - 1); } /** * Create a SimpleScheduleBuilder set to repeat the given number * of times - 1 with an interval of the given number of hours. * *

Note: Total count = 1 (at start time) + repeat count

* * @return the new SimpleScheduleBuilder */ public static SimpleScheduleBuilder repeatHourlyForTotalCount(int count, int hours) { if(count < 1) throw new IllegalArgumentException("Total count of firings must be at least one! Given count: " + count); return simpleSchedule() .withIntervalInHours(hours) .withRepeatCount(count - 1); } /** * Build the actual Trigger -- NOT intended to be invoked by end users, * but will rather be invoked by a TriggerBuilder which this * ScheduleBuilder is given to. * * @see TriggerBuilder#withSchedule(ScheduleBuilder) */ @Override public MutableTrigger build() { SimpleTriggerImpl st = new SimpleTriggerImpl(); st.setRepeatInterval(interval); st.setRepeatCount(repeatCount); st.setMisfireInstruction(misfireInstruction); return st; } /** * Specify a repeat interval in milliseconds. * * @param intervalInMillis the number of seconds at which the trigger should repeat. * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#getRepeatInterval() * @see #withRepeatCount(int) */ public SimpleScheduleBuilder withIntervalInMilliseconds(long intervalInMillis) { this.interval = intervalInMillis; return this; } /** * Specify a repeat interval in seconds - which will then be multiplied * by 1000 to produce milliseconds. * * @param intervalInSeconds the number of seconds at which the trigger should repeat. * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#getRepeatInterval() * @see #withRepeatCount(int) */ public SimpleScheduleBuilder withIntervalInSeconds(int intervalInSeconds) { this.interval = intervalInSeconds * 1000L; return this; } /** * Specify a repeat interval in minutes - which will then be multiplied * by 60 * 1000 to produce milliseconds. * * @param intervalInMinutes the number of seconds at which the trigger should repeat. * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#getRepeatInterval() * @see #withRepeatCount(int) */ public SimpleScheduleBuilder withIntervalInMinutes(int intervalInMinutes) { this.interval = intervalInMinutes * DateBuilder.MILLISECONDS_IN_MINUTE; return this; } /** * Specify a repeat interval in minutes - which will then be multiplied * by 60 * 60 * 1000 to produce milliseconds. * * @param intervalInHours the number of seconds at which the trigger should repeat. * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#getRepeatInterval() * @see #withRepeatCount(int) */ public SimpleScheduleBuilder withIntervalInHours(int intervalInHours) { this.interval = intervalInHours * DateBuilder.MILLISECONDS_IN_HOUR; return this; } /** * Specify the number of times the trigger will repeat - the total number of * firings will be this number + 1. * * @param triggerRepeatCount the number of times the trigger should repeat after the initial firing. * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#getRepeatCount() * @see #repeatForever() */ public SimpleScheduleBuilder withRepeatCount(int triggerRepeatCount) { this.repeatCount = triggerRepeatCount; return this; } /** * Specify that the trigger will repeat indefinitely. * * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#getRepeatCount() * @see SimpleTrigger#REPEAT_INDEFINITELY * @see #withIntervalInMilliseconds(long) * @see #withIntervalInSeconds(int) * @see #withIntervalInMinutes(int) * @see #withIntervalInHours(int) */ public SimpleScheduleBuilder repeatForever() { this.repeatCount = SimpleTrigger.REPEAT_INDEFINITELY; return this; } /** * If the Trigger misfires, use the * {@link Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY} instruction. * * @return the updated SimpleScheduleBuilder * @see Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY */ public SimpleScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() { misfireInstruction = Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY; return this; } /** * If the Trigger misfires, use the * {@link SimpleTrigger#MISFIRE_INSTRUCTION_FIRE_NOW} instruction. * * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#MISFIRE_INSTRUCTION_FIRE_NOW */ public SimpleScheduleBuilder withMisfireHandlingInstructionFireNow() { misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW; return this; } /** * If the Trigger misfires, use the * {@link SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT} instruction. * * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT */ public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithExistingCount() { misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT; return this; } /** * If the Trigger misfires, use the * {@link SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT} instruction. * * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT */ public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithRemainingCount() { misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT; return this; } /** * If the Trigger misfires, use the * {@link SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT} instruction. * * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT */ public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithExistingCount() { misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT; return this; } /** * If the Trigger misfires, use the * {@link SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT} instruction. * * @return the updated SimpleScheduleBuilder * @see SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT */ public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithRemainingCount() { misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT; return this; } } ================================================ FILE: quartz/src/main/java/org/quartz/SimpleTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * A {@link Trigger} that is used to fire a Job * at a given moment in time, and optionally repeated at a specified interval. * * @see TriggerBuilder * @see SimpleScheduleBuilder * * @author James House * @author contributions by Lieven Govaerts of Ebitec Nv, Belgium. */ public interface SimpleTrigger extends Trigger { 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} * . *

*/ 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). *

*/ 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. *

*/ 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. *

*/ 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. *

*/ 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. *

*/ int REPEAT_INDEFINITELY = -1; /** * Get the number of times the SimpleTrigger should * repeat, after the initial firing, after which it will be automatically deleted. * * The total number of firings will be this number + 1. * * @return the number of times the trigger should repeat after the initial firing. * @see #REPEAT_INDEFINITELY */ int getRepeatCount(); /** *

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

*/ long getRepeatInterval(); /** *

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

*/ int getTimesTriggered(); TriggerBuilder getTriggerBuilder(); } ================================================ FILE: quartz/src/main/java/org/quartz/StatefulJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * A marker interface for {@link 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 DisallowConcurrentExecution * @see PersistJobDataAfterExecution * * @see Job * @see JobDetail * @see JobDataMap * @see Scheduler * * * @deprecated use DisallowConcurrentExecution and/or PersistJobDataAfterExecution annotations instead. * * @author James House */ @Deprecated @PersistJobDataAfterExecution @DisallowConcurrentExecution public interface StatefulJob extends Job { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ } ================================================ FILE: quartz/src/main/java/org/quartz/TimeOfDay.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.Serializable; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; /** * Represents a time in hour, minute and second of any given day. * *

The hour is in 24-hour convention, meaning values are from 0 to 23.

* * @see DailyTimeIntervalScheduleBuilder * * @since 2.0.3 * * @author James House * @author Zemian Deng <saltnlight5@gmail.com> */ public class TimeOfDay implements Serializable { private static final long serialVersionUID = 2964774315889061771L; private final int hour; private final int minute; private final int second; /** * Create a TimeOfDay instance for the given hour, minute and second. * * @param hour The hour of day, between 0 and 23. * @param minute The minute of the hour, between 0 and 59. * @param second The second of the minute, between 0 and 59. * @throws IllegalArgumentException if one or more of the input values is out of their valid range. */ public TimeOfDay(int hour, int minute, int second) { this.hour = hour; this.minute = minute; this.second = second; validate(); } /** * Create a TimeOfDay instance for the given hour and minute (at the zero second of the minute). * * @param hour The hour of day, between 0 and 23. * @param minute The minute of the hour, between 0 and 59. * @throws IllegalArgumentException if one or more of the input values is out of their valid range. */ public TimeOfDay(int hour, int minute) { this.hour = hour; this.minute = minute; this.second = 0; validate(); } private void validate() { if(hour < 0 || hour > 23) throw new IllegalArgumentException("Hour must be from 0 to 23"); if(minute < 0 || minute > 59) throw new IllegalArgumentException("Minute must be from 0 to 59"); if(second < 0 || second > 59) throw new IllegalArgumentException("Second must be from 0 to 59"); } /** * Create a TimeOfDay instance for the given hour, minute and second. * * @param hour The hour of day, between 0 and 23. * @param minute The minute of the hour, between 0 and 59. * @param second The second of the minute, between 0 and 59. * @throws IllegalArgumentException if one or more of the input values is out of their valid range. */ public static TimeOfDay hourMinuteAndSecondOfDay(int hour, int minute, int second) { return new TimeOfDay(hour, minute, second); } /** * Create a TimeOfDay instance for the given hour and minute (at the zero second of the minute). * * @param hour The hour of day, between 0 and 23. * @param minute The minute of the hour, between 0 and 59. * @throws IllegalArgumentException if one or more of the input values is out of their valid range. */ public static TimeOfDay hourAndMinuteOfDay(int hour, int minute) { return new TimeOfDay(hour, minute); } /** * The hour of the day (between 0 and 23). * * @return The hour of the day (between 0 and 23). */ public int getHour() { return hour; } /** * The minute of the hour. * * @return The minute of the hour (between 0 and 59). */ public int getMinute() { return minute; } /** * The second of the minute. * * @return The second of the minute (between 0 and 59). */ public int getSecond() { return second; } /** * Determine with this time of day is before the given time of day. * * @return true this time of day is before the given time of day. */ public boolean before(TimeOfDay timeOfDay) { if(timeOfDay.hour > hour) return true; if(timeOfDay.hour < hour) return false; if(timeOfDay.minute > minute) return true; if(timeOfDay.minute < minute) return false; if(timeOfDay.second > second) return true; if(timeOfDay.second < second) return false; return false; // must be equal... } @Override public boolean equals(Object obj) { if(!(obj instanceof TimeOfDay)) return false; TimeOfDay other = (TimeOfDay)obj; return (other.hour == hour && other.minute == minute && other.second == second); } @Override public int hashCode() { return (hour + 1) ^ (minute + 1) ^ (second + 1); } /** Return a date with time of day reset to this object values. The millisecond value will be zero. */ public Date getTimeOfDayForDate(Date dateTime) { if (dateTime == null) return null; Calendar cal = Calendar.getInstance(); cal.setTime(dateTime); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.SECOND, second); cal.clear(Calendar.MILLISECOND); return cal.getTime(); } /** * Create a TimeOfDay from the given date, in the system default TimeZone. * * @param dateTime The java.util.Date from which to extract Hour, Minute and Second. */ public static TimeOfDay hourAndMinuteAndSecondFromDate(Date dateTime) { return hourAndMinuteAndSecondFromDate(dateTime, null); } /** * Create a TimeOfDay from the given date, in the given TimeZone. * * @param dateTime The java.util.Date from which to extract Hour, Minute and Second. * @param tz The TimeZone from which relate Hour, Minute and Second for the given date. If null, system default * TimeZone will be used. */ public static TimeOfDay hourAndMinuteAndSecondFromDate(Date dateTime, TimeZone tz) { if (dateTime == null) return null; Calendar cal = Calendar.getInstance(); cal.setTime(dateTime); if(tz != null) cal.setTimeZone(tz); return new TimeOfDay(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND)); } /** * Create a TimeOfDay from the given date (at the zero-second), in the system default TimeZone. * * @param dateTime The java.util.Date from which to extract Hour and Minute. */ public static TimeOfDay hourAndMinuteFromDate(Date dateTime) { return hourAndMinuteFromDate(dateTime, null); } /** * Create a TimeOfDay from the given date (at the zero-second), in the system default TimeZone. * * @param dateTime The java.util.Date from which to extract Hour and Minute. * @param tz The TimeZone from which relate Hour and Minute for the given date. If null, system default * TimeZone will be used. */ public static TimeOfDay hourAndMinuteFromDate(Date dateTime, TimeZone tz) { if (dateTime == null) return null; Calendar cal = Calendar.getInstance(); cal.setTime(dateTime); if(tz != null) cal.setTimeZone(tz); return new TimeOfDay(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); } @Override public String toString() { return "TimeOfDay[" + hour + ":" + minute + ":" + second + "]"; } } ================================================ FILE: quartz/src/main/java/org/quartz/Trigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.Serializable; import java.util.Comparator; import java.util.Date; /** * The base interface with properties common to all Triggers - * use {@link TriggerBuilder} to instantiate an actual Trigger. * *

* Triggerss have a {@link TriggerKey} 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. *

* *

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

* * @see TriggerBuilder * @see JobDataMap * @see JobExecutionContext * @see TriggerUtils * @see SimpleTrigger * @see CronTrigger * @see CalendarIntervalTrigger * * @author James House */ public interface Trigger extends Serializable, Cloneable, Comparable { long serialVersionUID = -3904243490805975570L; enum TriggerState { NONE, NORMAL, PAUSED, COMPLETE, ERROR, BLOCKED } /** *

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

* *

RE_EXECUTE_JOB Instructs the {@link Scheduler} that the * {@link Trigger} wants the {@link 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 * ability to 'see' anything placed in the context by its last execution).

* *

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

* *

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

* *

SET_ALL_JOB_TRIGGERS_COMPLETE Instructs the {@link Scheduler} * that all Triggers referencing the same {@link org.quartz.JobDetail} * as this one should be put in the COMPLETE state.

* *

SET_TRIGGER_ERROR Instructs the {@link Scheduler} that all * Triggers referencing the same {@link org.quartz.JobDetail} as * this one should be put in the ERROR state.

* *

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

*/ enum CompletedExecutionInstruction { NOOP, RE_EXECUTE_JOB, SET_TRIGGER_COMPLETE, DELETE_TRIGGER, SET_ALL_JOB_TRIGGERS_COMPLETE, SET_TRIGGER_ERROR, SET_ALL_JOB_TRIGGERS_ERROR } /** * 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, * which logic will be trigger-implementation-dependent. * *

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

* * @see org.quartz.impl.triggers.SimpleTriggerImpl#updateAfterMisfire(Calendar) * @see org.quartz.impl.triggers.CronTriggerImpl#updateAfterMisfire(Calendar) * @see org.quartz.impl.triggers.DailyTimeIntervalTriggerImpl#updateAfterMisfire(Calendar) * @see org.quartz.impl.triggers.CalendarIntervalTriggerImpl#updateAfterMisfire(Calendar) */ int MISFIRE_INSTRUCTION_SMART_POLICY = 0; /** * Instructs the {@link Scheduler} that the * Trigger will never be evaluated for a misfire situation, * and that the scheduler will simply try to fire it as soon as it can, * and then update the Trigger as if it had fired at the proper time. * *

NOTE: if a trigger uses this instruction, and it has missed * several of its scheduled firings, then several rapid firings may occur * as the trigger attempt to catch back up to where it would have been. * For example, a SimpleTrigger that fires every 15 seconds which has * misfired for 5 minutes will fire 20 times once it gets the chance to * fire.

*/ int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1; /** * The default value for priority. */ int DEFAULT_PRIORITY = 5; TriggerKey getKey(); JobKey getJobKey(); /** * Return the description given to the Trigger instance by * its creator (if any). * * @return null if no description was set. */ String getDescription(); /** * Get the name of the {@link Calendar} associated with this * Trigger. * * @return null if there is no associated Calendar. */ String getCalendarName(); /** * 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. *

*/ JobDataMap getJobDataMap(); /** * 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 */ int getPriority(); /** * 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 org.quartz.spi.JobStore}. *

*/ boolean mayFireAgain(); /** * Get the time at which the Trigger should occur. */ Date getStartTime(); /** * Get the time at which the Trigger should quit repeating - * regardless of any remaining repeats (based on the trigger's particular * repeat settings). * * @see #getFinalFireTime() */ 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(org.quartz.spi.OperableTrigger, Calendar, java.util.Date, java.util.Date) */ Date getNextFireTime(); /** * Returns the previous time at which the Trigger fired. * If the trigger has not yet fired, null will be returned. */ 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. */ 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. *

*/ Date getFinalFireTime(); /** * 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 set as this property's value. * *

* If not explicitly set, the default value is MISFIRE_INSTRUCTION_SMART_POLICY. *

* * @see #MISFIRE_INSTRUCTION_SMART_POLICY * @see SimpleTrigger * @see CronTrigger */ int getMisfireInstruction(); /** * Get a {@link TriggerBuilder} that is configured to produce a * Trigger identical to this one. * * @see #getScheduleBuilder() */ TriggerBuilder getTriggerBuilder(); /** * Get a {@link ScheduleBuilder} that is configured to produce a * schedule identical to this trigger's schedule. * * @see #getTriggerBuilder() */ ScheduleBuilder getScheduleBuilder(); /** * Trigger equality is based upon the equality of the TriggerKey. * * @return true if the key of this Trigger equals that of the given Trigger. */ boolean equals(Object other); /** *

* Compare the next fire time of this Trigger to that of * another by comparing their keys, or in other words, sorts them * according to the natural (i.e. alphabetical) order of their keys. *

*/ int compareTo(Trigger other); /** * A Comparator that compares trigger's next fire times, or in other words, * sorts them according to earliest next fire time. If the fire times are * the same, then the triggers are sorted according to priority (highest * value first), if the priorities are the same, then they are sorted * by key. */ class TriggerTimeComparator implements Comparator, Serializable { private static final long serialVersionUID = -3904243490805975570L; // This static method exists for comparator in TC clustered quartz public static int compare(Date nextFireTime1, int priority1, TriggerKey key1, Date nextFireTime2, int priority2, TriggerKey key2) { if (nextFireTime1 != null || nextFireTime2 != null) { if (nextFireTime1 == null) { return 1; } if (nextFireTime2 == null) { return -1; } if(nextFireTime1.before(nextFireTime2)) { return -1; } if(nextFireTime1.after(nextFireTime2)) { return 1; } } int comp = priority2 - priority1; if (comp != 0) { return comp; } return key1.compareTo(key2); } public int compare(Trigger t1, Trigger t2) { return compare(t1.getNextFireTime(), t1.getPriority(), t1.getKey(), t2.getNextFireTime(), t2.getPriority(), t2.getKey()); } } } ================================================ FILE: quartz/src/main/java/org/quartz/TriggerBuilder.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.time.Instant; import java.util.Date; import org.quartz.spi.MutableTrigger; import org.quartz.utils.Key; /** * TriggerBuilder is used to instantiate {@link Trigger}s. * *

The builder will always try to keep itself in a valid state, with * reasonable defaults set for calling build() at any point. For instance * if you do not invoke withSchedule(..) method, a default schedule * of firing once immediately will be used. As another example, if you * do not invoked withIdentity(..) a trigger name will be generated * for you.

* *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(simpleSchedule()
 *                 .withIntervalInHours(1)
 *                 .repeatForever())
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * @see JobBuilder * @see ScheduleBuilder * @see DateBuilder * @see Trigger */ public class TriggerBuilder { private TriggerKey key; private String description; private Date startTime = new Date(); private Date endTime; private int priority = Trigger.DEFAULT_PRIORITY; private String calendarName; private JobKey jobKey; private JobDataMap jobDataMap = new JobDataMap(); private ScheduleBuilder scheduleBuilder = null; private TriggerBuilder() { } /** * Create a new TriggerBuilder with which to define a * specification for a Trigger. * * @return the new TriggerBuilder */ public static TriggerBuilder newTrigger() { return new TriggerBuilder<>(); } /** * Produce the Trigger. * * @return a Trigger that meets the specifications of the builder. */ @SuppressWarnings("unchecked") public T build() { if(scheduleBuilder == null) scheduleBuilder = SimpleScheduleBuilder.simpleSchedule(); MutableTrigger trig = scheduleBuilder.build(); trig.setCalendarName(calendarName); trig.setDescription(description); trig.setStartTime(startTime); trig.setEndTime(endTime); if(key == null) key = new TriggerKey(Key.createUniqueName(null), null); trig.setKey(key); if(jobKey != null) trig.setJobKey(jobKey); trig.setPriority(priority); if(!jobDataMap.isEmpty()) trig.setJobDataMap(jobDataMap); return (T) trig; } /** * Use a TriggerKey with the given name and default group to * identify the Trigger. * *

If none of the 'withIdentity' methods are set on the TriggerBuilder, * then a random, unique TriggerKey will be generated.

* * @param name the name element for the Trigger's TriggerKey * @return the updated TriggerBuilder * @see TriggerKey * @see Trigger#getKey() */ public TriggerBuilder withIdentity(String name) { key = new TriggerKey(name, null); return this; } /** * Use a TriggerKey with the given name and group to * identify the Trigger. * *

If none of the 'withIdentity' methods are set on the TriggerBuilder, * then a random, unique TriggerKey will be generated.

* * @param name the name element for the Trigger's TriggerKey * @param group the group element for the Trigger's TriggerKey * @return the updated TriggerBuilder * @see TriggerKey * @see Trigger#getKey() */ public TriggerBuilder withIdentity(String name, String group) { key = new TriggerKey(name, group); return this; } /** * Use the given TriggerKey to identify the Trigger. * *

If none of the 'withIdentity' methods are set on the TriggerBuilder, * then a random, unique TriggerKey will be generated.

* * @param triggerKey the TriggerKey for the Trigger to be built * @return the updated TriggerBuilder * @see TriggerKey * @see Trigger#getKey() */ public TriggerBuilder withIdentity(TriggerKey triggerKey) { this.key = triggerKey; return this; } /** * Set the given (human-meaningful) description of the Trigger. * * @param triggerDescription the description for the Trigger * @return the updated TriggerBuilder * @see Trigger#getDescription() */ public TriggerBuilder withDescription(String triggerDescription) { this.description = triggerDescription; return this; } /** * Set the Trigger's priority. When more than one Trigger have the same * fire time, the scheduler will fire the one with the highest priority * first. * * @param triggerPriority the priority for the Trigger * @return the updated TriggerBuilder * @see Trigger#DEFAULT_PRIORITY * @see Trigger#getPriority() */ public TriggerBuilder withPriority(int triggerPriority) { this.priority = triggerPriority; return this; } /** * Set the name of the {@link Calendar} that should be applied to this * Trigger's schedule. * * @param calName the name of the Calendar to reference. * @return the updated TriggerBuilder * @see Calendar * @see Trigger#getCalendarName() */ public TriggerBuilder modifiedByCalendar(String calName) { this.calendarName = calName; return this; } /** * Set the time the Trigger should start at - the trigger may or may * not fire at this time - depending upon the schedule configured for * the Trigger. However the Trigger will NOT fire before this time, * regardless of the Trigger's schedule. * * @param triggerStartTime the start time for the Trigger. * @return the updated TriggerBuilder * @see Trigger#getStartTime() * @see DateBuilder */ public TriggerBuilder startAt(Date triggerStartTime) { this.startTime = triggerStartTime; return this; } /** * Change the Instant type to Date type to set the trigger start at. * * @param triggerStartTime the start time for the Trigger but type is Instant * @return the updated TriggerBuilder * @see Trigger#getStartTime() * @see DateBuilder */ public TriggerBuilder startAt(Instant triggerStartTime){ this.startTime = Date.from(triggerStartTime); return this; } /** * Set the time the Trigger should start at to the current moment - * the trigger may or may not fire at this time - depending upon the * schedule configured for the Trigger. * * @return the updated TriggerBuilder * @see Trigger#getStartTime() */ public TriggerBuilder startNow() { this.startTime = new Date(); return this; } /** * Set the time at which the Trigger will no longer fire - even if it's * schedule has remaining repeats. * * @param triggerEndTime the end time for the Trigger. If null, the end time is indefinite. * @return the updated TriggerBuilder * @see Trigger#getEndTime() * @see DateBuilder */ public TriggerBuilder endAt(Date triggerEndTime) { this.endTime = triggerEndTime; return this; } /** * Set the {@link ScheduleBuilder} that will be used to define the * Trigger's schedule. * *

The particular SchedulerBuilder used will dictate * the concrete type of Trigger that is produced by the TriggerBuilder.

* * @param schedBuilder the SchedulerBuilder to use. * @return the updated TriggerBuilder * @see ScheduleBuilder * @see SimpleScheduleBuilder * @see CronScheduleBuilder * @see CalendarIntervalScheduleBuilder */ @SuppressWarnings("unchecked") public TriggerBuilder withSchedule(ScheduleBuilder schedBuilder) { this.scheduleBuilder = schedBuilder; return (TriggerBuilder) this; } /** * Set the identity of the Job which should be fired by the produced * Trigger. * * @param keyOfJobToFire the identity of the Job to fire. * @return the updated TriggerBuilder * @see Trigger#getJobKey() */ public TriggerBuilder forJob(JobKey keyOfJobToFire) { this.jobKey = keyOfJobToFire; return this; } /** * Set the identity of the Job which should be fired by the produced * Trigger - a JobKey will be produced with the given * name and default group. * * @param jobName the name of the job (in default group) to fire. * @return the updated TriggerBuilder * @see Trigger#getJobKey() */ public TriggerBuilder forJob(String jobName) { this.jobKey = new JobKey(jobName, null); return this; } /** * Set the identity of the Job which should be fired by the produced * Trigger - a JobKey will be produced with the given * name and group. * * @param jobName the name of the job to fire. * @param jobGroup the group of the job to fire. * @return the updated TriggerBuilder * @see Trigger#getJobKey() */ public TriggerBuilder forJob(String jobName, String jobGroup) { this.jobKey = new JobKey(jobName, jobGroup); return this; } /** * Set the identity of the Job which should be fired by the produced * Trigger, by extracting the JobKey from the given job. * * @param jobDetail the Job to fire. * @return the updated TriggerBuilder * @see Trigger#getJobKey() */ public TriggerBuilder forJob(JobDetail jobDetail) { JobKey k = jobDetail.getKey(); if(k.getName() == null) throw new IllegalArgumentException("The given job has not yet had a name assigned to it."); this.jobKey = k; return this; } /** * Add the given key-value pair to the Trigger's {@link JobDataMap}. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(String dataKey, String value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the Trigger's {@link JobDataMap}. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(String dataKey, Integer value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the Trigger's {@link JobDataMap}. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(String dataKey, Long value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the Trigger's {@link JobDataMap}. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(String dataKey, Float value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the Trigger's {@link JobDataMap}. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(String dataKey, Double value) { jobDataMap.put(dataKey, value); return this; } /** * Add the given key-value pair to the Trigger's {@link JobDataMap}. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(String dataKey, Boolean value) { jobDataMap.put(dataKey, value); return this; } /** * Set the Trigger's {@link JobDataMap}, adding any values to it * that were already set on this TriggerBuilder using any of the * other 'usingJobData' methods. * * @return the updated TriggerBuilder * @see Trigger#getJobDataMap() */ public TriggerBuilder usingJobData(JobDataMap newJobDataMap) { // add any existing data to this new map for(String dataKey: jobDataMap.keySet()) { newJobDataMap.put(dataKey, jobDataMap.get(dataKey)); } jobDataMap = newJobDataMap; // set new map as the map to use return this; } } ================================================ FILE: quartz/src/main/java/org/quartz/TriggerKey.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.quartz.utils.Key; /** * Uniquely identifies a {@link Trigger}. * *

Keys are composed of both a name and group, and the name must be unique * within the group. If only a name is specified then the default group * name will be used.

* * *

Quartz provides a builder-style API for constructing scheduling-related * entities via a Domain-Specific Language (DSL). The DSL can best be * utilized through the usage of static imports of the methods on the classes * TriggerBuilder, JobBuilder, * DateBuilder, JobKey, TriggerKey * and the various ScheduleBuilder implementations.

* *

Client code can then use the DSL to write code such as this:

*
 *         JobDetail job = newJob(MyJob.class)
 *             .withIdentity("myJob")
 *             .build();
 *             
 *         Trigger trigger = newTrigger() 
 *             .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
 *             .withSchedule(simpleSchedule()
 *                 .withIntervalInHours(1)
 *                 .repeatForever())
 *             .startAt(futureDate(10, MINUTES))
 *             .build();
 *         
 *         scheduler.scheduleJob(job, trigger);
 * 
* * * @see Trigger * @see Key#DEFAULT_GROUP */ public final class TriggerKey extends Key { private static final long serialVersionUID = 8070357886703449660L; public TriggerKey(String name) { super(name, null); } public TriggerKey(String name, String group) { super(name, group); } public static TriggerKey triggerKey(String name) { return new TriggerKey(name, null); } public static TriggerKey triggerKey(String name, String group) { return new TriggerKey(name, group); } } ================================================ FILE: quartz/src/main/java/org/quartz/TriggerListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.quartz.Trigger.CompletedExecutionInstruction; /** * 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 ListenerManager#addTriggerListener(TriggerListener, Matcher) * @see Matcher * @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 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 org.quartz.JobDetail} * is about to be executed. If the implementation vetoes the execution (via * returning true), the job's execute method will not be called. *

* *

* 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 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, CompletedExecutionInstruction triggerInstructionCode); } ================================================ FILE: quartz/src/main/java/org/quartz/TriggerUtils.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Date; import java.util.LinkedList; import java.util.List; import org.quartz.spi.OperableTrigger; /** * Convenience and utility methods for working with {@link Trigger}s. * * * @see CronTrigger * @see SimpleTrigger * @see DateBuilder * * @author James House */ public class TriggerUtils { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Private constructor because this is a pure utility class. */ private TriggerUtils() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * 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 trigger * 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(OperableTrigger trigger, org.quartz.Calendar cal, int numTimes) { LinkedList lst = new LinkedList<>(); OperableTrigger t = (OperableTrigger) trigger.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); } /** * Compute the Date that is 1 second after the Nth firing of * the given Trigger, taking the trigger's associated * Calendar into consideration. * * 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 trigger * 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 the computed Date, or null if the trigger (as configured) will not fire that many times. */ public static Date computeEndTimeToAllowParticularNumberOfFirings(OperableTrigger trigger, org.quartz.Calendar cal, int numTimes) { OperableTrigger t = (OperableTrigger) trigger.clone(); if (t.getNextFireTime() == null) { t.computeFirstFireTime(cal); } int c = 0; Date endTime = null; for (int i = 0; i < numTimes; i++) { Date d = t.getNextFireTime(); if (d != null) { c++; t.triggered(cal); if(c == numTimes) endTime = d; } else { break; } } if(endTime == null) return null; endTime = new Date(endTime.getTime() + 1000L); return endTime; } /** * 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 occurred will not be listed * in the output List. *

* * @param trigger * 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(OperableTrigger trigger, org.quartz.Calendar cal, Date from, Date to) { LinkedList lst = new LinkedList<>(); OperableTrigger t = (OperableTrigger) trigger.clone(); if (t.getNextFireTime() == null) { t.setStartTime(from); t.setEndTime(to); t.computeFirstFireTime(cal); } 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); } } ================================================ FILE: quartz/src/main/java/org/quartz/UnableToInterruptJobException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; /** * An exception that is thrown to indicate that a call to * InterruptableJob.interrupt() failed without interrupting the Job. * * @see org.quartz.InterruptableJob#interrupt() * * @author James House */ public class UnableToInterruptJobException extends SchedulerException { private static final long serialVersionUID = -490863760696463776L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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); } } ================================================ FILE: quartz/src/main/java/org/quartz/commonj/WorkManagerThreadExecutor.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.commonj; import commonj.work.Work; import commonj.work.WorkManager; import org.quartz.spi.ThreadExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.naming.InitialContext; import javax.naming.NamingException; /** * CommonJ WorkManager implementation of hacked Quartz ThreadExecutor class. * This class schedules work on a WorkManager which is looked up in JNDI. The * advantage is that all the work performed is done on a managed thread which is * required by WebSphere, see QUARTZ-743 for * details. * * @author matt.accola * @version $Revision$ $Date$ */ public class WorkManagerThreadExecutor implements ThreadExecutor { private final Logger log = LoggerFactory.getLogger(getClass()); private String workManagerName; private WorkManager workManager; public void execute(Thread thread) { Work work = new org.quartz.commonj.DelegatingWork(thread); try { this.workManager.schedule(work); } catch (Exception e) { log.error("Error attempting to schedule QuartzSchedulerThread: {}", e.getMessage(), e); } } public void initialize() { try { this.workManager = (WorkManager) new InitialContext().lookup(workManagerName); } catch (NamingException e) { throw new IllegalStateException("Could not locate WorkManager: " + e.getMessage(), e); } } /** * Sets the JNDI name of the work manager to use. * * @param workManagerName the JNDI name to use to lookup the work manager */ public void setWorkManagerName(String workManagerName) { this.workManagerName = workManagerName; } } class DelegatingWork implements Work { private final Runnable delegate; /** * Create a new DelegatingWork. * * @param delegate the Runnable implementation to delegate to */ public DelegatingWork(Runnable delegate) { this.delegate = delegate; } /** * @return the wrapped Runnable implementation. */ public final Runnable getDelegate() { return this.delegate; } /** * Delegates execution to the underlying Runnable. */ public void run() { this.delegate.run(); } public boolean isDaemon() { return false; } /** * This implementation is empty, since we expect the Runnable to terminate * based on some specific shutdown signal. */ public void release() { } } ================================================ FILE: quartz/src/main/java/org/quartz/core/JobExecutionProcessException.java ================================================ package org.quartz.core; import static org.quartz.core.JobExecutionProcessException.ProcessErrorMessage.JobExecution; import static org.quartz.core.JobExecutionProcessException.ProcessErrorMessage.JobListenerExecution; import static org.quartz.core.JobExecutionProcessException.ProcessErrorMessage.TriggerListenerExecution; import org.quartz.JobExecutionContext; import org.quartz.JobListener; import org.quartz.SchedulerException; import org.quartz.SchedulerListener; import org.quartz.TriggerListener; /** * This exception is thrown when an error occurs during execution: *
    *
  • Job execution
  • *
  • {@link JobListener} methods
  • *
  • {@link TriggerListener} methods
  • *
* The exception ensures that the job execution context is transferred to the implementation of the error * handling method of the scheduler listener {@link org.quartz.SchedulerListener#schedulerError(String, SchedulerException)}, * to try to fix the problem */ public class JobExecutionProcessException extends SchedulerException { enum ProcessErrorMessage { JobExecution("Job threw an unhandled exception"), JobListenerExecution("JobListener '%s' threw exception: %s."), TriggerListenerExecution("TriggerListener '%s' threw exception: %s."); private final String errorMsg; ProcessErrorMessage(String errorMsg) { this.errorMsg = errorMsg; } public String getErrorMsg() { return errorMsg; } } /** The job execution context. */ private final JobExecutionContext jobExecutionContext; public JobExecutionProcessException(JobExecutionContext jobExecutionContext, Throwable cause) { super(JobExecution.errorMsg, cause); this.jobExecutionContext = jobExecutionContext; } public JobExecutionProcessException(JobListener listener, JobExecutionContext jobExecutionContext, Throwable cause) { super(String.format(JobListenerExecution.getErrorMsg(), listener.getName(), cause.getMessage()), cause); this.jobExecutionContext = jobExecutionContext; } public JobExecutionProcessException(TriggerListener listener, JobExecutionContext jobExecutionContext, Throwable cause) { super(String.format(TriggerListenerExecution.getErrorMsg(), listener.getName(), cause.getMessage()), cause); this.jobExecutionContext = jobExecutionContext; } public JobExecutionContext getJobExecutionContext() { return jobExecutionContext; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/JobRunShell.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.impl.JobExecutionContextImpl; import org.quartz.listeners.SchedulerListenerSupport; import org.quartz.spi.OperableTrigger; import org.quartz.spi.TriggerFiredBundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* 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 org.quartz.core.QuartzSchedulerThread * @see org.quartz.Job * @see org.quartz.Trigger * * @author James House */ public class JobRunShell extends SchedulerListenerSupport implements Runnable { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected JobExecutionContextImpl jec = null; protected QuartzScheduler qs = null; protected TriggerFiredBundle firedTriggerBundle; protected Scheduler scheduler; protected volatile boolean shutdownRequested = false; private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a JobRunShell instance with the given settings. *

* * @param scheduler * The Scheduler instance that should be made * available within the JobExecutionContext. */ public JobRunShell(Scheduler scheduler, TriggerFiredBundle bundle) { this.scheduler = scheduler; this.firedTriggerBundle = bundle; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @Override public void schedulerShuttingdown() { requestShutdown(); } @Override protected Logger getLog() { return log; } public void initialize(QuartzScheduler sched) throws SchedulerException { this.qs = sched; Job job; JobDetail jobDetail = firedTriggerBundle.getJobDetail(); try { job = sched.getJobFactory().newJob(firedTriggerBundle, scheduler); } catch (SchedulerException se) { sched.notifySchedulerListenersError( "An error occurred instantiating job to be executed. job= '" + jobDetail.getKey() + "'", se); throw se; } catch (Throwable ncdfe) { // such as NoClassDefFoundError SchedulerException se = new SchedulerException( "Problem instantiating class '" + jobDetail.getJobClass().getName() + "' - ", ncdfe); sched.notifySchedulerListenersError( "An error occurred instantiating job to be executed. job= '" + jobDetail.getKey() + "'", se); throw se; } this.jec = new JobExecutionContextImpl(scheduler, firedTriggerBundle, job); } public void requestShutdown() { shutdownRequested = true; } public void run() { qs.addInternalSchedulerListener(this); try { OperableTrigger trigger = (OperableTrigger) 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().getKey() + ": couldn't begin execution.", se); break; } // notify job & trigger listeners... try { if (!notifyListenersBeginning(jec)) { break; } } catch(VetoedException ve) { try { CompletedExecutionInstruction instCode = trigger.executionComplete(jec, null); qs.notifyJobStoreJobVetoed(trigger, jobDetail, instCode); // QTZ-205 // Even if trigger got vetoed, we still needs to check to see if it's the trigger's finalized run or not. if (jec.getTrigger().getNextFireTime() == null) { qs.notifySchedulerListenersFinalized(jec.getTrigger()); } complete(true); } catch (SchedulerException se) { qs.notifySchedulerListenersError("Error during veto of Job (" + jec.getJobDetail().getKey() + ": couldn't finalize execution.", se); } break; } long startTime = System.currentTimeMillis(); long endTime; // execute the job try { log.debug("Calling execute on job {}", jobDetail.getKey()); job.execute(jec); endTime = System.currentTimeMillis(); } catch (JobExecutionException jee) { endTime = System.currentTimeMillis(); jobExEx = jee; getLog().info("Job {} threw a JobExecutionException: ", jobDetail.getKey(), jobExEx); } catch (Throwable e) { endTime = System.currentTimeMillis(); getLog().error("Job {} threw an unhandled Exception: ", jobDetail.getKey(), e); SchedulerException se = new JobExecutionProcessException(jec, e); qs.notifySchedulerListenersError("Job " + jec.getJobDetail().getKey() + " threw an exception.", se); jobExEx = new JobExecutionException(se, false); } jec.setJobRunTime(endTime - startTime); // notify all job listeners if (!notifyJobListenersComplete(jec, jobExEx)) { break; } CompletedExecutionInstruction instCode = CompletedExecutionInstruction.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); 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 == CompletedExecutionInstruction.RE_EXECUTE_JOB) { jec.incrementRefireCount(); try { complete(false); } catch (SchedulerException se) { qs.notifySchedulerListenersError("Error executing Job (" + jec.getJobDetail().getKey() + ": couldn't finalize execution.", se); } continue; } try { complete(true); } catch (SchedulerException se) { qs.notifySchedulerListenersError("Error executing Job (" + jec.getJobDetail().getKey() + ": couldn't finalize execution.", se); continue; } qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode); break; } while (true); } finally { qs.removeInternalSchedulerListener(this); } } protected void begin() throws SchedulerException { } protected void complete(boolean successfulExecution) throws SchedulerException { } public void passivate() { jec = null; qs = null; } private boolean notifyListenersBeginning(JobExecutionContext jobExCtx) throws VetoedException { boolean vetoed; // notify all trigger listeners try { vetoed = qs.notifyTriggerListenersFired(jobExCtx); } catch (SchedulerException se) { qs.notifySchedulerListenersError( "Unable to notify TriggerListener(s) while firing trigger " + "(Trigger and Job will NOT be fired!). trigger= " + jobExCtx.getTrigger().getKey() + " job= " + jobExCtx.getJobDetail().getKey(), se); return false; } if(vetoed) { try { qs.notifyJobListenersWasVetoed(jobExCtx); } catch (SchedulerException se) { qs.notifySchedulerListenersError( "Unable to notify JobListener(s) of vetoed execution " + "while firing trigger (Trigger and Job will NOT be " + "fired!). trigger= " + jobExCtx.getTrigger().getKey() + " job= " + jobExCtx.getJobDetail().getKey(), se); } throw new VetoedException(); } // notify all job listeners try { qs.notifyJobListenersToBeExecuted(jobExCtx); } catch (SchedulerException se) { qs.notifySchedulerListenersError( "Unable to notify JobListener(s) of Job to be executed: " + "(Job will NOT be executed!). trigger= " + jobExCtx.getTrigger().getKey() + " job= " + jobExCtx.getJobDetail().getKey(), se); return false; } return true; } private boolean notifyJobListenersComplete(JobExecutionContext jobExCtx, JobExecutionException jobExEx) { try { qs.notifyJobListenersWasExecuted(jobExCtx, jobExEx); } catch (SchedulerException se) { qs.notifySchedulerListenersError( "Unable to notify JobListener(s) of Job that was executed: " + "(error will be ignored). trigger= " + jobExCtx.getTrigger().getKey() + " job= " + jobExCtx.getJobDetail().getKey(), se); return false; } return true; } private boolean notifyTriggerListenersComplete(JobExecutionContext jobExCtx, CompletedExecutionInstruction instCode) { try { qs.notifyTriggerListenersComplete(jobExCtx, instCode); } catch (SchedulerException se) { qs.notifySchedulerListenersError( "Unable to notify TriggerListener(s) of Job that was executed: " + "(error will be ignored). trigger= " + jobExCtx.getTrigger().getKey() + " job= " + jobExCtx.getJobDetail().getKey(), se); return false; } if (jobExCtx.getTrigger().getNextFireTime() == null) { qs.notifySchedulerListenersFinalized(jobExCtx.getTrigger()); } return true; } static class VetoedException extends Exception { private static final long serialVersionUID = 1539955697495918463L; public VetoedException() { } } } ================================================ FILE: quartz/src/main/java/org/quartz/core/JobRunShellFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.spi.TriggerFiredBundle; /** *

* Responsible for creating the instances of {@link JobRunShell} * to be used within the {@link QuartzScheduler} instance. *

* * @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. *

*/ void initialize(Scheduler scheduler) throws SchedulerConfigException; /** *

* Called by the {@link org.quartz.core.QuartzSchedulerThread} * to obtain instances of {@link JobRunShell}. *

*/ JobRunShell createJobRunShell(TriggerFiredBundle bundle) throws SchedulerException; } ================================================ FILE: quartz/src/main/java/org/quartz/core/ListenerManagerImpl.java ================================================ package org.quartz.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.quartz.JobKey; import org.quartz.JobListener; import org.quartz.ListenerManager; import org.quartz.Matcher; import org.quartz.SchedulerListener; import org.quartz.TriggerKey; import org.quartz.TriggerListener; import org.quartz.impl.matchers.EverythingMatcher; public class ListenerManagerImpl implements ListenerManager { private final Map globalJobListeners = new LinkedHashMap<>(10); private final Map globalTriggerListeners = new LinkedHashMap<>(10); private final Map>> globalJobListenersMatchers = new LinkedHashMap<>(10); private final Map>> globalTriggerListenersMatchers = new LinkedHashMap<>(10); private final ArrayList schedulerListeners = new ArrayList<>(10); public void addJobListener(JobListener jobListener, Matcher ... matchers) { addJobListener(jobListener, Arrays.asList(matchers)); } public void addJobListener(JobListener jobListener, List> matchers) { if (jobListener.getName() == null || jobListener.getName().isEmpty()) { throw new IllegalArgumentException( "JobListener name cannot be empty."); } synchronized (globalJobListeners) { globalJobListeners.put(jobListener.getName(), jobListener); LinkedList> matchersL = new LinkedList<>(); if(matchers != null && !matchers.isEmpty()) matchersL.addAll(matchers); else matchersL.add(EverythingMatcher.allJobs()); globalJobListenersMatchers.put(jobListener.getName(), matchersL); } } public void addJobListener(JobListener jobListener) { addJobListener(jobListener, EverythingMatcher.allJobs()); } public void addJobListener(JobListener jobListener, Matcher matcher) { if (jobListener.getName() == null || jobListener.getName().isEmpty()) { throw new IllegalArgumentException( "JobListener name cannot be empty."); } synchronized (globalJobListeners) { globalJobListeners.put(jobListener.getName(), jobListener); LinkedList> matchersL = new LinkedList<>(); if(matcher != null) matchersL.add(matcher); else matchersL.add(EverythingMatcher.allJobs()); globalJobListenersMatchers.put(jobListener.getName(), matchersL); } } public boolean addJobListenerMatcher(String listenerName, Matcher matcher) { if(matcher == null) throw new IllegalArgumentException("Null value not acceptable."); synchronized (globalJobListeners) { List> matchers = globalJobListenersMatchers.get(listenerName); if(matchers == null) return false; matchers.add(matcher); return true; } } public boolean removeJobListenerMatcher(String listenerName, Matcher matcher) { if(matcher == null) throw new IllegalArgumentException("Non-null value not acceptable."); synchronized (globalJobListeners) { List> matchers = globalJobListenersMatchers.get(listenerName); if(matchers == null) return false; return matchers.remove(matcher); } } public List> getJobListenerMatchers(String listenerName) { synchronized (globalJobListeners) { List> matchers = globalJobListenersMatchers.get(listenerName); if(matchers == null) return null; return Collections.unmodifiableList(matchers); } } public boolean setJobListenerMatchers(String listenerName, List> matchers) { if(matchers == null) throw new IllegalArgumentException("Non-null value not acceptable."); synchronized (globalJobListeners) { List> oldMatchers = globalJobListenersMatchers.get(listenerName); if(oldMatchers == null) return false; globalJobListenersMatchers.put(listenerName, matchers); return true; } } public boolean removeJobListener(String name) { synchronized (globalJobListeners) { return (globalJobListeners.remove(name) != null); } } public List getJobListeners() { synchronized (globalJobListeners) { return java.util.Collections.unmodifiableList(new LinkedList<>(globalJobListeners.values())); } } public JobListener getJobListener(String name) { synchronized (globalJobListeners) { return globalJobListeners.get(name); } } public void addTriggerListener(TriggerListener triggerListener, Matcher ... matchers) { addTriggerListener(triggerListener, Arrays.asList(matchers)); } public void addTriggerListener(TriggerListener triggerListener, List> matchers) { if (triggerListener.getName() == null || triggerListener.getName().isEmpty()) { throw new IllegalArgumentException( "TriggerListener name cannot be empty."); } synchronized (globalTriggerListeners) { globalTriggerListeners.put(triggerListener.getName(), triggerListener); LinkedList> matchersL = new LinkedList<>(); if(matchers != null && !matchers.isEmpty()) matchersL.addAll(matchers); else matchersL.add(EverythingMatcher.allTriggers()); globalTriggerListenersMatchers.put(triggerListener.getName(), matchersL); } } public void addTriggerListener(TriggerListener triggerListener) { addTriggerListener(triggerListener, EverythingMatcher.allTriggers()); } public void addTriggerListener(TriggerListener triggerListener, Matcher matcher) { if(matcher == null) throw new IllegalArgumentException("Null value not acceptable for matcher."); if (triggerListener.getName() == null || triggerListener.getName().isEmpty()) { throw new IllegalArgumentException( "TriggerListener name cannot be empty."); } synchronized (globalTriggerListeners) { globalTriggerListeners.put(triggerListener.getName(), triggerListener); List> matchers = new LinkedList<>(); matchers.add(matcher); globalTriggerListenersMatchers.put(triggerListener.getName(), matchers); } } public boolean addTriggerListenerMatcher(String listenerName, Matcher matcher) { if(matcher == null) throw new IllegalArgumentException("Non-null value not acceptable."); synchronized (globalTriggerListeners) { List> matchers = globalTriggerListenersMatchers.get(listenerName); if(matchers == null) return false; matchers.add(matcher); return true; } } public boolean removeTriggerListenerMatcher(String listenerName, Matcher matcher) { if(matcher == null) throw new IllegalArgumentException("Non-null value not acceptable."); synchronized (globalTriggerListeners) { List> matchers = globalTriggerListenersMatchers.get(listenerName); if(matchers == null) return false; return matchers.remove(matcher); } } public List> getTriggerListenerMatchers(String listenerName) { synchronized (globalTriggerListeners) { List> matchers = globalTriggerListenersMatchers.get(listenerName); if(matchers == null) return null; return Collections.unmodifiableList(matchers); } } public boolean setTriggerListenerMatchers(String listenerName, List> matchers) { if(matchers == null) throw new IllegalArgumentException("Non-null value not acceptable."); synchronized (globalTriggerListeners) { List> oldMatchers = globalTriggerListenersMatchers.get(listenerName); if(oldMatchers == null) return false; globalTriggerListenersMatchers.put(listenerName, matchers); return true; } } public boolean removeTriggerListener(String name) { synchronized (globalTriggerListeners) { return (globalTriggerListeners.remove(name) != null); } } public List getTriggerListeners() { synchronized (globalTriggerListeners) { return java.util.Collections.unmodifiableList(new LinkedList<>(globalTriggerListeners.values())); } } public TriggerListener getTriggerListener(String name) { synchronized (globalTriggerListeners) { return globalTriggerListeners.get(name); } } public void addSchedulerListener(SchedulerListener schedulerListener) { synchronized (schedulerListeners) { schedulerListeners.add(schedulerListener); } } public boolean removeSchedulerListener(SchedulerListener schedulerListener) { synchronized (schedulerListeners) { return schedulerListeners.remove(schedulerListener); } } public List getSchedulerListeners() { synchronized (schedulerListeners) { return java.util.Collections.unmodifiableList(new ArrayList<>(schedulerListeners)); } } } ================================================ FILE: quartz/src/main/java/org/quartz/core/NullSampledStatisticsImpl.java ================================================ package org.quartz.core; public class NullSampledStatisticsImpl implements SampledStatistics { public long getJobsCompletedMostRecentSample() { return 0; } public long getJobsExecutingMostRecentSample() { return 0; } public long getJobsScheduledMostRecentSample() { return 0; } public void shutdown() { // nothing to do } } ================================================ FILE: quartz/src/main/java/org/quartz/core/QuartzScheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import java.io.InputStream; import java.lang.management.ManagementFactory; 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.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; import javax.management.MBeanServer; import javax.management.ObjectName; import org.quartz.Calendar; import org.quartz.InterruptableJob; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.JobListener; import org.quartz.ListenerManager; import org.quartz.Matcher; import org.quartz.ObjectAlreadyExistsException; import org.quartz.Scheduler; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.quartz.SchedulerListener; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import static org.quartz.TriggerBuilder.*; import org.quartz.TriggerKey; import org.quartz.TriggerListener; import org.quartz.UnableToInterruptJobException; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.Trigger.TriggerState; import org.quartz.core.jmx.QuartzSchedulerMBean; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.listeners.SchedulerListenerSupport; import org.quartz.simpl.PropertySettingJobFactory; import org.quartz.spi.JobFactory; import org.quartz.spi.OperableTrigger; import org.quartz.spi.SchedulerPlugin; import org.quartz.spi.SchedulerSignaler; import org.quartz.spi.ThreadExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is the heart of Quartz, an indirect implementation of the {@link org.quartz.Scheduler} * interface, containing methods to schedule {@link org.quartz.Job}s, * register {@link org.quartz.JobListener} instances, etc. *

* * @see org.quartz.Scheduler * @see org.quartz.core.QuartzSchedulerThread * @see org.quartz.spi.JobStore * @see 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(); try (InputStream is = QuartzScheduler.class.getResourceAsStream("quartz-build.properties")) { if (is != null) { props.load(is); String version = props.getProperty("version"); if (version != null) { String[] versionComponents = version.split("\\."); VERSION_MAJOR = versionComponents[0]; VERSION_MINOR = versionComponents[1]; if (versionComponents.length > 2) VERSION_ITERATION = versionComponents[2]; else VERSION_ITERATION = "0"; } else { (LoggerFactory.getLogger(QuartzScheduler.class)).error( "Can't parse Quartz version from quartz-build.properties"); } } } catch (Exception e) { (LoggerFactory.getLogger(QuartzScheduler.class)).error( "Error loading version info from quartz-build.properties.", e); } } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final QuartzSchedulerResources resources; private final QuartzSchedulerThread schedThread; private ThreadGroup threadGroup; private final SchedulerContext context = new SchedulerContext(); private final ListenerManager listenerManager = new ListenerManagerImpl(); private final HashMap internalJobListeners = new HashMap<>(10); private final HashMap internalTriggerListeners = new HashMap<>(10); private final ArrayList internalSchedulerListeners = new ArrayList<>(10); private JobFactory jobFactory = new PropertySettingJobFactory(); ExecutingJobsManager jobMgr = null; ErrorLogger errLogger = null; private final SchedulerSignaler signaler; private final Random random = new Random(); private final ArrayList holdToPreventGC = new ArrayList<>(5); private boolean signalOnSchedulingChange = true; private volatile boolean closed = false; private volatile boolean shuttingDown = false; private boolean boundRemotely = false; private QuartzSchedulerMBean jmxBean = null; private Date initialStart = null; private final Logger log = LoggerFactory.getLogger(getClass()); // private static final Map MGMT_SVR_BY_BIND = new // HashMap(); // private String registeredManagementServerBind; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a QuartzScheduler with the given configuration * properties. *

* * @see QuartzSchedulerResources */ public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval) throws SchedulerException { this.resources = resources; if (resources.getJobStore() instanceof JobListener) { addInternalJobListener((JobListener)resources.getJobStore()); } this.schedThread = new QuartzSchedulerThread(this, resources); ThreadExecutor schedThreadExecutor = resources.getThreadExecutor(); schedThreadExecutor.execute(this.schedThread); if (idleWaitTime > 0) { this.schedThread.setIdleWaitTime(idleWaitTime); } jobMgr = new ExecutingJobsManager(); addInternalJobListener(jobMgr); errLogger = new ErrorLogger(); addInternalSchedulerListener(errLogger); signaler = new SchedulerSignalerImpl(this, this.schedThread); getLog().info("Quartz Scheduler v{} created.", getVersion()); } public void initialize() throws SchedulerException { 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); } } // ManagementRESTServiceConfiguration managementRESTServiceConfiguration // = resources.getManagementRESTServiceConfiguration(); // // if (managementRESTServiceConfiguration != null && // managementRESTServiceConfiguration.isEnabled()) { // try { // /** // * ManagementServer will only be instantiated and started if one // * isn't already running on the configured port for this class // * loader space. // */ // synchronized (QuartzScheduler.class) { // if // (!MGMT_SVR_BY_BIND.containsKey(managementRESTServiceConfiguration.getBind())) // { // Class managementServerImplClass = // Class.forName("org.quartz.management.ManagementServerImpl"); // Class managementRESTServiceConfigurationClass[] = new Class[] { // managementRESTServiceConfiguration.getClass() }; // Constructor managementRESTServiceConfigurationConstructor = // managementServerImplClass // .getConstructor(managementRESTServiceConfigurationClass); // Object arglist[] = new Object[] { managementRESTServiceConfiguration // }; // ManagementServer embeddedRESTServer = ((ManagementServer) // managementRESTServiceConfigurationConstructor.newInstance(arglist)); // embeddedRESTServer.start(); // MGMT_SVR_BY_BIND.put(managementRESTServiceConfiguration.getBind(), // embeddedRESTServer); // } // registeredManagementServerBind = // managementRESTServiceConfiguration.getBind(); // ManagementServer embeddedRESTServer = // MGMT_SVR_BY_BIND.get(registeredManagementServerBind); // embeddedRESTServer.register(this); // } // } catch (Exception e) { // throw new // SchedulerException("Unable to start the scheduler management REST service", // e); // } // } getLog().info("Scheduler meta-data: {}", new SchedulerMetaData(getSchedulerName(), getSchedulerInstanceId(), getClass(), boundRemotely, runningSince() != null, isInStandbyMode(), isShutdown(), runningSince(), numJobsExecuted(), getJobStoreClass(), supportsPersistence(), isClustered(), getThreadPoolClass(), getThreadPoolSize(), getVersion())); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 Logger getLog() { return log; } /** * Register the scheduler in the local MBeanServer. */ private void registerJMX() throws Exception { String jmxObjectName = resources.getJMXObjectName(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); jmxBean = new QuartzSchedulerMBeanImpl(this); mbs.registerMBean(jmxBean, new ObjectName(jmxObjectName)); } /** * Unregister the scheduler from the local MBeanServer. */ private void unregisterJMX() throws Exception { String jmxObjectName = resources.getJMXObjectName(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); mbs.unregisterMBean(new ObjectName(jmxObjectName)); jmxBean.setSampledStatisticsEnabled(false); getLog().info("Scheduler unregistered from name '{}' in the local MBeanServer.", jmxObjectName); } /** *

* 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.isEmpty()) { return; } RemotableQuartzScheduler exportable; if(resources.getRMIServerPort() > 0) { exportable = (RemotableQuartzScheduler) UnicastRemoteObject .exportObject(this, resources.getRMIServerPort()); } else { exportable = (RemotableQuartzScheduler) UnicastRemoteObject .exportObject(this, 1099); } Registry registry; 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-deployed, 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-deployed, 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); boundRemotely = true; 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.isEmpty()) { 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 '{}' in RMI registry", bindName); } /** *

* 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 thread group for Quartz's main threads. *

*/ 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; } /////////////////////////////////////////////////////////////////////////// /// /// Scheduler State Management Methods /// /////////////////////////////////////////////////////////////////////////// /** *

* Starts the QuartzScheduler's threads that fire {@link org.quartz.Trigger}s. *

* *

* All {@link 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."); } // QTZ-212 : calling new schedulerStarting() method on the listeners // right after entering start() notifySchedulerListenersStarting(); if (initialStart == null) { initialStart = new Date(); this.resources.getJobStore().schedulerStarted(); startPlugins(); } else { resources.getJobStore().schedulerResumed(); } schedThread.togglePause(false); getLog().info("Scheduler {} started.", resources.getUniqueIdentifier()); notifySchedulerListenersStarted(); } 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(() -> { try { Thread.sleep(seconds * 1000L); } catch(InterruptedException ignore) {} try { start(); } catch(SchedulerException se) { getLog().error("Unable to start scheduler after startup delay.", se); } }); t.start(); } /** *

* Temporarily halts the QuartzScheduler's firing of {@link org.quartz.Trigger}s. *

* *

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

*/ public void standby() { resources.getJobStore().schedulerPaused(); schedThread.togglePause(true); getLog().info("Scheduler {} paused.", resources.getUniqueIdentifier()); notifySchedulerListenersInStandbyMode(); } /** *

* Reports whether the Scheduler is paused. *

*/ public boolean isInStandbyMode() { return schedThread.isPaused(); } public Date runningSince() { if(initialStart == null) return null; return new Date(initialStart.getTime()); } public int numJobsExecuted() { return jobMgr.getNumJobsFired(); } public Class getJobStoreClass() { return resources.getJobStore().getClass(); } public boolean supportsPersistence() { return resources.getJobStore().supportsPersistence(); } public boolean isClustered() { return resources.getJobStore().isClustered(); } public Class getThreadPoolClass() { return resources.getThreadPool().getClass(); } public int getThreadPoolSize() { return resources.getThreadPool().getPoolSize(); } /** *

* Halts the QuartzScheduler's firing of {@link 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 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 {} shutting down.", resources.getUniqueIdentifier()); // boolean removeMgmtSvr = false; // if (registeredManagementServerBind != null) { // ManagementServer standaloneRestServer = // MGMT_SVR_BY_BIND.get(registeredManagementServerBind); // // try { // standaloneRestServer.unregister(this); // // if (!standaloneRestServer.hasRegistered()) { // removeMgmtSvr = true; // standaloneRestServer.stop(); // } // } catch (Exception e) { // getLog().warn("Failed to shutdown the ManagementRESTService", e); // } finally { // if (removeMgmtSvr) { // MGMT_SVR_BY_BIND.remove(registeredManagementServerBind); // } // // registeredManagementServerBind = null; // } // } standby(); schedThread.halt(waitForJobsToComplete); notifySchedulerListenersShuttingdown(); if( (resources.isInterruptJobsOnShutdown() && !waitForJobsToComplete) || (resources.isInterruptJobsOnShutdownWithWait() && waitForJobsToComplete)) { List jobs = getCurrentlyExecutingJobs(); for(JobExecutionContext job: jobs) { if(job.getJobInstance() instanceof InterruptableJob) try { ((InterruptableJob)job.getJobInstance()).interrupt(); } catch (Throwable e) { // do nothing, this was just a courtesy effort getLog().warn("Encountered error when interrupting job {} during shutdown: {}", job.getJobDetail().getKey(), e.getMessage(), e); } } } resources.getThreadPool().shutdown(waitForJobsToComplete); closed = true; if (resources.getJMXExport()) { try { unregisterJMX(); } catch (Exception e) { } } if(boundRemotely) { try { unBind(); } catch (RemoteException re) { } } shutdownPlugins(); resources.getJobStore().shutdown(); notifySchedulerListenersShutdown(); SchedulerRepository.getInstance().remove(resources.getName()); holdToPreventGC.clear(); getLog().info("Scheduler {} shutdown complete.", resources.getUniqueIdentifier()); } /** *

* Reports whether the Scheduler has been shutdown. *

*/ public boolean isShutdown() { return closed; } public boolean isShuttingDown() { return shuttingDown; } public boolean isStarted() { return !shuttingDown && !closed && !isInStandbyMode() && initialStart != null; } 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 org.quartz.Job} identified by the given * {@link org.quartz.JobDetail} to the Scheduler, and * associate the given {@link 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(JobDetail jobDetail, Trigger trigger) throws SchedulerException { validateState(); if (jobDetail == null) { throw new SchedulerException("JobDetail cannot be null"); } if (trigger == null) { throw new SchedulerException("Trigger cannot be null"); } if (jobDetail.getKey() == null) { throw new SchedulerException("Job's key cannot be null"); } if (jobDetail.getJobClass() == null) { throw new SchedulerException("Job's class cannot be null"); } OperableTrigger trig = (OperableTrigger)trigger; if (trigger.getJobKey() == null) { trig.setJobKey(jobDetail.getKey()); } else if (!trigger.getJobKey().equals(jobDetail.getKey())) { throw new SchedulerException( "Trigger does not reference given job!"); } trig.validate(); Calendar cal = null; if (trigger.getCalendarName() != null) { cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName()); } Date ft = trig.computeFirstFireTime(cal); if (ft == null) { throw new SchedulerException( "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire."); } resources.getJobStore().storeJobAndTrigger(jobDetail, trig); notifySchedulerListenersJobAdded(jobDetail); notifySchedulerThread(trigger.getNextFireTime().getTime()); notifySchedulerListenersScheduled(trigger); return ft; } /** *

* Schedule the given {@link 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(Trigger trigger) throws SchedulerException { validateState(); if (trigger == null) { throw new SchedulerException("Trigger cannot be null"); } OperableTrigger trig = (OperableTrigger)trigger; trig.validate(); Calendar cal = null; if (trigger.getCalendarName() != null) { cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName()); if(cal == null) { throw new SchedulerException( "Calendar not found: " + trigger.getCalendarName()); } } Date ft = trig.computeFirstFireTime(cal); if (ft == null) { throw new SchedulerException( "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire."); } resources.getJobStore().storeTrigger(trig, false); notifySchedulerThread(trigger.getNextFireTime().getTime()); notifySchedulerListenersScheduled(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(JobDetail jobDetail, boolean replace) throws SchedulerException { addJob(jobDetail, replace, false); } public void addJob(JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) throws SchedulerException { validateState(); if (!storeNonDurableWhileAwaitingScheduling && !jobDetail.isDurable()) { throw new SchedulerException( "Jobs added with no trigger must be durable."); } resources.getJobStore().storeJob(jobDetail, replace); notifySchedulerThread(0L); notifySchedulerListenersJobAdded(jobDetail); } /** *

* 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(JobKey jobKey) throws SchedulerException { validateState(); boolean result = false; List triggers = getTriggersOfJob(jobKey); for (Trigger trigger : triggers) { if (!unscheduleJob(trigger.getKey())) { StringBuilder sb = new StringBuilder().append( "Unable to unschedule trigger [").append( trigger.getKey()).append("] while deleting job [") .append(jobKey).append( "]"); throw new SchedulerException(sb.toString()); } result = true; } result = resources.getJobStore().removeJob(jobKey) || result; if (result) { notifySchedulerThread(0L); notifySchedulerListenersJobDeleted(jobKey); } return result; } public boolean deleteJobs(List jobKeys) throws SchedulerException { validateState(); boolean result; result = resources.getJobStore().removeJobs(jobKeys); notifySchedulerThread(0L); for(JobKey key: jobKeys) notifySchedulerListenersJobDeleted(key); return result; } public void scheduleJobs(Map> triggersAndJobs, boolean replace) throws SchedulerException { validateState(); // make sure all triggers refer to their associated job for(Entry> e: triggersAndJobs.entrySet()) { JobDetail job = e.getKey(); if(job == null) // there can be one of these (for adding a bulk set of triggers for preexisting jobs) continue; Set triggers = e.getValue(); if(triggers == null) // this is possible because the job may be durable, and not yet be having triggers continue; for(Trigger trigger: triggers) { OperableTrigger opt = (OperableTrigger)trigger; opt.setJobKey(job.getKey()); opt.validate(); Calendar cal = null; if (trigger.getCalendarName() != null) { cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName()); if(cal == null) { throw new SchedulerException( "Calendar '" + trigger.getCalendarName() + "' not found for trigger: " + trigger.getKey()); } } Date ft = opt.computeFirstFireTime(cal); if (ft == null) { throw new SchedulerException( "Based on configured schedule, the given trigger will never fire."); } } } resources.getJobStore().storeJobsAndTriggers(triggersAndJobs, replace); notifySchedulerThread(0L); for (JobDetail job : triggersAndJobs.keySet()) { notifySchedulerListenersJobAdded(job); Set triggers = triggersAndJobs.get(job); for (Trigger trigger : triggers) { notifySchedulerListenersScheduled(trigger); } } } public void scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace) throws SchedulerException { Map> triggersAndJobs = new HashMap<>(); triggersAndJobs.put(jobDetail, triggersForJob); scheduleJobs(triggersAndJobs, replace); } public boolean unscheduleJobs(List triggerKeys) throws SchedulerException { validateState(); boolean result; result = resources.getJobStore().removeTriggers(triggerKeys); notifySchedulerThread(0L); for(TriggerKey key: triggerKeys) notifySchedulerListenersUnscheduled(key); return result; } /** *

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

*/ public boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException { validateState(); if (resources.getJobStore().removeTrigger(triggerKey)) { notifySchedulerThread(0L); notifySchedulerListenersUnscheduled(triggerKey); } else { return false; } return true; } /** *

* Remove (delete) the {@link org.quartz.Trigger} with the * given name, and store the new given one - which must be associated * with the same job. *

* @param newTrigger * The new Trigger to be stored. * * @return null if a Trigger with the given * name and group was not found and removed from the store, otherwise * the first fire time of the newly scheduled trigger. */ public Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException { validateState(); if (triggerKey == null) { throw new IllegalArgumentException("triggerKey cannot be null"); } if (newTrigger == null) { throw new IllegalArgumentException("newTrigger cannot be null"); } OperableTrigger trig = (OperableTrigger)newTrigger; Trigger oldTrigger = getTrigger(triggerKey); if (oldTrigger == null) { return null; } else { trig.setJobKey(oldTrigger.getJobKey()); } trig.validate(); Calendar cal = null; if (newTrigger.getCalendarName() != null) { cal = resources.getJobStore().retrieveCalendar( newTrigger.getCalendarName()); } Date ft = trig.computeFirstFireTime(cal); if (ft == null) { throw new SchedulerException( "Based on configured schedule, the given trigger will never fire."); } if (resources.getJobStore().replaceTrigger(triggerKey, trig)) { notifySchedulerThread(newTrigger.getNextFireTime().getTime()); notifySchedulerListenersUnscheduled(triggerKey); notifySchedulerListenersScheduled(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 org.quartz.Job} (execute it * now) - with a non-volatile trigger. *

*/ @SuppressWarnings("deprecation") public void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException { validateState(); OperableTrigger trig = (OperableTrigger) newTrigger().withIdentity(newTriggerId(), Scheduler.DEFAULT_GROUP).forJob(jobKey).build(); trig.computeFirstFireTime(null); if(data != null) { trig.setJobDataMap(data); } boolean collision = true; while (collision) { try { resources.getJobStore().storeTrigger(trig, false); collision = false; } catch (ObjectAlreadyExistsException oaee) { trig.setKey(new TriggerKey(newTriggerId(), Scheduler.DEFAULT_GROUP)); } } notifySchedulerThread(trig.getNextFireTime().getTime()); notifySchedulerListenersScheduled(trig); } /** *

* Store and schedule the identified {@link org.quartz.spi.OperableTrigger} *

*/ public void triggerJob(OperableTrigger trig) throws SchedulerException { validateState(); trig.computeFirstFireTime(null); boolean collision = true; while (collision) { try { resources.getJobStore().storeTrigger(trig, false); collision = false; } catch (ObjectAlreadyExistsException oaee) { trig.setKey(new TriggerKey(newTriggerId(), Scheduler.DEFAULT_GROUP)); } } notifySchedulerThread(trig.getNextFireTime().getTime()); notifySchedulerListenersScheduled(trig); } /** *

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

* */ public void pauseTrigger(TriggerKey triggerKey) throws SchedulerException { validateState(); resources.getJobStore().pauseTrigger(triggerKey); notifySchedulerThread(0L); notifySchedulerListenersPausedTrigger(triggerKey); } /** *

* Pause all of the {@link Trigger}s in the matching groups. *

* */ public void pauseTriggers(GroupMatcher matcher) throws SchedulerException { validateState(); if(matcher == null) { matcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } Collection pausedGroups = resources.getJobStore().pauseTriggers(matcher); notifySchedulerThread(0L); for (String pausedGroup : pausedGroups) { notifySchedulerListenersPausedTriggers(pausedGroup); } } /** *

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

* */ public void pauseJob(JobKey jobKey) throws SchedulerException { validateState(); resources.getJobStore().pauseJob(jobKey); notifySchedulerThread(0L); notifySchedulerListenersPausedJob(jobKey); } /** *

* Pause all of the {@link org.quartz.JobDetail}s in the * matching groups - by pausing all of their Triggers. *

* */ public void pauseJobs(GroupMatcher groupMatcher) throws SchedulerException { validateState(); if(groupMatcher == null) { groupMatcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } Collection pausedGroups = resources.getJobStore().pauseJobs(groupMatcher); notifySchedulerThread(0L); for (String pausedGroup : pausedGroups) { notifySchedulerListenersPausedJobs(pausedGroup); } } /** *

* 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(TriggerKey triggerKey) throws SchedulerException { validateState(); resources.getJobStore().resumeTrigger(triggerKey); notifySchedulerThread(0L); notifySchedulerListenersResumedTrigger(triggerKey); } /** *

* Resume (un-pause) all of the {@link Trigger}s in the * matching groups. *

* *

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

* */ public void resumeTriggers(GroupMatcher matcher) throws SchedulerException { validateState(); if(matcher == null) { matcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } Collection pausedGroups = resources.getJobStore().resumeTriggers(matcher); notifySchedulerThread(0L); for (String pausedGroup : pausedGroups) { notifySchedulerListenersResumedTriggers(pausedGroup); } } public Set getPausedTriggerGroups() throws SchedulerException { return resources.getJobStore().getPausedTriggerGroups(); } /** *

* Resume (un-pause) the {@link 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(JobKey jobKey) throws SchedulerException { validateState(); resources.getJobStore().resumeJob(jobKey); notifySchedulerThread(0L); notifySchedulerListenersResumedJob(jobKey); } /** *

* Resume (un-pause) all of the {@link org.quartz.JobDetail}s * in the matching groups. *

* *

* 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 resumeJobs(GroupMatcher matcher) throws SchedulerException { validateState(); if(matcher == null) { matcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } Collection resumedGroups = resources.getJobStore().resumeJobs(matcher); notifySchedulerThread(0L); for (String pausedGroup : resumedGroups) { notifySchedulerListenersResumedJobs(pausedGroup); } } /** *

* Pause all triggers - equivalent of calling pauseTriggers(GroupMatcher) * with a matcher matching all known groups. *

* *

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

* * @see #resumeAll() * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) * @see #standby() */ public void pauseAll() throws SchedulerException { validateState(); resources.getJobStore().pauseAll(); notifySchedulerThread(0L); notifySchedulerListenersPausedTriggers(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() */ public void resumeAll() throws SchedulerException { validateState(); resources.getJobStore().resumeAll(); notifySchedulerThread(0L); notifySchedulerListenersResumedTrigger(null); } /** *

* Get the names of all known {@link org.quartz.Job} groups. *

*/ public List getJobGroupNames() throws SchedulerException { validateState(); return resources.getJobStore().getJobGroupNames(); } /** *

* Get the names of all the {@link org.quartz.Job}s in the * matching groups. *

*/ public Set getJobKeys(GroupMatcher matcher) throws SchedulerException { validateState(); if(matcher == null) { matcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } return resources.getJobStore().getJobKeys(matcher); } /** *

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

*/ public List getTriggersOfJob(JobKey jobKey) throws SchedulerException { validateState(); return resources.getJobStore().getTriggersForJob(jobKey); } /** *

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

*/ public List getTriggerGroupNames() throws SchedulerException { validateState(); return resources.getJobStore().getTriggerGroupNames(); } /** *

* Get the names of all the {@link org.quartz.Trigger}s in * the matching groups. *

*/ public Set getTriggerKeys(GroupMatcher matcher) throws SchedulerException { validateState(); if(matcher == null) { matcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } return resources.getJobStore().getTriggerKeys(matcher); } /** *

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

*/ public JobDetail getJobDetail(JobKey jobKey) throws SchedulerException { validateState(); return resources.getJobStore().retrieveJob(jobKey); } public List getJobDetails(GroupMatcher matcher) throws SchedulerException { validateState(); if(matcher == null) { matcher = GroupMatcher.groupEquals(Scheduler.DEFAULT_GROUP); } return resources.getJobStore().getJobDetails(matcher); } /** *

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

*/ public Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException { validateState(); return resources.getJobStore().retrieveTrigger(triggerKey); } /** * Determine whether a {@link Job} with the given identifier already * exists within the scheduler. * * @param jobKey the identifier to check for * @return true if a Job exists with the given identifier * @throws SchedulerException */ public boolean checkExists(JobKey jobKey) throws SchedulerException { validateState(); return resources.getJobStore().checkExists(jobKey); } /** * Determine whether a {@link Trigger} with the given identifier already * exists within the scheduler. * * @param triggerKey the identifier to check for * @return true if a Trigger exists with the given identifier * @throws SchedulerException */ public boolean checkExists(TriggerKey triggerKey) throws SchedulerException { validateState(); return resources.getJobStore().checkExists(triggerKey); } /** * Clears (deletes!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws SchedulerException */ public void clear() throws SchedulerException { validateState(); resources.getJobStore().clearAllSchedulingData(); notifySchedulerListenersUnscheduled(null); } /** *

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

J * * @see TriggerState */ public TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException { validateState(); return resources.getJobStore().getTriggerState(triggerKey); } public void resetTriggerFromErrorState(TriggerKey triggerKey) throws SchedulerException { validateState(); resources.getJobStore().resetTriggerFromErrorState(triggerKey); } /** *

* 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(String calName, Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException { validateState(); resources.getJobStore().storeCalendar(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(String calName) throws SchedulerException { validateState(); return resources.getJobStore().removeCalendar(calName); } /** *

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

*/ public Calendar getCalendar(String calName) throws SchedulerException { validateState(); return resources.getJobStore().retrieveCalendar(calName); } /** *

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

*/ public List getCalendarNames() throws SchedulerException { validateState(); return resources.getJobStore().getCalendarNames(); } public ListenerManager getListenerManager() { return listenerManager; } /** *

* Add the given {@link org.quartz.JobListener} to the * Scheduler's internal list. *

*/ public void addInternalJobListener(JobListener jobListener) { if (jobListener.getName() == null || jobListener.getName().isEmpty()) { throw new IllegalArgumentException( "JobListener name cannot be empty."); } synchronized (internalJobListeners) { internalJobListeners.put(jobListener.getName(), jobListener); } } /** *

* Remove the identified {@link JobListener} from the Scheduler's * list of internal listeners. *

* * @return true if the identified listener was found in the list, and * removed. */ public boolean removeInternalJobListener(String name) { synchronized (internalJobListeners) { return (internalJobListeners.remove(name) != null); } } /** *

* Get a List containing all of the {@link org.quartz.JobListener}s * in the Scheduler's internal list. *

*/ public List getInternalJobListeners() { synchronized (internalJobListeners) { return java.util.Collections.unmodifiableList(new LinkedList<>(internalJobListeners.values())); } } /** *

* Get the internal {@link org.quartz.JobListener} * that has the given name. *

*/ public JobListener getInternalJobListener(String name) { synchronized (internalJobListeners) { return internalJobListeners.get(name); } } /** *

* Add the given {@link org.quartz.TriggerListener} to the * Scheduler's internal list. *

*/ public void addInternalTriggerListener(TriggerListener triggerListener) { if (triggerListener.getName() == null || triggerListener.getName().isEmpty()) { throw new IllegalArgumentException( "TriggerListener name cannot be empty."); } synchronized (internalTriggerListeners) { internalTriggerListeners.put(triggerListener.getName(), triggerListener); } } /** *

* Remove the identified {@link TriggerListener} from the Scheduler's * list of internal listeners. *

* * @return true if the identified listener was found in the list, and * removed. */ public boolean removeinternalTriggerListener(String name) { synchronized (internalTriggerListeners) { return (internalTriggerListeners.remove(name) != null); } } /** *

* Get a list containing all of the {@link org.quartz.TriggerListener}s * in the Scheduler's internal list. *

*/ public List getInternalTriggerListeners() { synchronized (internalTriggerListeners) { return java.util.Collections.unmodifiableList(new LinkedList<>(internalTriggerListeners.values())); } } /** *

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

*/ public TriggerListener getInternalTriggerListener(String name) { synchronized (internalTriggerListeners) { return internalTriggerListeners.get(name); } } /** *

* Register the given {@link SchedulerListener} with the * Scheduler's list of internal listeners. *

*/ public void addInternalSchedulerListener(SchedulerListener schedulerListener) { synchronized (internalSchedulerListeners) { internalSchedulerListeners.add(schedulerListener); } } /** *

* Remove the given {@link SchedulerListener} from the * Scheduler's list of internal listeners. *

* * @return true if the identified listener was found in the list, and * removed. */ public boolean removeInternalSchedulerListener(SchedulerListener schedulerListener) { synchronized (internalSchedulerListeners) { return internalSchedulerListeners.remove(schedulerListener); } } /** *

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

*/ public List getInternalSchedulerListeners() { synchronized (internalSchedulerListeners) { return java.util.Collections.unmodifiableList(new ArrayList<>(internalSchedulerListeners)); } } protected void notifyJobStoreJobComplete(OperableTrigger trigger, JobDetail detail, CompletedExecutionInstruction instCode) { resources.getJobStore().triggeredJobComplete(trigger, detail, instCode); } protected void notifyJobStoreJobVetoed(OperableTrigger trigger, JobDetail detail, CompletedExecutionInstruction instCode) { resources.getJobStore().triggeredJobComplete(trigger, detail, instCode); } protected void notifySchedulerThread(long candidateNewNextFireTime) { if (isSignalOnSchedulingChange()) { signaler.signalSchedulingChange(candidateNewNextFireTime); } } private List buildTriggerListenerList() throws SchedulerException { List allListeners = new LinkedList<>(); allListeners.addAll(getListenerManager().getTriggerListeners()); allListeners.addAll(getInternalTriggerListeners()); return allListeners; } private List buildJobListenerList() throws SchedulerException { List allListeners = new LinkedList<>(); allListeners.addAll(getListenerManager().getJobListeners()); allListeners.addAll(getInternalJobListeners()); return allListeners; } private List buildSchedulerListenerList() { List allListeners = new LinkedList<>(); allListeners.addAll(getListenerManager().getSchedulerListeners()); allListeners.addAll(getInternalSchedulerListeners()); return allListeners; } private boolean matchJobListener(JobListener listener, JobKey key) { List> matchers = getListenerManager().getJobListenerMatchers(listener.getName()); if(matchers == null) return true; for(Matcher matcher: matchers) { if(matcher.isMatch(key)) return true; } return false; } private boolean matchTriggerListener(TriggerListener listener, TriggerKey key) { List> matchers = getListenerManager().getTriggerListenerMatchers(listener.getName()); if(matchers == null) return true; for(Matcher matcher: matchers) { if(matcher.isMatch(key)) return true; } return false; } public boolean notifyTriggerListenersFired(JobExecutionContext jec) throws SchedulerException { boolean vetoedExecution = false; // build a list of all trigger listeners that are to be notified... List triggerListeners = buildTriggerListenerList(); // notify all trigger listeners in the list for(TriggerListener tl: triggerListeners) { try { if(!matchTriggerListener(tl, jec.getTrigger().getKey())) continue; tl.triggerFired(jec.getTrigger(), jec); if(tl.vetoJobExecution(jec.getTrigger(), jec)) { vetoedExecution = true; } } catch (Exception e) { throw new JobExecutionProcessException(tl, jec, e); } } return vetoedExecution; } public void notifyTriggerListenersMisfired(Trigger trigger) throws SchedulerException { // build a list of all trigger listeners that are to be notified... List triggerListeners = buildTriggerListenerList(); // notify all trigger listeners in the list for(TriggerListener tl: triggerListeners) { try { if(!matchTriggerListener(tl, trigger.getKey())) continue; tl.triggerMisfired(trigger); } catch (Exception e) { throw new SchedulerException( "TriggerListener '" + tl.getName() + "' threw exception: " + e.getMessage(), e); } } } public void notifyTriggerListenersComplete(JobExecutionContext jec, CompletedExecutionInstruction instCode) throws SchedulerException { // build a list of all trigger listeners that are to be notified... List triggerListeners = buildTriggerListenerList(); // notify all trigger listeners in the list for(TriggerListener tl: triggerListeners) { try { if(!matchTriggerListener(tl, jec.getTrigger().getKey())) continue; tl.triggerComplete(jec.getTrigger(), jec, instCode); } catch (Exception e) { throw new JobExecutionProcessException(tl, jec, e); } } } public void notifyJobListenersToBeExecuted(JobExecutionContext jec) throws SchedulerException { // build a list of all job listeners that are to be notified... List jobListeners = buildJobListenerList(); // notify all job listeners for(JobListener jl: jobListeners) { try { if(!matchJobListener(jl, jec.getJobDetail().getKey())) continue; jl.jobToBeExecuted(jec); } catch (Exception e) { throw new JobExecutionProcessException(jl, jec, e); } } } public void notifyJobListenersWasVetoed(JobExecutionContext jec) throws SchedulerException { // build a list of all job listeners that are to be notified... List jobListeners = buildJobListenerList(); // notify all job listeners for(JobListener jl: jobListeners) { try { if(!matchJobListener(jl, jec.getJobDetail().getKey())) continue; jl.jobExecutionVetoed(jec); } catch (Exception e) { throw new JobExecutionProcessException(jl, jec, e); } } } public void notifyJobListenersWasExecuted(JobExecutionContext jec, JobExecutionException je) throws SchedulerException { // build a list of all job listeners that are to be notified... List jobListeners = buildJobListenerList(); // notify all job listeners for(JobListener jl: jobListeners) { try { if(!matchJobListener(jl, jec.getJobDetail().getKey())) continue; jl.jobWasExecuted(jec, je); } catch (Exception e) { throw new JobExecutionProcessException(jl, jec, e); } } } public void notifySchedulerListenersError(String msg, SchedulerException se) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { 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 notifySchedulerListenersScheduled(Trigger trigger) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobScheduled(trigger); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of scheduled job. Trigger={}", trigger.getKey(), e); } } } public void notifySchedulerListenersUnscheduled(TriggerKey triggerKey) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { if(triggerKey == null) sl.schedulingDataCleared(); else sl.jobUnscheduled(triggerKey); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of unscheduled job. Trigger={}", triggerKey == null ? "ALL DATA" : triggerKey, e); } } } public void notifySchedulerListenersFinalized(Trigger trigger) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.triggerFinalized(trigger); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of finalized trigger. Trigger={}", trigger.getKey(), e); } } } public void notifySchedulerListenersPausedTrigger(TriggerKey triggerKey) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.triggerPaused(triggerKey); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of paused trigger: {}", triggerKey, e); } } } public void notifySchedulerListenersPausedTriggers(String group) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.triggersPaused(group); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of paused trigger group.{}", group, e); } } } public void notifySchedulerListenersResumedTrigger(TriggerKey key) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.triggerResumed(key); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of resumed trigger: {}", key, e); } } } public void notifySchedulerListenersResumedTriggers(String group) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.triggersResumed(group); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of resumed group: {}", group, e); } } } public void notifySchedulerListenersPausedJob(JobKey key) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobPaused(key); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of paused job: {}", key, e); } } } public void notifySchedulerListenersPausedJobs(String group) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobsPaused(group); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of paused job group: {}", group, e); } } } public void notifySchedulerListenersResumedJob(JobKey key) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobResumed(key); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of resumed job: {}", key, e); } } } public void notifySchedulerListenersResumedJobs(String group) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobsResumed(group); } catch (Exception e) { getLog().error("Error while notifying SchedulerListener of resumed job group: {}", group, e); } } } public void notifySchedulerListenersInStandbyMode() { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.schedulerInStandbyMode(); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of inStandByMode.", e); } } } public void notifySchedulerListenersStarted() { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.schedulerStarted(); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of startup.", e); } } } public void notifySchedulerListenersStarting() { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for (SchedulerListener sl : schedListeners) { try { sl.schedulerStarting(); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of startup.", e); } } } public void notifySchedulerListenersShutdown() { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.schedulerShutdown(); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of shutdown.", e); } } } public void notifySchedulerListenersShuttingdown() { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.schedulerShuttingdown(); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of shutdown.", e); } } } public void notifySchedulerListenersJobAdded(JobDetail jobDetail) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobAdded(jobDetail); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of JobAdded.", e); } } } public void notifySchedulerListenersJobDeleted(JobKey jobKey) { // build a list of all scheduler listeners that are to be notified... List schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobDeleted(jobKey); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of JobAdded.", 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 org.quartz.core.RemotableQuartzScheduler#interrupt(JobKey) */ public boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException { List jobs = getCurrentlyExecutingJobs(); JobDetail jobDetail; Job job; boolean interrupted = false; for(JobExecutionContext jec : jobs) { jobDetail = jec.getJobDetail(); if (jobKey.equals(jobDetail.getKey())) { job = jec.getJobInstance(); if (job instanceof InterruptableJob) { ((InterruptableJob)job).interrupt(); interrupted = true; } else { throw new UnableToInterruptJobException( "Job " + jobDetail.getKey() + " can not be interrupted, since it does not implement " + InterruptableJob.class.getName()); } } } return interrupted; } /** * Interrupt 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 org.quartz.core.RemotableQuartzScheduler#interrupt(JobKey) */ public boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException { List jobs = getCurrentlyExecutingJobs(); Job job; for(JobExecutionContext jec : jobs) { if (jec.getFireInstanceId().equals(fireInstanceId)) { job = jec.getJobInstance(); if (job instanceof InterruptableJob) { ((InterruptableJob)job).interrupt(); return true; } else { throw new UnableToInterruptJobException( "Job " + jec.getJobDetail().getKey() + " can not be interrupted, since it does not implement " + InterruptableJob.class.getName()); } } } return false; } private void shutdownPlugins() { for (SchedulerPlugin plugin : resources.getSchedulerPlugins()) { plugin.shutdown(); } } private void startPlugins() { for (SchedulerPlugin plugin : resources.getSchedulerPlugins()) { plugin.start(); } } } ///////////////////////////////////////////////////////////////////////////// // // ErrorLogger - Scheduler Listener Class // ///////////////////////////////////////////////////////////////////////////// class ErrorLogger extends SchedulerListenerSupport { ErrorLogger() { } @Override public void schedulerError(String msg, SchedulerException cause) { getLog().error(msg, cause); } } ///////////////////////////////////////////////////////////////////////////// // // ExecutingJobsManager - Job Listener Class // ///////////////////////////////////////////////////////////////////////////// class ExecutingJobsManager implements JobListener { final HashMap executingJobs = new HashMap<>(); final AtomicInteger numJobsFired = new AtomicInteger(0); ExecutingJobsManager() { } public String getName() { return getClass().getName(); } public int getNumJobsCurrentlyExecuting() { synchronized (executingJobs) { return executingJobs.size(); } } public void jobToBeExecuted(JobExecutionContext context) { numJobsFired.incrementAndGet(); synchronized (executingJobs) { executingJobs .put(((OperableTrigger)context.getTrigger()).getFireInstanceId(), context); } } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { synchronized (executingJobs) { executingJobs.remove(((OperableTrigger)context.getTrigger()).getFireInstanceId()); } } public int getNumJobsFired() { return numJobsFired.get(); } public List getExecutingJobs() { synchronized (executingJobs) { return java.util.Collections.unmodifiableList(new ArrayList<>( executingJobs.values())); } } public void jobExecutionVetoed(JobExecutionContext context) { } } ================================================ FILE: quartz/src/main/java/org/quartz/core/QuartzSchedulerMBeanImpl.java ================================================ package org.quartz.core; import static org.quartz.JobKey.jobKey; import static org.quartz.TriggerKey.triggerKey; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.MethodDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import javax.management.ListenerNotFoundException; import javax.management.MBeanNotificationInfo; import javax.management.NotCompliantMBeanException; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.StandardMBean; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.JobListener; import org.quartz.SchedulerException; import org.quartz.SchedulerListener; import org.quartz.Trigger; import org.quartz.Trigger.TriggerState; import org.quartz.TriggerKey; import org.quartz.core.jmx.JobDetailSupport; import org.quartz.core.jmx.JobExecutionContextSupport; import org.quartz.core.jmx.QuartzSchedulerMBean; import org.quartz.core.jmx.TriggerSupport; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.triggers.AbstractTrigger; import org.quartz.spi.OperableTrigger; public class QuartzSchedulerMBeanImpl extends StandardMBean implements NotificationEmitter, QuartzSchedulerMBean, JobListener, SchedulerListener { private static final MBeanNotificationInfo[] NOTIFICATION_INFO; private final QuartzScheduler scheduler; private boolean sampledStatisticsEnabled; private SampledStatistics sampledStatistics; private final static SampledStatistics NULL_SAMPLED_STATISTICS = new NullSampledStatisticsImpl(); static { final String[] notificationTypes = new String[] { SCHEDULER_STARTED, SCHEDULER_PAUSED, SCHEDULER_SHUTDOWN, }; final String name = Notification.class.getName(); final String description = "QuartzScheduler JMX Event"; NOTIFICATION_INFO = new MBeanNotificationInfo[] { new MBeanNotificationInfo( notificationTypes, name, description), }; } /** * emitter */ protected final Emitter emitter = new Emitter(); /** * sequenceNumber */ protected final AtomicLong sequenceNumber = new AtomicLong(); /** * QuartzSchedulerMBeanImpl * * @throws NotCompliantMBeanException */ protected QuartzSchedulerMBeanImpl(QuartzScheduler scheduler) throws NotCompliantMBeanException { super(QuartzSchedulerMBean.class); this.scheduler = scheduler; this.scheduler.addInternalJobListener(this); this.scheduler.addInternalSchedulerListener(this); this.sampledStatistics = NULL_SAMPLED_STATISTICS; this.sampledStatisticsEnabled = false; } public TabularData getCurrentlyExecutingJobs() throws Exception { try { List currentlyExecutingJobs = scheduler.getCurrentlyExecutingJobs(); return JobExecutionContextSupport.toTabularData(currentlyExecutingJobs); } catch (Exception e) { throw newPlainException(e); } } public TabularData getAllJobDetails() throws Exception { try { List detailList = new ArrayList<>(); for (String jobGroupName : scheduler.getJobGroupNames()) { for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(jobGroupName))) { detailList.add(scheduler.getJobDetail(jobKey)); } } return JobDetailSupport.toTabularData(detailList.toArray(new JobDetail[detailList.size()])); } catch (Exception e) { throw newPlainException(e); } } public List getAllTriggers() throws Exception { try { List triggerList = new ArrayList<>(); for (String triggerGroupName : scheduler.getTriggerGroupNames()) { for (TriggerKey triggerKey : scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroupName))) { triggerList.add(scheduler.getTrigger(triggerKey)); } } return TriggerSupport.toCompositeList(triggerList); } catch (Exception e) { throw newPlainException(e); } } public void addJob(CompositeData jobDetail, boolean replace) throws Exception { try { scheduler.addJob(JobDetailSupport.newJobDetail(jobDetail), replace); } catch (Exception e) { throw newPlainException(e); } } private static void invokeSetter(Object target, String attribute, Object value) throws Exception { String setterName = "set" + Character.toUpperCase(attribute.charAt(0)) + attribute.substring(1); Class[] argTypes = {value.getClass()}; Method setter = findMethod(target.getClass(), setterName, argTypes); if(setter != null) { setter.invoke(target, value); } else { throw new Exception("Unable to find setter for attribute '" + attribute + "' and value '" + value + "'"); } } private static Class getWrapperIfPrimitive(Class c) { Class result = c; try { Field f = c.getField("TYPE"); f.setAccessible(true); result = (Class) f.get(null); } catch (Exception e) { /**/ } return result; } private static Method findMethod(Class targetType, String methodName, Class[] argTypes) throws IntrospectionException { BeanInfo beanInfo = Introspector.getBeanInfo(targetType); if (beanInfo != null) { for(MethodDescriptor methodDesc: beanInfo.getMethodDescriptors()) { Method method = methodDesc.getMethod(); Class[] parameterTypes = method.getParameterTypes(); if (methodName.equals(method.getName()) && argTypes.length == parameterTypes.length) { boolean matchedArgTypes = true; for (int i = 0; i < argTypes.length; i++) { if (getWrapperIfPrimitive(argTypes[i]) != parameterTypes[i]) { matchedArgTypes = false; break; } } if (matchedArgTypes) { return method; } } } } return null; } public void scheduleBasicJob(Map jobDetailInfo, Map triggerInfo) throws Exception { try { JobDetail jobDetail = JobDetailSupport.newJobDetail(jobDetailInfo); OperableTrigger trigger = TriggerSupport.newTrigger(triggerInfo); scheduler.deleteJob(jobDetail.getKey()); scheduler.scheduleJob(jobDetail, trigger); } catch (ParseException pe) { throw pe; } catch (Exception e) { throw newPlainException(e); } } public void scheduleJob(Map abstractJobInfo, Map abstractTriggerInfo) throws Exception { try { String triggerClassName = (String) abstractTriggerInfo.remove("triggerClass"); if(triggerClassName == null) { throw new IllegalArgumentException("No triggerClass specified"); } Class triggerClass = Class.forName(triggerClassName); Trigger trigger = (Trigger) triggerClass.getDeclaredConstructor().newInstance(); String jobDetailClassName = (String) abstractJobInfo.remove("jobDetailClass"); if(jobDetailClassName == null) { throw new IllegalArgumentException("No jobDetailClass specified"); } Class jobDetailClass = Class.forName(jobDetailClassName); JobDetail jobDetail = (JobDetail) jobDetailClass.getDeclaredConstructor().newInstance(); String jobClassName = (String) abstractJobInfo.remove("jobClass"); if(jobClassName == null) { throw new IllegalArgumentException("No jobClass specified"); } Class jobClass = Class.forName(jobClassName); abstractJobInfo.put("jobClass", jobClass); for(Map.Entry entry : abstractTriggerInfo.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if("jobDataMap".equals(key)) { value = new JobDataMap((Map)value); } invokeSetter(trigger, key, value); } for(Map.Entry entry : abstractJobInfo.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if("jobDataMap".equals(key)) { value = new JobDataMap((Map)value); } invokeSetter(jobDetail, key, value); } AbstractTrigger at = (AbstractTrigger)trigger; at.setKey(new TriggerKey(at.getName(), at.getGroup())); Date startDate = at.getStartTime(); if(startDate == null || startDate.before(new Date())) { at.setStartTime(new Date()); } scheduler.deleteJob(jobDetail.getKey()); scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { throw newPlainException(e); } } public void scheduleJob(String jobName, String jobGroup, Map abstractTriggerInfo) throws Exception { try { JobKey jobKey = new JobKey(jobName, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if(jobDetail == null) { throw new IllegalArgumentException("No such job '" + jobKey + "'"); } String triggerClassName = (String) abstractTriggerInfo.remove("triggerClass"); if(triggerClassName == null) { throw new IllegalArgumentException("No triggerClass specified"); } Class triggerClass = Class.forName(triggerClassName); Trigger trigger = (Trigger) triggerClass.getDeclaredConstructor().newInstance(); for(Map.Entry entry : abstractTriggerInfo.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if("jobDataMap".equals(key)) { value = new JobDataMap((Map)value); } invokeSetter(trigger, key, value); } AbstractTrigger at = (AbstractTrigger)trigger; at.setKey(new TriggerKey(at.getName(), at.getGroup())); Date startDate = at.getStartTime(); if(startDate == null || startDate.before(new Date())) { at.setStartTime(new Date()); } scheduler.scheduleJob(trigger); } catch (Exception e) { throw newPlainException(e); } } public void addJob(Map abstractJobInfo, boolean replace) throws Exception { try { String jobDetailClassName = (String) abstractJobInfo.remove("jobDetailClass"); if(jobDetailClassName == null) { throw new IllegalArgumentException("No jobDetailClass specified"); } Class jobDetailClass = Class.forName(jobDetailClassName); JobDetail jobDetail = (JobDetail) jobDetailClass.getDeclaredConstructor().newInstance(); String jobClassName = (String) abstractJobInfo.remove("jobClass"); if(jobClassName == null) { throw new IllegalArgumentException("No jobClass specified"); } Class jobClass = Class.forName(jobClassName); abstractJobInfo.put("jobClass", jobClass); for(Map.Entry entry : abstractJobInfo.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if("jobDataMap".equals(key)) { value = new JobDataMap((Map)value); } invokeSetter(jobDetail, key, value); } scheduler.addJob(jobDetail, replace); } catch (Exception e) { throw newPlainException(e); } } private Exception newPlainException(Exception e) { String type = e.getClass().getName(); if(type.startsWith("java.") || type.startsWith("javax.")) { return e; } else { Exception result = new Exception(e.getMessage()); result.setStackTrace(e.getStackTrace()); return result; } } public void deleteCalendar(String calendarName) throws Exception { try { scheduler.deleteCalendar(calendarName); } catch(Exception e) { throw newPlainException(e); } } public boolean deleteJob(String jobName, String jobGroupName) throws Exception { try { return scheduler.deleteJob(jobKey(jobName, jobGroupName)); } catch (Exception e) { throw newPlainException(e); } } public List getCalendarNames() throws Exception { try { return scheduler.getCalendarNames(); } catch (Exception e) { throw newPlainException(e); } } public CompositeData getJobDetail(String jobName, String jobGroupName) throws Exception { try { JobDetail jobDetail = scheduler.getJobDetail(jobKey(jobName, jobGroupName)); return JobDetailSupport.toCompositeData(jobDetail); } catch (Exception e) { throw newPlainException(e); } } public List getJobGroupNames() throws Exception { try { return scheduler.getJobGroupNames(); } catch (Exception e) { throw newPlainException(e); } } public List getJobNames(String groupName) throws Exception { try { List jobNames = new ArrayList<>(); for(JobKey key: scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { jobNames.add(key.getName()); } return jobNames; } catch (Exception e) { throw newPlainException(e); } } public String getJobStoreClassName() { return scheduler.getJobStoreClass().getName(); } public Set getPausedTriggerGroups() throws Exception { try { return scheduler.getPausedTriggerGroups(); } catch (Exception e) { throw newPlainException(e); } } public CompositeData getTrigger(String name, String groupName) throws Exception { try { Trigger trigger = scheduler.getTrigger(triggerKey(name, groupName)); return TriggerSupport.toCompositeData(trigger); } catch (Exception e) { throw newPlainException(e); } } public List getTriggerGroupNames() throws Exception { try { return scheduler.getTriggerGroupNames(); } catch (Exception e) { throw newPlainException(e); } } public List getTriggerNames(String groupName) throws Exception { try { List triggerNames = new ArrayList<>(); for(TriggerKey key: scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(groupName))) { triggerNames.add(key.getName()); } return triggerNames; } catch (Exception e) { throw newPlainException(e); } } public String getTriggerState(String triggerName, String triggerGroupName) throws Exception { try { TriggerKey triggerKey = triggerKey(triggerName, triggerGroupName); TriggerState ts = scheduler.getTriggerState(triggerKey); return ts.name(); } catch (Exception e) { throw newPlainException(e); } } public List getTriggersOfJob(String jobName, String jobGroupName) throws Exception { try { JobKey jobKey = jobKey(jobName, jobGroupName); return TriggerSupport.toCompositeList(scheduler.getTriggersOfJob(jobKey)); } catch (Exception e) { throw newPlainException(e); } } public boolean interruptJob(String jobName, String jobGroupName) throws Exception { try { return scheduler.interrupt(jobKey(jobName, jobGroupName)); } catch (Exception e) { throw newPlainException(e); } } public boolean interruptJob(String fireInstanceId) throws Exception { try { return scheduler.interrupt(fireInstanceId); } catch (Exception e) { throw newPlainException(e); } } public Date scheduleJob(String jobName, String jobGroup, String triggerName, String triggerGroup) throws Exception { try { JobKey jobKey = jobKey(jobName, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null) { throw new IllegalArgumentException("No such job: " + jobKey); } TriggerKey triggerKey = triggerKey(triggerName, triggerGroup); Trigger trigger = scheduler.getTrigger(triggerKey); if (trigger == null) { throw new IllegalArgumentException("No such trigger: " + triggerKey); } return scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { throw newPlainException(e); } } public boolean unscheduleJob(String triggerName, String triggerGroup) throws Exception { try { return scheduler.unscheduleJob(triggerKey(triggerName, triggerGroup)); } catch (Exception e) { throw newPlainException(e); } } public void clear() throws Exception { try { scheduler.clear(); } catch (Exception e) { throw newPlainException(e); } } public String getVersion() { return scheduler.getVersion(); } public boolean isShutdown() { return scheduler.isShutdown(); } public boolean isStarted() { return scheduler.isStarted(); } public void start() throws Exception { try { scheduler.start(); } catch (Exception e) { throw newPlainException(e); } } public void shutdown() { scheduler.shutdown(); } public void standby() { scheduler.standby(); } public boolean isStandbyMode() { return scheduler.isInStandbyMode(); } public String getSchedulerName() { return scheduler.getSchedulerName(); } public String getSchedulerInstanceId() { return scheduler.getSchedulerInstanceId(); } public String getThreadPoolClassName() { return scheduler.getThreadPoolClass().getName(); } public int getThreadPoolSize() { return scheduler.getThreadPoolSize(); } public void pauseJob(String jobName, String jobGroup) throws Exception { try { scheduler.pauseJob(jobKey(jobName, jobGroup)); } catch (Exception e) { throw newPlainException(e); } } public void pauseJobs(GroupMatcher matcher) throws Exception { try { scheduler.pauseJobs(matcher); } catch (Exception e) { throw newPlainException(e); } } public void pauseJobGroup(String jobGroup) throws Exception { pauseJobs(GroupMatcher.groupEquals(jobGroup)); } public void pauseJobsStartingWith(String jobGroupPrefix) throws Exception { pauseJobs(GroupMatcher.groupStartsWith(jobGroupPrefix)); } public void pauseJobsEndingWith(String jobGroupSuffix) throws Exception { pauseJobs(GroupMatcher.groupEndsWith(jobGroupSuffix)); } public void pauseJobsContaining(String jobGroupToken) throws Exception { pauseJobs(GroupMatcher.groupContains(jobGroupToken)); } public void pauseJobsAll() throws Exception { pauseJobs(GroupMatcher.anyJobGroup()); } public void pauseAllTriggers() throws Exception { try { scheduler.pauseAll(); } catch (Exception e) { throw newPlainException(e); } } private void pauseTriggers(GroupMatcher matcher) throws Exception { try { scheduler.pauseTriggers(matcher); } catch (Exception e) { throw newPlainException(e); } } public void pauseTriggerGroup(String triggerGroup) throws Exception { pauseTriggers(GroupMatcher.groupEquals(triggerGroup)); } public void pauseTriggersStartingWith(String triggerGroupPrefix) throws Exception { pauseTriggers(GroupMatcher.groupStartsWith(triggerGroupPrefix)); } public void pauseTriggersEndingWith(String triggerGroupSuffix) throws Exception { pauseTriggers(GroupMatcher.groupEndsWith(triggerGroupSuffix)); } public void pauseTriggersContaining(String triggerGroupToken) throws Exception { pauseTriggers(GroupMatcher.groupContains(triggerGroupToken)); } public void pauseTriggersAll() throws Exception { pauseTriggers(GroupMatcher.anyTriggerGroup()); } public void pauseTrigger(String triggerName, String triggerGroup) throws Exception { try { scheduler.pauseTrigger(triggerKey(triggerName, triggerGroup)); } catch (Exception e) { throw newPlainException(e); } } public void resumeAllTriggers() throws Exception { try { scheduler.resumeAll(); } catch (Exception e) { throw newPlainException(e); } } public void resumeJob(String jobName, String jobGroup) throws Exception { try { scheduler.resumeJob(jobKey(jobName, jobGroup)); } catch (Exception e) { throw newPlainException(e); } } public void resumeJobs(GroupMatcher matcher) throws Exception { try { scheduler.resumeJobs(matcher); } catch (Exception e) { throw newPlainException(e); } } public void resumeJobGroup(String jobGroup) throws Exception { resumeJobs(GroupMatcher.groupEquals(jobGroup)); } public void resumeJobsStartingWith(String jobGroupPrefix) throws Exception { resumeJobs(GroupMatcher.groupStartsWith(jobGroupPrefix)); } public void resumeJobsEndingWith(String jobGroupSuffix) throws Exception { resumeJobs(GroupMatcher.groupEndsWith(jobGroupSuffix)); } public void resumeJobsContaining(String jobGroupToken) throws Exception { resumeJobs(GroupMatcher.groupContains(jobGroupToken)); } public void resumeJobsAll() throws Exception { resumeJobs(GroupMatcher.anyJobGroup()); } public void resumeTrigger(String triggerName, String triggerGroup) throws Exception { try { scheduler.resumeTrigger(triggerKey(triggerName, triggerGroup)); } catch (Exception e) { throw newPlainException(e); } } private void resumeTriggers(GroupMatcher matcher) throws Exception { try { scheduler.resumeTriggers(matcher); } catch (Exception e) { throw newPlainException(e); } } public void resumeTriggerGroup(String triggerGroup) throws Exception { resumeTriggers(GroupMatcher.groupEquals(triggerGroup)); } public void resumeTriggersStartingWith(String triggerGroupPrefix) throws Exception { resumeTriggers(GroupMatcher.groupStartsWith(triggerGroupPrefix)); } public void resumeTriggersEndingWith(String triggerGroupSuffix) throws Exception { resumeTriggers(GroupMatcher.groupEndsWith(triggerGroupSuffix)); } public void resumeTriggersContaining(String triggerGroupToken) throws Exception { resumeTriggers(GroupMatcher.groupContains(triggerGroupToken)); } public void resumeTriggersAll() throws Exception { resumeTriggers(GroupMatcher.anyTriggerGroup()); } public void triggerJob(String jobName, String jobGroup, Map jobDataMap) throws Exception { try { scheduler.triggerJob(jobKey(jobName, jobGroup), new JobDataMap(jobDataMap)); } catch (Exception e) { throw newPlainException(e); } } public void triggerJob(CompositeData trigger) throws Exception { try { scheduler.triggerJob(TriggerSupport.newTrigger(trigger)); } catch (Exception e) { throw newPlainException(e); } } // ScheduleListener public void jobAdded(JobDetail jobDetail) { sendNotification(JOB_ADDED, JobDetailSupport.toCompositeData(jobDetail)); } public void jobDeleted(JobKey jobKey) { Map map = new HashMap<>(); map.put("jobName", jobKey.getName()); map.put("jobGroup", jobKey.getGroup()); sendNotification(JOB_DELETED, map); } public void jobScheduled(Trigger trigger) { sendNotification(JOB_SCHEDULED, TriggerSupport.toCompositeData(trigger)); } public void jobUnscheduled(TriggerKey triggerKey) { Map map = new HashMap<>(); map.put("triggerName", triggerKey.getName()); map.put("triggerGroup", triggerKey.getGroup()); sendNotification(JOB_UNSCHEDULED, map); } public void schedulingDataCleared() { sendNotification(SCHEDULING_DATA_CLEARED); } public void jobPaused(JobKey jobKey) { Map map = new HashMap<>(); map.put("jobName", jobKey.getName()); map.put("jobGroup", jobKey.getGroup()); sendNotification(JOBS_PAUSED, map); } public void jobsPaused(String jobGroup) { Map map = new HashMap<>(); map.put("jobName", null); map.put("jobGroup", jobGroup); sendNotification(JOBS_PAUSED, map); } public void jobsResumed(String jobGroup) { Map map = new HashMap<>(); map.put("jobName", null); map.put("jobGroup", jobGroup); sendNotification(JOBS_RESUMED, map); } public void jobResumed(JobKey jobKey) { Map map = new HashMap<>(); map.put("jobName", jobKey.getName()); map.put("jobGroup", jobKey.getGroup()); sendNotification(JOBS_RESUMED, map); } public void schedulerError(String msg, SchedulerException cause) { sendNotification(SCHEDULER_ERROR, cause.getMessage()); } public void schedulerStarted() { sendNotification(SCHEDULER_STARTED); } //not doing anything, just like schedulerShuttingdown public void schedulerStarting() { } public void schedulerInStandbyMode() { sendNotification(SCHEDULER_PAUSED); } public void schedulerShutdown() { scheduler.removeInternalSchedulerListener(this); scheduler.removeInternalJobListener(getName()); sendNotification(SCHEDULER_SHUTDOWN); } public void schedulerShuttingdown() { } public void triggerFinalized(Trigger trigger) { Map map = new HashMap<>(); map.put("triggerName", trigger.getKey().getName()); map.put("triggerGroup", trigger.getKey().getGroup()); sendNotification(TRIGGER_FINALIZED, map); } public void triggersPaused(String triggerGroup) { Map map = new HashMap<>(); map.put("triggerName", null); map.put("triggerGroup", triggerGroup); sendNotification(TRIGGERS_PAUSED, map); } public void triggerPaused(TriggerKey triggerKey) { Map map = new HashMap<>(); if(triggerKey != null) { map.put("triggerName", triggerKey.getName()); map.put("triggerGroup", triggerKey.getGroup()); } sendNotification(TRIGGERS_PAUSED, map); } public void triggersResumed(String triggerGroup) { Map map = new HashMap<>(); map.put("triggerName", null); map.put("triggerGroup", triggerGroup); sendNotification(TRIGGERS_RESUMED, map); } public void triggerResumed(TriggerKey triggerKey) { Map map = new HashMap<>(); if(triggerKey != null) { map.put("triggerName", triggerKey.getName()); map.put("triggerGroup", triggerKey.getGroup()); } sendNotification(TRIGGERS_RESUMED, map); } // JobListener public String getName() { return "QuartzSchedulerMBeanImpl.listener"; } public void jobExecutionVetoed(JobExecutionContext context) { try { sendNotification(JOB_EXECUTION_VETOED, JobExecutionContextSupport .toCompositeData(context)); } catch (Exception e) { throw new RuntimeException(newPlainException(e)); } } public void jobToBeExecuted(JobExecutionContext context) { try { sendNotification(JOB_TO_BE_EXECUTED, JobExecutionContextSupport .toCompositeData(context)); } catch (Exception e) { throw new RuntimeException(newPlainException(e)); } } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { try { sendNotification(JOB_WAS_EXECUTED, JobExecutionContextSupport .toCompositeData(context)); } catch (Exception e) { throw new RuntimeException(newPlainException(e)); } } // NotificationBroadcaster /** * sendNotification * * @param eventType */ public void sendNotification(String eventType) { sendNotification(eventType, null, null); } /** * sendNotification * * @param eventType * @param data */ public void sendNotification(String eventType, Object data) { sendNotification(eventType, data, null); } /** * sendNotification * * @param eventType * @param data * @param msg */ public void sendNotification(String eventType, Object data, String msg) { Notification notification = new Notification(eventType, this, sequenceNumber .incrementAndGet(), System.currentTimeMillis(), msg); if (data != null) { notification.setUserData(data); } emitter.sendNotification(notification); } /** * @author gkeim */ private class Emitter extends NotificationBroadcasterSupport { /** * @see javax.management.NotificationBroadcasterSupport#getNotificationInfo() */ @Override public MBeanNotificationInfo[] getNotificationInfo() { return QuartzSchedulerMBeanImpl.this.getNotificationInfo(); } } /** * @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, * javax.management.NotificationFilter, java.lang.Object) */ public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object callBack) { emitter.addNotificationListener(listener, filter, callBack); } /** * @see javax.management.NotificationBroadcaster#getNotificationInfo() */ public MBeanNotificationInfo[] getNotificationInfo() { return NOTIFICATION_INFO; } /** * @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener) */ public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { emitter.removeNotificationListener(listener); } /** * @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener, * javax.management.NotificationFilter, java.lang.Object) */ public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object callBack) throws ListenerNotFoundException { emitter.removeNotificationListener(listener, filter, callBack); } public synchronized boolean isSampledStatisticsEnabled() { return sampledStatisticsEnabled; } public void setSampledStatisticsEnabled(boolean enabled) { if (enabled != this.sampledStatisticsEnabled) { this.sampledStatisticsEnabled = enabled; if(enabled) { this.sampledStatistics = new SampledStatisticsImpl(scheduler); } else { this.sampledStatistics.shutdown(); this.sampledStatistics = NULL_SAMPLED_STATISTICS; } sendNotification(SAMPLED_STATISTICS_ENABLED, enabled); } } public long getJobsCompletedMostRecentSample() { return this.sampledStatistics.getJobsCompletedMostRecentSample(); } public long getJobsExecutedMostRecentSample() { return this.sampledStatistics.getJobsExecutingMostRecentSample(); } public long getJobsScheduledMostRecentSample() { return this.sampledStatistics.getJobsScheduledMostRecentSample(); } public Map getPerformanceMetrics() { Map result = new HashMap<>(); result.put("JobsCompleted", getJobsCompletedMostRecentSample()); result.put("JobsExecuted", getJobsExecutedMostRecentSample()); result.put("JobsScheduled", getJobsScheduledMostRecentSample()); return result; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/QuartzSchedulerResources.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import java.util.ArrayList; import java.util.List; import org.quartz.management.ManagementRESTServiceConfiguration; import org.quartz.spi.JobStore; import org.quartz.spi.SchedulerPlugin; import org.quartz.spi.ThreadExecutor; import 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 final List schedulerPlugins = new ArrayList<>(10); private boolean makeSchedulerThreadDaemon = false; private boolean threadsInheritInitializersClassLoadContext = false; private String rmiBindName; private boolean jmxExport; private String jmxObjectName; private ManagementRESTServiceConfiguration managementRESTServiceConfiguration; private ThreadExecutor threadExecutor; private long batchTimeWindow = 0; private int maxBatchSize = 1; private boolean interruptJobsOnShutdown = false; private boolean interruptJobsOnShutdownWithWait = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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().isEmpty()) { 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().isEmpty()) { 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().isEmpty()) { 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().isEmpty()) { 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( "Failed 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 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 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 explicit 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; } /** * Get the ThreadExecutor which runs the QuartzSchedulerThread */ public ThreadExecutor getThreadExecutor() { return threadExecutor; } /** * Set the ThreadExecutor which runs the QuartzSchedulerThread */ public void setThreadExecutor(ThreadExecutor threadExecutor) { this.threadExecutor = threadExecutor; } /** * 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.replaceAll(":|=|\n", ".") + ",instance=" + schedInstId; } public long getBatchTimeWindow() { return batchTimeWindow; } public void setBatchTimeWindow(long batchTimeWindow) { this.batchTimeWindow = batchTimeWindow; } public int getMaxBatchSize() { return maxBatchSize; } public void setMaxBatchSize(int maxBatchSize) { this.maxBatchSize = maxBatchSize; } public boolean isInterruptJobsOnShutdown() { return interruptJobsOnShutdown; } public void setInterruptJobsOnShutdown(boolean interruptJobsOnShutdown) { this.interruptJobsOnShutdown = interruptJobsOnShutdown; } public boolean isInterruptJobsOnShutdownWithWait() { return interruptJobsOnShutdownWithWait; } public void setInterruptJobsOnShutdownWithWait( boolean interruptJobsOnShutdownWithWait) { this.interruptJobsOnShutdownWithWait = interruptJobsOnShutdownWithWait; } public ManagementRESTServiceConfiguration getManagementRESTServiceConfiguration() { return managementRESTServiceConfiguration; } public void setManagementRESTServiceConfiguration(ManagementRESTServiceConfiguration managementRESTServiceConfiguration) { this.managementRESTServiceConfiguration = managementRESTServiceConfiguration; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/QuartzSchedulerThread.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import org.quartz.JobPersistenceException; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.spi.JobStore; import org.quartz.spi.OperableTrigger; import org.quartz.spi.TriggerFiredBundle; import org.quartz.spi.TriggerFiredResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* The thread responsible for performing the work of firing {@link Trigger} * s that are registered with the {@link QuartzScheduler}. *

* * @see QuartzScheduler * @see org.quartz.Job * @see Trigger * * @author James House */ public class QuartzSchedulerThread extends Thread { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private QuartzScheduler qs; private QuartzSchedulerResources qsRsrcs; private final Object sigLock = new Object(); private boolean signaled; private long signaledNextFireTime; private boolean paused; private final AtomicBoolean halted; private final 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 final long DEFAULT_IDLE_WAIT_TIME = 30L * 1000L; private long idleWaitTime = DEFAULT_IDLE_WAIT_TIME; private int idleWaitVariableness = 7 * 1000; private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Construct a new QuartzSchedulerThread for the given * QuartzScheduler as a non-daemon Thread * with normal priority. *

*/ QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs) { this(qs, qsRsrcs, 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, boolean setDaemon, int threadPrio) { super(qs.getSchedulerThreadGroup(), qsRsrcs.getThreadName()); this.qs = qs; this.qsRsrcs = qsRsrcs; 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 = new AtomicBoolean(false); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ void setIdleWaitTime(long waitTime) { idleWaitTime = waitTime; idleWaitVariableness = (int) (waitTime * 0.2); } private long getRandomizedIdleWaitTime() { return idleWaitTime - random.nextInt(idleWaitVariableness); } /** *

* 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(boolean wait) { synchronized (sigLock) { halted.set(true); if (paused) { sigLock.notifyAll(); } else { signalSchedulingChange(0); } } this.interrupt(); if (wait) { boolean interrupted = false; try { while (true) { try { join(); break; } catch (InterruptedException e) { interrupted = true; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } } 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 occurring while * waiting for the fire time to arrive. *

* * @param candidateNewNextFireTime 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. *

*/ @Override public void run() { int acquiresFailed = 0; while (!halted.get()) { try { // check if we're supposed to pause... synchronized (sigLock) { while (paused && !halted.get()) { try { // wait until togglePause(false) is called... sigLock.wait(1000L); } catch (InterruptedException ignore) { } // reset failure counter when paused, so that we don't // wait again after unpausing acquiresFailed = 0; } if (halted.get()) { break; } } // wait a bit, if reading from job store is consistently // failing (e.g. DB is down or restarting).. if (acquiresFailed > 1) { try { long delay = computeDelayForRepeatedErrors(qsRsrcs.getJobStore(), acquiresFailed); Thread.sleep(delay); } catch (Exception ignore) { } } int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads(); synchronized (sigLock) { if (halted.get()) { break; } } if(availThreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads... List triggers; long now = System.currentTimeMillis(); clearSignaledSchedulingChange(); try { triggers = qsRsrcs.getJobStore().acquireNextTriggers( now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow()); acquiresFailed = 0; if (log.isDebugEnabled()) log.debug("batch acquisition of {} triggers", triggers == null ? 0 : triggers.size()); } catch (JobPersistenceException jpe) { if (acquiresFailed == 0) { qs.notifySchedulerListenersError( "An error occurred while scanning for the next triggers to fire.", jpe); } if (acquiresFailed < Integer.MAX_VALUE) acquiresFailed++; continue; } catch (RuntimeException e) { if (acquiresFailed == 0) { getLog().error("quartzSchedulerThreadLoop: RuntimeException {}", e.getMessage(), e); } if (acquiresFailed < Integer.MAX_VALUE) acquiresFailed++; continue; } if (triggers != null && !triggers.isEmpty()) { now = System.currentTimeMillis(); long triggerTime = triggers.get(0).getNextFireTime().getTime(); long timeUntilTrigger = triggerTime - now; while(timeUntilTrigger > 2) { synchronized (sigLock) { if (halted.get()) { break; } if (!isCandidateNewTimeEarlierWithinReason(triggerTime, false)) { 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) { } } } synchronized (sigLock) { if (halted.get()) { break; } } if(releaseIfScheduleChangedSignificantly(triggers, triggerTime)) { break; } now = System.currentTimeMillis(); timeUntilTrigger = triggerTime - now; } // this happens if releaseIfScheduleChangedSignificantly decided to release triggers if(triggers.isEmpty()) continue; // set triggers to 'executing' List bundles = new ArrayList<>(); boolean goAhead; synchronized(sigLock) { goAhead = !halted.get(); } if(goAhead) { try { List res = qsRsrcs.getJobStore().triggersFired(triggers); if(res != null) bundles = res; } catch (SchedulerException se) { qs.notifySchedulerListenersError( "An error occurred while firing triggers '" + triggers + "'", se); //QTZ-179 : a problem occurred interacting with the triggers from the db //we release them and loop again for (OperableTrigger trigger : triggers) { qsRsrcs.getJobStore().releaseAcquiredTrigger(trigger); } continue; } } for (int i = 0; i < bundles.size(); i++) { TriggerFiredResult result = bundles.get(i); TriggerFiredBundle bundle = result.getTriggerFiredBundle(); Exception exception = result.getException(); if (exception instanceof RuntimeException) { getLog().error("RuntimeException while firing trigger {}", triggers.get(i), exception); qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i)); continue; } // it's possible to get 'null' if the triggers was paused, // blocked, or other similar occurrences that prevent it being // fired at this time... or if the scheduler was shutdown (halted) if (bundle == null) { qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i)); continue; } JobRunShell shell; try { shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bundle); shell.initialize(qs); } catch (SchedulerException se) { qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bundle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR); continue; } if (!qsRsrcs.getThreadPool().runInThread(shell)) { // 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(triggers.get(i), bundle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR); } } continue; // while (!halted) } } else { // if(availThreadCount > 0) // should never happen, if threadPool.blockForAvailableThreads() follows contract continue; // while (!halted) } long now = System.currentTimeMillis(); long waitTime = now + getRandomizedIdleWaitTime(); long timeUntilContinue = waitTime - now; synchronized(sigLock) { try { if(!halted.get()) { // QTZ-336 A job might have been completed in the mean time and we might have // missed the scheduled changed signal by not waiting for the notify() yet // Check that before waiting for too long in case this very job needs to be // scheduled very soon if (!isScheduleChanged()) { sigLock.wait(timeUntilContinue); } } } catch (InterruptedException ignore) { } } } catch(RuntimeException re) { getLog().error("Runtime error occurred in main trigger firing loop.", re); } } // while (!halted) // drop references to scheduler stuff to aid garbage collection... qs = null; qsRsrcs = null; } private static final long MIN_DELAY = 20; private static final long MAX_DELAY = 600000; private static long computeDelayForRepeatedErrors(JobStore jobStore, int acquiresFailed) { long delay; try { delay = jobStore.getAcquireRetryDelay(acquiresFailed); } catch (Exception ignored) { // we're trying to be useful in case of error states, not cause // additional errors.. delay = 100; } // sanity check per getAcquireRetryDelay specification if (delay < MIN_DELAY) delay = MIN_DELAY; if (delay > MAX_DELAY) delay = MAX_DELAY; return delay; } private boolean releaseIfScheduleChangedSignificantly( List triggers, long triggerTime) { if (isCandidateNewTimeEarlierWithinReason(triggerTime, true)) { // above call does a clearSignaledSchedulingChange() for (OperableTrigger trigger : triggers) { qsRsrcs.getJobStore().releaseAcquiredTrigger(trigger); } triggers.clear(); return true; } return false; } private boolean isCandidateNewTimeEarlierWithinReason(long oldTime, boolean clearSignal) { // 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) { if (!isScheduleChanged()) return false; 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? long diff = oldTime - System.currentTimeMillis(); if(diff < (qsRsrcs.getJobStore().supportsPersistence() ? 70L : 7L)) earlier = false; } if(clearSignal) { clearSignaledSchedulingChange(); } return earlier; } } public Logger getLog() { return log; } } // end of QuartzSchedulerThread ================================================ FILE: quartz/src/main/java/org/quartz/core/RemotableQuartzScheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import java.rmi.Remote; import java.rmi.RemoteException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.quartz.Calendar; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.UnableToInterruptJobException; import org.quartz.Trigger.TriggerState; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.spi.OperableTrigger; /** * @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; boolean isClustered() throws RemoteException; Class getThreadPoolClass() throws RemoteException; int getThreadPoolSize() throws RemoteException; void clear() throws SchedulerException, RemoteException; List getCurrentlyExecutingJobs() throws SchedulerException, RemoteException; Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException, RemoteException; Date scheduleJob(Trigger trigger) throws SchedulerException, RemoteException; void addJob(JobDetail jobDetail, boolean replace) throws SchedulerException, RemoteException; void addJob(JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) throws SchedulerException, RemoteException; boolean deleteJob(JobKey jobKey) throws SchedulerException, RemoteException; boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException, RemoteException; Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException, RemoteException; void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException, RemoteException; void triggerJob(OperableTrigger trig) throws SchedulerException, RemoteException; void pauseTrigger(TriggerKey triggerKey) throws SchedulerException, RemoteException; void pauseTriggers(GroupMatcher matcher) throws SchedulerException, RemoteException; void pauseJob(JobKey jobKey) throws SchedulerException, RemoteException; void pauseJobs(GroupMatcher matcher) throws SchedulerException, RemoteException; void resumeTrigger(TriggerKey triggerKey) throws SchedulerException, RemoteException; void resumeTriggers(GroupMatcher matcher) throws SchedulerException, RemoteException; Set getPausedTriggerGroups() throws SchedulerException, RemoteException; void resumeJob(JobKey jobKey) throws SchedulerException, RemoteException; void resumeJobs(GroupMatcher matcher) throws SchedulerException, RemoteException; void pauseAll() throws SchedulerException, RemoteException; void resumeAll() throws SchedulerException, RemoteException; List getJobGroupNames() throws SchedulerException, RemoteException; Set getJobKeys(GroupMatcher matcher) throws SchedulerException, RemoteException; List getTriggersOfJob(JobKey jobKey) throws SchedulerException, RemoteException; List getTriggerGroupNames() throws SchedulerException, RemoteException; Set getTriggerKeys(GroupMatcher matcher) throws SchedulerException, RemoteException; JobDetail getJobDetail(JobKey jobKey) throws SchedulerException, RemoteException; List getJobDetails(GroupMatcher matcher) throws SchedulerException, RemoteException; Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException, RemoteException; TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException, RemoteException; void resetTriggerFromErrorState(TriggerKey triggerKey) throws SchedulerException, RemoteException; void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException, RemoteException; boolean deleteCalendar(String calName) throws SchedulerException, RemoteException; Calendar getCalendar(String calName) throws SchedulerException, RemoteException; List getCalendarNames() throws SchedulerException, RemoteException; boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException,RemoteException; boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException,RemoteException; boolean checkExists(JobKey jobKey) throws SchedulerException,RemoteException; boolean checkExists(TriggerKey triggerKey) throws SchedulerException,RemoteException; boolean deleteJobs(List jobKeys) throws SchedulerException,RemoteException; void scheduleJobs(Map> triggersAndJobs, boolean replace) throws SchedulerException,RemoteException; void scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace) throws SchedulerException,RemoteException; boolean unscheduleJobs(List triggerKeys) throws SchedulerException,RemoteException; } ================================================ FILE: quartz/src/main/java/org/quartz/core/SampledStatistics.java ================================================ package org.quartz.core; public interface SampledStatistics { long getJobsScheduledMostRecentSample(); long getJobsExecutingMostRecentSample(); long getJobsCompletedMostRecentSample(); void shutdown(); } ================================================ FILE: quartz/src/main/java/org/quartz/core/SampledStatisticsImpl.java ================================================ package org.quartz.core; import java.util.Timer; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.SchedulerListener; import org.quartz.Trigger; import org.quartz.listeners.SchedulerListenerSupport; import org.quartz.utils.counter.CounterConfig; import org.quartz.utils.counter.CounterManager; import org.quartz.utils.counter.CounterManagerImpl; import org.quartz.utils.counter.sampled.SampledCounter; import org.quartz.utils.counter.sampled.SampledCounterConfig; import org.quartz.utils.counter.sampled.SampledRateCounterConfig; public class SampledStatisticsImpl extends SchedulerListenerSupport implements SampledStatistics, JobListener, SchedulerListener { @SuppressWarnings("unused") private final QuartzScheduler scheduler; private static final String NAME = "QuartzSampledStatistics"; private static final int DEFAULT_HISTORY_SIZE = 30; private static final int DEFAULT_INTERVAL_SECS = 1; private final static SampledCounterConfig DEFAULT_SAMPLED_COUNTER_CONFIG = new SampledCounterConfig(DEFAULT_INTERVAL_SECS, DEFAULT_HISTORY_SIZE, true, 0L); @SuppressWarnings("unused") private final static SampledRateCounterConfig DEFAULT_SAMPLED_RATE_COUNTER_CONFIG = new SampledRateCounterConfig(DEFAULT_INTERVAL_SECS, DEFAULT_HISTORY_SIZE, true); private final CounterManager counterManager; private final SampledCounter jobsScheduledCount; private final SampledCounter jobsExecutingCount; private final SampledCounter jobsCompletedCount; SampledStatisticsImpl(QuartzScheduler scheduler) { this.scheduler = scheduler; counterManager = new CounterManagerImpl(new Timer(NAME+"Timer")); jobsScheduledCount = createSampledCounter(DEFAULT_SAMPLED_COUNTER_CONFIG); jobsExecutingCount = createSampledCounter(DEFAULT_SAMPLED_COUNTER_CONFIG); jobsCompletedCount = createSampledCounter(DEFAULT_SAMPLED_COUNTER_CONFIG); scheduler.addInternalSchedulerListener(this); scheduler.addInternalJobListener(this); } public void shutdown() { counterManager.shutdown(true); } private SampledCounter createSampledCounter(CounterConfig defaultCounterConfig) { return (SampledCounter) counterManager.createCounter(defaultCounterConfig); } /** * Clears the collected statistics. Resets all counters to zero */ public void clearStatistics() { jobsScheduledCount.getAndReset(); jobsExecutingCount.getAndReset(); jobsCompletedCount.getAndReset(); } public long getJobsCompletedMostRecentSample() { return jobsCompletedCount.getMostRecentSample().getCounterValue(); } public long getJobsExecutingMostRecentSample() { return jobsExecutingCount.getMostRecentSample().getCounterValue(); } public long getJobsScheduledMostRecentSample() { return jobsScheduledCount.getMostRecentSample().getCounterValue(); } public String getName() { return NAME; } @Override public void jobScheduled(Trigger trigger) { jobsScheduledCount.increment(); } public void jobExecutionVetoed(JobExecutionContext context) { /**/ } public void jobToBeExecuted(JobExecutionContext context) { jobsExecutingCount.increment(); } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { jobsCompletedCount.increment(); } @Override public void jobAdded(JobDetail jobDetail) { /**/ } public void jobDeleted(String jobName, String groupName) { /**/ } } ================================================ FILE: quartz/src/main/java/org/quartz/core/SchedulerSignalerImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.JobKey; import org.quartz.SchedulerException; import org.quartz.Trigger; import 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 { final Logger log = LoggerFactory.getLogger(SchedulerSignalerImpl.class); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected final QuartzScheduler sched; protected final 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); } public void notifySchedulerListenersJobDeleted(JobKey jobKey) { sched.notifySchedulerListenersJobDeleted(jobKey); } public void notifySchedulerListenersError(String string, SchedulerException jpe) { sched.notifySchedulerListenersError(string, jpe); } } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/CronTriggerSupport.java ================================================ package org.quartz.core.jmx; import static javax.management.openmbean.SimpleType.STRING; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.quartz.CronTrigger; import org.quartz.impl.triggers.CronTriggerImpl; import org.quartz.spi.OperableTrigger; public class CronTriggerSupport { private static final String COMPOSITE_TYPE_NAME = "CronTrigger"; private static final String COMPOSITE_TYPE_DESCRIPTION = "CronTrigger Details"; private static final String[] ITEM_NAMES = new String[] { "expression", "timeZone" }; private static final String[] ITEM_DESCRIPTIONS = new String[] { "expression", "timeZone" }; private static final OpenType[] ITEM_TYPES = new OpenType[] { STRING, STRING }; private static final CompositeType COMPOSITE_TYPE; private static final String TABULAR_TYPE_NAME = "CronTrigger collection"; private static final String TABULAR_TYPE_DESCRIPTION = "CronTrigger collection"; private static final TabularType TABULAR_TYPE; static { try { COMPOSITE_TYPE = new CompositeType(COMPOSITE_TYPE_NAME, COMPOSITE_TYPE_DESCRIPTION, getItemNames(), getItemDescriptions(), getItemTypes()); TABULAR_TYPE = new TabularType(TABULAR_TYPE_NAME, TABULAR_TYPE_DESCRIPTION, COMPOSITE_TYPE, getItemNames()); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static String[] getItemNames() { List l = new ArrayList<>(Arrays.asList(ITEM_NAMES)); l.addAll(Arrays.asList(TriggerSupport.getItemNames())); return l.toArray(new String[l.size()]); } public static String[] getItemDescriptions() { List l = new ArrayList<>(Arrays.asList(ITEM_DESCRIPTIONS)); l.addAll(Arrays.asList(TriggerSupport.getItemDescriptions())); return l.toArray(new String[l.size()]); } public static OpenType[] getItemTypes() { List l = new ArrayList<>(Arrays.asList(ITEM_TYPES)); l.addAll(Arrays.asList(TriggerSupport.getItemTypes())); return l.toArray(new OpenType[l.size()]); } public static CompositeData toCompositeData(CronTrigger trigger) { try { return new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, new Object[] { trigger.getCronExpression(), trigger.getTimeZone(), trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getJobKey().getName(), trigger.getJobKey().getGroup(), trigger.getDescription(), JobDataMapSupport.toTabularData(trigger .getJobDataMap()), trigger.getCalendarName(), ((OperableTrigger)trigger).getFireInstanceId(), trigger.getMisfireInstruction(), trigger.getPriority(), trigger.getStartTime(), trigger.getEndTime(), trigger.getNextFireTime(), trigger.getPreviousFireTime(), trigger.getFinalFireTime() }); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static TabularData toTabularData(List triggers) { TabularData tData = new TabularDataSupport(TABULAR_TYPE); if (triggers != null) { ArrayList list = new ArrayList<>(); for (CronTrigger trigger : triggers) { list.add(toCompositeData(trigger)); } tData.putAll(list.toArray(new CompositeData[list.size()])); } return tData; } public static OperableTrigger newTrigger(CompositeData cData) throws ParseException { CronTriggerImpl result = new CronTriggerImpl(); result.setCronExpression((String) cData.get("cronExpression")); if(cData.containsKey("timeZone")) { result.setTimeZone(TimeZone.getTimeZone((String)cData.get("timeZone"))); } TriggerSupport.initializeTrigger(result, cData); return result; } public static OperableTrigger newTrigger(Map attrMap) throws ParseException { CronTriggerImpl result = new CronTriggerImpl(); result.setCronExpression((String) attrMap.get("cronExpression")); if(attrMap.containsKey("timeZone")) { result.setTimeZone(TimeZone.getTimeZone((String)attrMap.get("timeZone"))); } TriggerSupport.initializeTrigger(result, attrMap); return result; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/JobDataMapSupport.java ================================================ package org.quartz.core.jmx; import static javax.management.openmbean.SimpleType.STRING; import java.util.ArrayList; import java.util.Map; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.quartz.JobDataMap; public class JobDataMapSupport { private static final String TYPE_NAME = "JobDataMap"; private static final String[] keyValue = new String[] { "key", "value" }; private static final OpenType[] openTypes = new OpenType[] { STRING, STRING }; private static final CompositeType rowType; public static final TabularType TABULAR_TYPE; static { try { rowType = new CompositeType(TYPE_NAME, TYPE_NAME, keyValue, keyValue, openTypes); TABULAR_TYPE = new TabularType(TYPE_NAME, TYPE_NAME, rowType, new String[] { "key" }); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static JobDataMap newJobDataMap(TabularData tabularData) { JobDataMap jobDataMap = new JobDataMap(); if(tabularData != null) { for (Object o : tabularData.values()) { CompositeData cData = (CompositeData) o; jobDataMap.put((String) cData.get("key"), (String) cData.get("value")); } } return jobDataMap; } public static JobDataMap newJobDataMap(Map map) { JobDataMap jobDataMap = new JobDataMap(); if (map != null) { jobDataMap.putAll(map); } return jobDataMap; } /** * @return composite data */ public static CompositeData toCompositeData(String key, String value) { try { return new CompositeDataSupport(rowType, keyValue, new Object[] { key, value }); } catch (OpenDataException e) { throw new RuntimeException(e); } } /** * @param jobDataMap * @return TabularData */ public static TabularData toTabularData(JobDataMap jobDataMap) { TabularData tData = new TabularDataSupport(TABULAR_TYPE); ArrayList list = new ArrayList<>(); for (Map.Entry entry : jobDataMap.entrySet()) { list.add(toCompositeData(entry.getKey(), String.valueOf(entry.getValue()))); } tData.putAll(list.toArray(new CompositeData[list.size()])); return tData; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/JobDetailSupport.java ================================================ package org.quartz.core.jmx; import static javax.management.openmbean.SimpleType.BOOLEAN; import static javax.management.openmbean.SimpleType.STRING; import java.util.ArrayList; import java.util.Map; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.impl.JobDetailImpl; public class JobDetailSupport { private static final String COMPOSITE_TYPE_NAME = "JobDetail"; private static final String COMPOSITE_TYPE_DESCRIPTION = "Job Execution Details"; private static final String[] ITEM_NAMES = new String[] { "name", "group", "description", "jobClass", "jobDataMap", "durability", "shouldRecover",}; private static final String[] ITEM_DESCRIPTIONS = new String[] { "name", "group", "description", "jobClass", "jobDataMap", "durability", "shouldRecover",}; private static final OpenType[] ITEM_TYPES = new OpenType[] { STRING, STRING, STRING, STRING, JobDataMapSupport.TABULAR_TYPE, BOOLEAN, BOOLEAN, }; private static final CompositeType COMPOSITE_TYPE; private static final String TABULAR_TYPE_NAME = "JobDetail collection"; private static final String TABULAR_TYPE_DESCRIPTION = "JobDetail collection"; private static final String[] INDEX_NAMES = new String[] { "name", "group" }; private static final TabularType TABULAR_TYPE; static { try { COMPOSITE_TYPE = new CompositeType(COMPOSITE_TYPE_NAME, COMPOSITE_TYPE_DESCRIPTION, ITEM_NAMES, ITEM_DESCRIPTIONS, ITEM_TYPES); TABULAR_TYPE = new TabularType(TABULAR_TYPE_NAME, TABULAR_TYPE_DESCRIPTION, COMPOSITE_TYPE, INDEX_NAMES); } catch (OpenDataException e) { throw new RuntimeException(e); } } /** * @param cData * @return JobDetail */ public static JobDetail newJobDetail(CompositeData cData) throws ClassNotFoundException { JobDetailImpl jobDetail = new JobDetailImpl(); int i = 0; jobDetail.setName((String) cData.get(ITEM_NAMES[i++])); jobDetail.setGroup((String) cData.get(ITEM_NAMES[i++])); jobDetail.setDescription((String) cData.get(ITEM_NAMES[i++])); Class jobClass = Class.forName((String) cData.get(ITEM_NAMES[i++])); @SuppressWarnings("unchecked") Class jobClassTyped = (Class)jobClass; jobDetail.setJobClass(jobClassTyped); jobDetail.setJobDataMap(JobDataMapSupport.newJobDataMap((TabularData) cData.get(ITEM_NAMES[i++]))); jobDetail.setDurability((Boolean) cData.get(ITEM_NAMES[i++])); jobDetail.setRequestsRecovery((Boolean) cData.get(ITEM_NAMES[i++])); return jobDetail; } /** * @param attrMap the attributes that define the job * @return JobDetail */ public static JobDetail newJobDetail(Map attrMap) throws ClassNotFoundException { JobDetailImpl jobDetail = new JobDetailImpl(); int i = 0; jobDetail.setName((String) attrMap.get(ITEM_NAMES[i++])); jobDetail.setGroup((String) attrMap.get(ITEM_NAMES[i++])); jobDetail.setDescription((String) attrMap.get(ITEM_NAMES[i++])); Class jobClass = Class.forName((String) attrMap.get(ITEM_NAMES[i++])); @SuppressWarnings("unchecked") Class jobClassTyped = (Class)jobClass; jobDetail.setJobClass(jobClassTyped); if(attrMap.containsKey(ITEM_NAMES[i])) { @SuppressWarnings("unchecked") Map map = (Map)attrMap.get(ITEM_NAMES[i]); jobDetail.setJobDataMap(JobDataMapSupport.newJobDataMap(map)); } i++; if(attrMap.containsKey(ITEM_NAMES[i])) { jobDetail.setDurability((Boolean) attrMap.get(ITEM_NAMES[i])); } i++; if(attrMap.containsKey(ITEM_NAMES[i])) { jobDetail.setRequestsRecovery((Boolean) attrMap.get(ITEM_NAMES[i])); } i++; return jobDetail; } /** * @param jobDetail * @return CompositeData */ public static CompositeData toCompositeData(JobDetail jobDetail) { try { return new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, new Object[] { jobDetail.getKey().getName(), jobDetail.getKey().getGroup(), jobDetail.getDescription(), jobDetail.getJobClass().getName(), JobDataMapSupport.toTabularData(jobDetail .getJobDataMap()), jobDetail.isDurable(), jobDetail.requestsRecovery(), }); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static TabularData toTabularData(JobDetail[] jobDetails) { TabularData tData = new TabularDataSupport(TABULAR_TYPE); if (jobDetails != null) { ArrayList list = new ArrayList<>(); for (JobDetail jobDetail : jobDetails) { list.add(toCompositeData(jobDetail)); } tData.putAll(list.toArray(new CompositeData[list.size()])); } return tData; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/JobExecutionContextSupport.java ================================================ package org.quartz.core.jmx; import static javax.management.openmbean.SimpleType.BOOLEAN; import static javax.management.openmbean.SimpleType.DATE; import static javax.management.openmbean.SimpleType.INTEGER; import static javax.management.openmbean.SimpleType.LONG; import static javax.management.openmbean.SimpleType.STRING; import java.util.ArrayList; import java.util.List; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.quartz.JobExecutionContext; import org.quartz.SchedulerException; public class JobExecutionContextSupport { private static final String COMPOSITE_TYPE_NAME = "JobExecutionContext"; private static final String COMPOSITE_TYPE_DESCRIPTION = "Job Execution Instance Details"; private static final String[] ITEM_NAMES = new String[] { "schedulerName", "triggerName", "triggerGroup", "jobName", "jobGroup", "jobDataMap", "calendarName", "recovering", "refireCount", "fireTime", "scheduledFireTime", "previousFireTime", "nextFireTime", "jobRunTime", "fireInstanceId" }; private static final String[] ITEM_DESCRIPTIONS = new String[] { "schedulerName", "triggerName", "triggerGroup", "jobName", "jobGroup", "jobDataMap", "calendarName", "recovering", "refireCount", "fireTime", "scheduledFireTime", "previousFireTime", "nextFireTime", "jobRunTime", "fireInstanceId" }; private static final OpenType[] ITEM_TYPES = new OpenType[] { STRING, STRING, STRING, STRING, STRING, JobDataMapSupport.TABULAR_TYPE, STRING, BOOLEAN, INTEGER, DATE, DATE, DATE, DATE, LONG, STRING }; private static final CompositeType COMPOSITE_TYPE; private static final String TABULAR_TYPE_NAME = "JobExecutionContextArray"; private static final String TABULAR_TYPE_DESCRIPTION = "Array of composite JobExecutionContext"; private static final String[] INDEX_NAMES = new String[] { "schedulerName", "triggerName", "triggerGroup", "jobName", "jobGroup", "fireTime" }; private static final TabularType TABULAR_TYPE; static { try { COMPOSITE_TYPE = new CompositeType(COMPOSITE_TYPE_NAME, COMPOSITE_TYPE_DESCRIPTION, ITEM_NAMES, ITEM_DESCRIPTIONS, ITEM_TYPES); TABULAR_TYPE = new TabularType(TABULAR_TYPE_NAME, TABULAR_TYPE_DESCRIPTION, COMPOSITE_TYPE, INDEX_NAMES); } catch (OpenDataException e) { throw new RuntimeException(e); } } /** * @return composite data */ public static CompositeData toCompositeData(JobExecutionContext jec) throws SchedulerException { try { return new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, new Object[] { jec.getScheduler().getSchedulerName(), jec.getTrigger().getKey().getName(), jec.getTrigger().getKey().getGroup(), jec.getJobDetail().getKey().getName(), jec.getJobDetail().getKey().getGroup(), JobDataMapSupport.toTabularData(jec .getMergedJobDataMap()), jec.getTrigger().getCalendarName(), jec.isRecovering(), jec.getRefireCount(), jec.getFireTime(), jec.getScheduledFireTime(), jec.getPreviousFireTime(), jec.getNextFireTime(), jec.getJobRunTime(), jec.getFireInstanceId() }); } catch (OpenDataException e) { throw new RuntimeException(e); } } /** * @return array of region statistics */ public static TabularData toTabularData( final List executingJobs) throws SchedulerException { List list = new ArrayList<>(); for (JobExecutionContext executingJob : executingJobs) { list.add(toCompositeData(executingJob)); } TabularData td = new TabularDataSupport(TABULAR_TYPE); td.putAll(list.toArray(new CompositeData[list.size()])); return td; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/QuartzSchedulerMBean.java ================================================ package org.quartz.core.jmx; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; public interface QuartzSchedulerMBean { String SCHEDULER_STARTED = "schedulerStarted"; String SCHEDULER_PAUSED = "schedulerPaused"; String SCHEDULER_SHUTDOWN = "schedulerShutdown"; String SCHEDULER_ERROR = "schedulerError"; String JOB_ADDED = "jobAdded"; String JOB_DELETED = "jobDeleted"; String JOB_SCHEDULED = "jobScheduled"; String JOB_UNSCHEDULED = "jobUnscheduled"; String JOBS_PAUSED = "jobsPaused"; String JOBS_RESUMED = "jobsResumed"; String JOB_EXECUTION_VETOED = "jobExecutionVetoed"; String JOB_TO_BE_EXECUTED = "jobToBeExecuted"; String JOB_WAS_EXECUTED = "jobWasExecuted"; String TRIGGER_FINALIZED = "triggerFinalized"; String TRIGGERS_PAUSED = "triggersPaused"; String TRIGGERS_RESUMED = "triggersResumed"; String SCHEDULING_DATA_CLEARED = "schedulingDataCleared"; String SAMPLED_STATISTICS_ENABLED = "sampledStatisticsEnabled"; String SAMPLED_STATISTICS_RESET = "sampledStatisticsReset"; String getSchedulerName(); String getSchedulerInstanceId(); boolean isStandbyMode(); boolean isShutdown(); String getVersion(); String getJobStoreClassName(); String getThreadPoolClassName(); int getThreadPoolSize(); long getJobsScheduledMostRecentSample(); long getJobsExecutedMostRecentSample(); long getJobsCompletedMostRecentSample(); Map getPerformanceMetrics(); /** * @return TabularData of CompositeData:JobExecutionContext * @throws Exception */ TabularData getCurrentlyExecutingJobs() throws Exception; /** * @return TabularData of CompositeData:JobDetail * @throws Exception * @see JobDetailSupport */ TabularData getAllJobDetails() throws Exception; /** * @return List of CompositeData:[CronTrigger|SimpleTrigger] * @throws Exception * @see TriggerSupport */ List getAllTriggers() throws Exception; List getJobGroupNames() throws Exception; List getJobNames(String groupName) throws Exception; /** * @return CompositeData:JobDetail * @throws Exception * @see JobDetailSupport */ CompositeData getJobDetail(String jobName, String jobGroupName) throws Exception; boolean isStarted(); void start() throws Exception; void shutdown(); void standby(); void clear() throws Exception; /** * Schedule an existing job with an existing trigger. * * @param jobName * @param jobGroup * @param triggerName * @param triggerGroup * @return date of nextFireTime * @throws Exception */ Date scheduleJob(String jobName, String jobGroup, String triggerName, String triggerGroup) throws Exception; /** * Schedules a job using the given Cron/Simple triggerInfo. * * The triggerInfo and jobDetailInfo must contain well-known attribute values. * TriggerInfo attributes: name, group, description, calendarName, priority, * CronExpression | (startTime, endTime, repeatCount, repeatInterval) * JobDetailInfo attributes: name, group, description, jobClass, jobDataMap, durability, * shouldRecover */ void scheduleBasicJob(Map jobDetailInfo, Map triggerInfo) throws Exception; /** * Schedules an arbitrary job described by abstractJobInfo using a trigger specified by abstractTriggerInfo. * * AbstractTriggerInfo and AbstractJobInfo must contain the following String attributes. * AbstractTriggerInfo: triggerClass, the fully-qualified class name of a concrete Trigger type * AbstractJobInfo: jobDetailClass, the fully-qualified class name of a concrete JobDetail type * * If the Trigger and JobDetail can be successfully instantiated, the remaining attributes will be * reflectively applied to those instances. The remaining attributes are limited to the types: * Integer, Double, Float, String, Boolean, Date, Character, Map<String, Object>. * Maps are further limited to containing values from the same set of types, less Map itself. * * @throws Exception */ void scheduleJob(Map abstractJobInfo, Map abstractTriggerInfo) throws Exception; /** * Schedules the specified job using a trigger described by abstractTriggerInfo, which must contain the * fully-qualified trigger class name under the key "triggerClass." That trigger type must contain a * no-arg constructor and have public access. Other attributes are applied reflectively and are limited * to the types: * Integer, Double, Float, String, Boolean, Date, Character, Map<String, Object>. * Maps are limited to containing values from the same set of types, less Map itself. * * @param jobName * @param jobGroup * @param abstractTriggerInfo * @throws Exception */ void scheduleJob(String jobName, String jobGroup, Map abstractTriggerInfo) throws Exception; boolean unscheduleJob(String triggerName, String triggerGroup) throws Exception; boolean interruptJob(String jobName, String jobGroupName) throws Exception; boolean interruptJob(String fireInstanceId) throws Exception; void triggerJob(String jobName, String jobGroupName, Map jobDataMap) throws Exception; boolean deleteJob(String jobName, String jobGroupName) throws Exception; void addJob(CompositeData jobDetail, boolean replace) throws Exception; /** * Adds a durable job described by abstractJobInfo, which must contain the fully-qualified JobDetail * class name under the key "jobDetailClass." That JobDetail type must contain a no-arg constructor * and have public access. Other attributes are applied reflectively and are limited * to the types: * Integer, Double, Float, String, Boolean, Date, Character, Map<String, Object>. * Maps are limited to containing values from the same set of types, less Map itself. * * @param abstractJobInfo map of attributes defining job * @param replace whether or not to replace a preexisting job with the same key * @throws Exception */ void addJob(Map abstractJobInfo, boolean replace) throws Exception; void pauseJobGroup(String jobGroup) throws Exception; /** * Pause all jobs whose group starts with jobGroupPrefix * @throws Exception */ void pauseJobsStartingWith(String jobGroupPrefix) throws Exception; /** * Pause all jobs whose group ends with jobGroupSuffix */ void pauseJobsEndingWith(String jobGroupSuffix) throws Exception; /** * Pause all jobs whose group contains jobGroupToken */ void pauseJobsContaining(String jobGroupToken) throws Exception; /** * Pause all jobs whose group is anything */ void pauseJobsAll() throws Exception; /** * Resume all jobs in the given group */ void resumeJobGroup(String jobGroup) throws Exception; /** * Resume all jobs whose group starts with jobGroupPrefix */ void resumeJobsStartingWith(String jobGroupPrefix) throws Exception; /** * Resume all jobs whose group ends with jobGroupSuffix */ void resumeJobsEndingWith(String jobGroupSuffix) throws Exception; /** * Resume all jobs whose group contains jobGroupToken */ void resumeJobsContaining(String jobGroupToken) throws Exception; /** * Resume all jobs whose group is anything */ void resumeJobsAll() throws Exception; void pauseJob(String jobName, String groupName) throws Exception; void resumeJob(String jobName, String jobGroupName) throws Exception; List getTriggerGroupNames() throws Exception; List getTriggerNames(String triggerGroupName) throws Exception; CompositeData getTrigger(String triggerName, String triggerGroupName) throws Exception; String getTriggerState(String triggerName, String triggerGroupName) throws Exception; /** * @return List of CompositeData:[CronTrigger|SimpleTrigger] for the specified job. * @see TriggerSupport */ List getTriggersOfJob(String jobName, String jobGroupName) throws Exception; Set getPausedTriggerGroups() throws Exception; void pauseAllTriggers() throws Exception; void resumeAllTriggers() throws Exception; void pauseTriggerGroup(String triggerGroup) throws Exception; /** * Pause all triggers whose group starts with triggerGroupPrefix */ void pauseTriggersStartingWith(String triggerGroupPrefix) throws Exception; /** * Pause all triggers whose group ends with triggerGroupSuffix */ void pauseTriggersEndingWith(String suffix) throws Exception; /** * Pause all triggers whose group contains triggerGroupToken */ void pauseTriggersContaining(String triggerGroupToken) throws Exception; /** * Pause all triggers whose group is anything */ void pauseTriggersAll() throws Exception; void resumeTriggerGroup(String triggerGroup) throws Exception; /** * Resume all triggers whose group starts with triggerGroupPrefix */ void resumeTriggersStartingWith(String triggerGroupPrefix) throws Exception; /** * Resume all triggers whose group ends with triggerGroupSuffix */ void resumeTriggersEndingWith(String triggerGroupSuffix) throws Exception; /** * Resume all triggers whose group contains triggerGroupToken */ void resumeTriggersContaining(String triggerGroupToken) throws Exception; /** * Resume all triggers whose group is anything */ void resumeTriggersAll() throws Exception; void pauseTrigger(String triggerName, String triggerGroupName) throws Exception; void resumeTrigger(String triggerName, String triggerGroupName) throws Exception; List getCalendarNames() throws Exception; void deleteCalendar(String name) throws Exception; void setSampledStatisticsEnabled(boolean enabled); boolean isSampledStatisticsEnabled(); } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/SimpleTriggerSupport.java ================================================ package org.quartz.core.jmx; import static javax.management.openmbean.SimpleType.INTEGER; import static javax.management.openmbean.SimpleType.LONG; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.quartz.SimpleTrigger; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.OperableTrigger; public class SimpleTriggerSupport { private static final String COMPOSITE_TYPE_NAME = "SimpleTrigger"; private static final String COMPOSITE_TYPE_DESCRIPTION = "SimpleTrigger Details"; private static final String[] ITEM_NAMES = new String[] { "repeatCount", "repeatInterval", "timesTriggered" }; private static final String[] ITEM_DESCRIPTIONS = new String[] { "repeatCount", "repeatInterval", "timesTriggered" }; private static final OpenType[] ITEM_TYPES = new OpenType[] { INTEGER, LONG, INTEGER }; private static final CompositeType COMPOSITE_TYPE; private static final String TABULAR_TYPE_NAME = "SimpleTrigger collection"; private static final String TABULAR_TYPE_DESCRIPTION = "SimpleTrigger collection"; private static final TabularType TABULAR_TYPE; static { try { COMPOSITE_TYPE = new CompositeType(COMPOSITE_TYPE_NAME, COMPOSITE_TYPE_DESCRIPTION, getItemNames(), getItemDescriptions(), getItemTypes()); TABULAR_TYPE = new TabularType(TABULAR_TYPE_NAME, TABULAR_TYPE_DESCRIPTION, COMPOSITE_TYPE, getItemNames()); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static String[] getItemNames() { List l = new ArrayList<>(Arrays.asList(ITEM_NAMES)); l.addAll(Arrays.asList(TriggerSupport.getItemNames())); return l.toArray(new String[l.size()]); } public static String[] getItemDescriptions() { List l = new ArrayList<>(Arrays.asList(ITEM_DESCRIPTIONS)); l.addAll(Arrays.asList(TriggerSupport.getItemDescriptions())); return l.toArray(new String[l.size()]); } public static OpenType[] getItemTypes() { List l = new ArrayList<>(Arrays.asList(ITEM_TYPES)); l.addAll(Arrays.asList(TriggerSupport.getItemTypes())); return l.toArray(new OpenType[l.size()]); } public static CompositeData toCompositeData(SimpleTrigger trigger) { try { return new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, new Object[] { trigger.getRepeatCount(), trigger.getRepeatInterval(), trigger.getTimesTriggered(), trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getJobKey().getName(), trigger.getJobKey().getGroup(), trigger.getDescription(), JobDataMapSupport.toTabularData(trigger .getJobDataMap()), trigger.getCalendarName(), ((OperableTrigger)trigger).getFireInstanceId(), trigger.getMisfireInstruction(), trigger.getPriority(), trigger.getStartTime(), trigger.getEndTime(), trigger.getNextFireTime(), trigger.getPreviousFireTime(), trigger.getFinalFireTime() }); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static TabularData toTabularData(List triggers) { TabularData tData = new TabularDataSupport(TABULAR_TYPE); if (triggers != null) { ArrayList list = new ArrayList<>(); for (SimpleTrigger trigger : triggers) { list.add(toCompositeData(trigger)); } tData.putAll(list.toArray(new CompositeData[list.size()])); } return tData; } public static OperableTrigger newTrigger(CompositeData cData) throws ParseException { SimpleTriggerImpl result = new SimpleTriggerImpl(); result.setRepeatCount((Integer) cData.get("repeatCount")); result.setRepeatInterval((Long) cData.get("repeatInterval")); result.setTimesTriggered((Integer) cData.get("timesTriggered")); TriggerSupport.initializeTrigger(result, cData); return result; } public static OperableTrigger newTrigger(Map attrMap) throws ParseException { SimpleTriggerImpl result = new SimpleTriggerImpl(); if(attrMap.containsKey("repeatCount")) { result.setRepeatCount((Integer) attrMap.get("repeatCount")); } if(attrMap.containsKey("repeatInterval")) { result.setRepeatInterval((Long) attrMap.get("repeatInterval")); } if(attrMap.containsKey("timesTriggered")) { result.setTimesTriggered((Integer) attrMap.get("timesTriggered")); } TriggerSupport.initializeTrigger(result, attrMap); return result; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/jmx/TriggerSupport.java ================================================ package org.quartz.core.jmx; import static javax.management.openmbean.SimpleType.DATE; import static javax.management.openmbean.SimpleType.INTEGER; import static javax.management.openmbean.SimpleType.STRING; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.quartz.JobKey; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.spi.MutableTrigger; import org.quartz.spi.OperableTrigger; public class TriggerSupport { private static final String COMPOSITE_TYPE_NAME = "Trigger"; private static final String COMPOSITE_TYPE_DESCRIPTION = "Trigger Details"; private static final String[] ITEM_NAMES = new String[] { "name", "group", "jobName", "jobGroup", "description", "jobDataMap", "calendarName", "fireInstanceId", "misfireInstruction", "priority", "startTime", "endTime", "nextFireTime", "previousFireTime", "finalFireTime" }; private static final String[] ITEM_DESCRIPTIONS = new String[] { "name", "group", "jobName", "jobGroup", "description", "jobDataMap", "calendarName", "fireInstanceId", "misfireInstruction", "priority", "startTime", "endTime", "nextFireTime", "previousFireTime", "finalFireTime" }; private static final OpenType[] ITEM_TYPES = new OpenType[] { STRING, STRING, STRING, STRING, STRING, JobDataMapSupport.TABULAR_TYPE, STRING, STRING, INTEGER, INTEGER, DATE, DATE, DATE, DATE, DATE }; private static final CompositeType COMPOSITE_TYPE; private static final String TABULAR_TYPE_NAME = "Trigger collection"; private static final String TABULAR_TYPE_DESCRIPTION = "Trigger collection"; private static final String[] INDEX_NAMES = new String[] { "name", "group" }; private static final TabularType TABULAR_TYPE; static { try { COMPOSITE_TYPE = new CompositeType(COMPOSITE_TYPE_NAME, COMPOSITE_TYPE_DESCRIPTION, ITEM_NAMES, ITEM_DESCRIPTIONS, ITEM_TYPES); TABULAR_TYPE = new TabularType(TABULAR_TYPE_NAME, TABULAR_TYPE_DESCRIPTION, COMPOSITE_TYPE, INDEX_NAMES); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static String[] getItemNames() { return ITEM_NAMES; } public static String[] getItemDescriptions() { return ITEM_DESCRIPTIONS; } public static OpenType[] getItemTypes() { return ITEM_TYPES; } public String[] getIndexNames() { return INDEX_NAMES; } public static CompositeData toCompositeData(Trigger trigger) { try { return new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, new Object[] { trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getJobKey().getName(), trigger.getJobKey().getGroup(), trigger.getDescription(), JobDataMapSupport.toTabularData(trigger .getJobDataMap()), trigger.getCalendarName(), ((OperableTrigger)trigger).getFireInstanceId(), trigger.getMisfireInstruction(), trigger.getPriority(), trigger.getStartTime(), trigger.getEndTime(), trigger.getNextFireTime(), trigger.getPreviousFireTime(), trigger.getFinalFireTime() }); } catch (OpenDataException e) { throw new RuntimeException(e); } } public static TabularData toTabularData(List triggers) { TabularData tData = new TabularDataSupport(TABULAR_TYPE); if (triggers != null) { ArrayList list = new ArrayList<>(); for (Trigger trigger : triggers) { list.add(toCompositeData(trigger)); } tData.putAll(list.toArray(new CompositeData[list.size()])); } return tData; } public static List toCompositeList(List triggers) { List result = new ArrayList<>(); for(Trigger trigger : triggers) { CompositeData cData = TriggerSupport.toCompositeData(trigger); result.add(cData); } return result; } public static void initializeTrigger(MutableTrigger trigger, CompositeData cData) { trigger.setDescription((String) cData.get("description")); trigger.setCalendarName((String) cData.get("calendarName")); if(cData.containsKey("priority")) { trigger.setPriority((Integer) cData.get("priority")); } if(cData.containsKey("jobDataMap")) { trigger.setJobDataMap(JobDataMapSupport.newJobDataMap((TabularData)cData.get("jobDataMap"))); } Date startTime; if(cData.containsKey("startTime")) { startTime = (Date) cData.get("startTime"); } else { startTime = new Date(); } trigger.setStartTime(startTime); trigger.setEndTime((Date) cData.get("endTime")); if(cData.containsKey("misfireInstruction")) { trigger.setMisfireInstruction((Integer) cData.get("misfireInstruction")); } trigger.setKey(new TriggerKey((String) cData.get("name"), (String) cData.get("group"))); trigger.setJobKey(new JobKey((String) cData.get("jobName"), (String) cData.get("jobGroup"))); } public static void initializeTrigger(MutableTrigger trigger, Map attrMap) { trigger.setDescription((String) attrMap.get("description")); trigger.setCalendarName((String) attrMap.get("calendarName")); if(attrMap.containsKey("priority")) { trigger.setPriority((Integer) attrMap.get("priority")); } if(attrMap.containsKey("jobDataMap")) { @SuppressWarnings("unchecked") // cast as expected. Map mapTyped = (Map)attrMap.get("jobDataMap"); trigger.setJobDataMap(JobDataMapSupport.newJobDataMap(mapTyped)); } Date startTime; if(attrMap.containsKey("startTime")) { startTime = (Date) attrMap.get("startTime"); } else { startTime = new Date(); } trigger.setStartTime(startTime); if(attrMap.containsKey("endTime")) { trigger.setEndTime((Date) attrMap.get("endTime")); } if(attrMap.containsKey("misfireInstruction")) { trigger.setMisfireInstruction((Integer) attrMap.get("misfireInstruction")); } trigger.setKey(new TriggerKey((String) attrMap.get("name"), (String) attrMap.get("group"))); trigger.setJobKey(new JobKey((String) attrMap.get("jobName"), (String) attrMap.get("jobGroup"))); } public static OperableTrigger newTrigger(CompositeData cData) throws ParseException { OperableTrigger result; if(cData.containsKey("cronExpression")) { result = CronTriggerSupport.newTrigger(cData); } else { result = SimpleTriggerSupport.newTrigger(cData); } return result; } public static OperableTrigger newTrigger(Map attrMap) throws ParseException { OperableTrigger result; if(attrMap.containsKey("cronExpression")) { result = CronTriggerSupport.newTrigger(attrMap); } else { result = SimpleTriggerSupport.newTrigger(attrMap); } return result; } } ================================================ FILE: quartz/src/main/java/org/quartz/core/mbeans-descriptors.xml ================================================ ================================================ FILE: quartz/src/main/java/org/quartz/core/package.html ================================================ Package org.quartz.core

Contains the core classes and interfaces for the Quartz job scheduler.




See the Quartz project for more information. ================================================ FILE: quartz/src/main/java/org/quartz/ee/jboss/doc-files/quartz-service.xml ================================================ jboss.jca:service=DataSourceBinding,name=QuartzDS # Default Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance, if a different # properties file is not explicitly specified. # # org.quartz.scheduler.classLoadHelper.class = org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.xaTransacted = false org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 4 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource = QUARTZ org.quartz.jobStore.nonManagedTXDataSource = QUARTZ_NO_TX org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.dataSource.QUARTZ.jndiURL = java:/jdbc/QuartzDS org.quartz.dataSource.QUARTZ_NO_TX.jndiURL = java:/jdbc/QuartzNoTxDS ================================================ FILE: quartz/src/main/java/org/quartz/ee/jmx/jboss/JBoss4RMIRemoteMBeanScheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jmx.jboss; import org.quartz.SchedulerException; import org.quartz.impl.RemoteMBeanScheduler; import javax.management.AttributeList; import javax.management.MBeanServerConnection; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Arrays; import java.util.Properties; /** *

* An implementation of the Scheduler interface that remotely * proxies all method calls to the equivalent call on a given QuartzScheduler * instance, via JBoss's JMX RMIAdaptor. *

* *

* Set the providerURL property to your MBean server URL. * This defaults to: jnp://localhost:1099 *

* * @see org.quartz.Scheduler * @see org.quartz.core.QuartzScheduler */ public class JBoss4RMIRemoteMBeanScheduler extends RemoteMBeanScheduler { private static final String DEFAULT_PROVIDER_URL = "jnp://localhost:1099"; private static final String RMI_ADAPTOR_JNDI_NAME = "jmx/rmi/RMIAdaptor"; private MBeanServerConnection server = null; private String providerURL = DEFAULT_PROVIDER_URL; public JBoss4RMIRemoteMBeanScheduler() throws SchedulerException { } /** * Set the remote MBean server URL. * * Defaults to: jnp://localhost:1099 */ public void setProviderURL(String providerURL) { this.providerURL = providerURL; } /** * Initialize this remote MBean scheduler, getting the JBoss * RMIAdaptor for communication. */ @Override public void initialize() throws SchedulerException { InitialContext ctx = null; try { ctx = new InitialContext(getContextProperties()); server = (MBeanServerConnection)ctx.lookup(RMI_ADAPTOR_JNDI_NAME); } catch (Exception e) { throw new SchedulerException("Failed to lookup JBoss JMX RMI Adaptor.", e); } finally { if (ctx != null) { try { ctx.close(); } catch (NamingException ignore) { } } } } /** * Get the properties to use when creating a JNDI InitialContext. * *

* This method is broken out so it can be extended to pass credentials * or other properties not currently supported. *

*/ protected Properties getContextProperties() { Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); props.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); props.put(Context.PROVIDER_URL, providerURL); return props; } @Override protected Object getAttribute(String attribute) throws SchedulerException { try { return server.getAttribute(getSchedulerObjectName(), attribute); } catch (Exception e) { throw new SchedulerException("Failed to get Scheduler MBean attribute: " + attribute, e); } } @Override protected AttributeList getAttributes(String[] attributes) throws SchedulerException { try { return server.getAttributes(getSchedulerObjectName(), attributes); } catch (Exception e) { throw new SchedulerException("Failed to get Scheduler MBean attributes: " + Arrays.asList(attributes), e); } } @Override protected Object invoke(String operationName, Object[] params, String[] signature) throws SchedulerException { try { return server.invoke(getSchedulerObjectName(), operationName, params, signature); } catch (Exception e) { throw new SchedulerException( "Failed to invoke Scheduler MBean operation: " + operationName, e); } } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/jmx/jboss/QuartzService.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jmx.jboss; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.Properties; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NamingException; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.impl.StdSchedulerFactory; import org.jboss.naming.NonSerializableFactory; import org.jboss.system.ServiceMBeanSupport; /** * JBoss specific MBean implementation for configuring, starting, and * binding to JNDI a Quartz Scheduler instance. * *

* Sample MBean deployment descriptor: * quartz-service.xml *

* *

* Note: The Scheduler instance bound to JNDI is not Serializable, so * you will get a null reference back if you try to retrieve it from outside * the JBoss server in which it was bound. If you have a need for remote * access to a Scheduler instance you may want to consider using Quartz's RMI * support instead. *

* * @see org.quartz.ee.jmx.jboss.QuartzServiceMBean * * @author Andrew Collins */ public class QuartzService extends ServiceMBeanSupport implements QuartzServiceMBean { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Properties properties; private StdSchedulerFactory schedulerFactory; private String jndiName; private String propertiesFile; private boolean error; private boolean useProperties; private boolean usePropertiesFile; /* * If true, the scheduler will be started. If false, the scheduler is initialized * (and available) but start() is not called - it will not execute jobs. */ private boolean startScheduler = true; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public QuartzService() { // flag initialization errors error = false; // use PropertiesFile attribute usePropertiesFile = false; propertiesFile = ""; // use Properties attribute useProperties = false; properties = new Properties(); // default JNDI name for Scheduler jndiName = "Quartz"; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public void setJndiName(String jndiName) throws Exception { String oldName = this.jndiName; this.jndiName = jndiName; if (super.getState() == STARTED) { unbind(oldName); try { rebind(); } catch (NamingException ne) { log.error("Failed to rebind Scheduler", ne); throw new SchedulerConfigException( "Failed to rebind Scheduler - ", ne); } } } public String getJndiName() { return jndiName; } @Override public String getName() { return "QuartzService(" + jndiName + ")"; } public void setProperties(String properties) { if (usePropertiesFile) { log .error("Must specify only one of 'Properties' or 'PropertiesFile'"); error = true; return; } useProperties = true; try { properties = properties.replace(File.separator, "/"); ByteArrayInputStream bais = new ByteArrayInputStream(properties .getBytes()); this.properties = new Properties(); this.properties.load(bais); } catch (IOException ioe) { // should not happen } } public String getProperties() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); properties.store(baos, ""); return new String(baos.toByteArray()); } catch (IOException ioe) { // should not happen return ""; } } public void setPropertiesFile(String propertiesFile) { if (useProperties) { log .error("Must specify only one of 'Properties' or 'PropertiesFile'"); error = true; return; } usePropertiesFile = true; this.propertiesFile = propertiesFile; } public String getPropertiesFile() { return propertiesFile; } public void setStartScheduler(boolean startScheduler) { this.startScheduler = startScheduler; } public boolean getStartScheduler() { return startScheduler; } @Override public void createService() throws Exception { log.info("Create QuartzService(" + jndiName + ")..."); if (error) { log .error("Must specify only one of 'Properties' or 'PropertiesFile'"); throw new Exception( "Must specify only one of 'Properties' or 'PropertiesFile'"); } schedulerFactory = new StdSchedulerFactory(); try { if (useProperties) { schedulerFactory.initialize(properties); } if (usePropertiesFile) { schedulerFactory.initialize(propertiesFile); } } catch (Exception e) { log.error("Failed to initialize Scheduler", e); throw new SchedulerConfigException( "Failed to initialize Scheduler - ", e); } log.info("QuartzService(" + jndiName + ") created."); } @Override public void destroyService() throws Exception { log.info("Destroy QuartzService(" + jndiName + ")..."); schedulerFactory = null; log.info("QuartzService(" + jndiName + ") destroyed."); } @Override public void startService() throws Exception { log.info("Start QuartzService(" + jndiName + ")..."); try { rebind(); } catch (NamingException ne) { log.error("Failed to rebind Scheduler", ne); throw new SchedulerConfigException("Failed to rebind Scheduler - ", ne); } try { Scheduler scheduler = schedulerFactory.getScheduler(); if (startScheduler) { scheduler.start(); } else { log.info("Skipping starting the scheduler (will not run jobs)."); } } catch (Exception e) { log.error("Failed to start Scheduler", e); throw new SchedulerConfigException("Failed to start Scheduler - ", e); } log.info("QuartzService(" + jndiName + ") started."); } @Override public void stopService() throws Exception { log.info("Stop QuartzService(" + jndiName + ")..."); try { Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.shutdown(); } catch (Exception e) { log.error("Failed to shutdown Scheduler", e); throw new SchedulerConfigException( "Failed to shutdown Scheduler - ", e); } unbind(jndiName); log.info("QuartzService(" + jndiName + ") stopped."); } private void rebind() throws NamingException, SchedulerException { InitialContext rootCtx = null; try { rootCtx = new InitialContext(); Name fullName = rootCtx.getNameParser("").parse(jndiName); Scheduler scheduler = schedulerFactory.getScheduler(); NonSerializableFactory.rebind(fullName, scheduler, true); } finally { if (rootCtx != null) { try { rootCtx.close(); } catch (NamingException ignore) {} } } } private void unbind(String name) { InitialContext rootCtx = null; try { rootCtx = new InitialContext(); rootCtx.unbind(name); NonSerializableFactory.unbind(name); } catch (NamingException e) { log.warn("Failed to unbind scheduler with jndiName: " + name, e); } finally { if (rootCtx != null) { try { rootCtx.close(); } catch (NamingException ignore) {} } } } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/jmx/jboss/QuartzServiceMBean.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jmx.jboss; import org.jboss.system.ServiceMBean; /** * Interface exposed via JMX for MBean for configuring, starting, and * binding to JNDI a Quartz Scheduler instance. * *

* Sample MBean deployment descriptor: * quartz-service.xml *

* *

* Note: The Scheduler instance bound to JNDI is not Serializable, so * you will get a null reference back if you try to retrieve it from outside * the JBoss server in which it was bound. If you have a need for remote * access to a Scheduler instance you may want to consider using Quartz's RMI * support instead. *

* * @see org.quartz.ee.jmx.jboss.QuartzService * * @author Andrew Collins */ public interface QuartzServiceMBean extends ServiceMBean { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ void setJndiName(String jndiName) throws Exception; String getJndiName(); void setProperties(String properties); void setPropertiesFile(String propertiesFile); void setStartScheduler(boolean startScheduler); } ================================================ FILE: quartz/src/main/java/org/quartz/ee/jta/JTAAnnotationAwareJobRunShellFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jta; import org.quartz.ExecuteInJTATransaction; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.core.JobRunShell; import org.quartz.core.JobRunShellFactory; import org.quartz.spi.TriggerFiredBundle; import org.quartz.utils.ClassUtils; /** *

* Responsible for creating the instances of a {@link JobRunShell} * to be used within the {@link org.quartz.core.QuartzScheduler} * instance. It will create a standard {@link JobRunShell} * unless the job class has the {@link ExecuteInJTATransaction} * annotation in which case it will create a {@link JTAJobRunShell}. *

* *

* This implementation does not re-use any objects, it simply makes a new * JTAJobRunShell each time borrowJobRunShell() is called. *

* * @author James House */ public class JTAAnnotationAwareJobRunShellFactory implements JobRunShellFactory { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Scheduler scheduler; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public JTAAnnotationAwareJobRunShellFactory() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 sched) throws SchedulerConfigException { this.scheduler = sched; } /** *

* Called by the {@link org.quartz.core.QuartzSchedulerThread} * to obtain instances of {@link org.quartz.core.JobRunShell}. *

*/ public JobRunShell createJobRunShell(TriggerFiredBundle bundle) throws SchedulerException { ExecuteInJTATransaction jtaAnnotation = ClassUtils.getAnnotation(bundle.getJobDetail().getJobClass(), ExecuteInJTATransaction.class); if(jtaAnnotation == null) return new JobRunShell(scheduler, bundle); else { int timeout = jtaAnnotation.timeout(); if (timeout >= 0) { return new JTAJobRunShell(scheduler, bundle, timeout); } else { return new JTAJobRunShell(scheduler, bundle); } } } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/jta/JTAJobRunShell.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jta; import jakarta.transaction.Status; import jakarta.transaction.SystemException; import jakarta.transaction.UserTransaction; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.core.JobRunShell; import org.quartz.spi.TriggerFiredBundle; /** *

* An extension of {@link org.quartz.core.JobRunShell} that * begins an XA transaction before executing the Job, and commits (or * rolls-back) the transaction after execution completes. *

* * @see org.quartz.core.JobRunShell * * @author James House */ public class JTAJobRunShell extends JobRunShell { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final Integer transactionTimeout; private UserTransaction ut; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a JTAJobRunShell instance with the given settings. *

*/ public JTAJobRunShell(Scheduler scheduler, TriggerFiredBundle bundle) { super(scheduler, bundle); this.transactionTimeout = null; } /** *

* Create a JTAJobRunShell instance with the given settings. *

*/ public JTAJobRunShell(Scheduler scheduler, TriggerFiredBundle bundle, int timeout) { super(scheduler, bundle); this.transactionTimeout = timeout; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @Override 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(); if (transactionTimeout != null) { ut.setTransactionTimeout(transactionTimeout); } 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) { cleanupUserTransaction(); } } } @Override 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. */ @Override public void passivate() { cleanupUserTransaction(); super.passivate(); } private void cleanupUserTransaction() { if (ut != null) { UserTransactionHelper.returnUserTransaction(ut); ut = null; } } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/jta/JTAJobRunShellFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jta; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.core.JobRunShell; import org.quartz.core.JobRunShellFactory; import org.quartz.spi.TriggerFiredBundle; /** *

* Responsible for creating the instances of {@link org.quartz.ee.jta.JTAJobRunShell} * to be used within the {@link 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; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 sched) throws SchedulerConfigException { this.scheduler = sched; } /** *

* Called by the {@link org.quartz.core.QuartzSchedulerThread} * to obtain instances of {@link org.quartz.core.JobRunShell}. *

*/ public JobRunShell createJobRunShell(TriggerFiredBundle bundle) throws SchedulerException { return new JTAJobRunShell(scheduler, bundle); } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/jta/UserTransactionHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.jta; import javax.naming.InitialContext; import jakarta.transaction.HeuristicMixedException; import jakarta.transaction.HeuristicRollbackException; import jakarta.transaction.NotSupportedException; import jakarta.transaction.RollbackException; import jakarta.transaction.SystemException; import jakarta.transaction.UserTransaction; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* 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; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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. */ @Override protected void finalize() throws Throwable { try { if (context != null) { getLog().warn("UserTransaction was never returned to the UserTransactionHelper."); closeContext(); } } finally { super.finalize(); } } private static Logger getLog() { return LoggerFactory.getLogger(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); } } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/servlet/QuartzInitializerListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.servlet; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.impl.StdSchedulerFactory; /** *

* A ServletContextListener that can be used to initialize Quartz. *

* *

* You'll want to add something like this to your WEB-INF/web.xml file: *

*
 *     <context-param>
 *         <param-name>quartz:config-file</param-name>
 *         <param-value>/some/path/my_quartz.properties</param-value>
 *     </context-param>
 *     <context-param>
 *         <param-name>quartz:shutdown-on-unload</param-name>
 *         <param-value>true</param-value>
 *     </context-param>
 *     <context-param>
 *         <param-name>quartz:wait-on-shutdown</param-name>
 *         <param-value>true</param-value>
 *     </context-param>
 *     <context-param>
 *         <param-name>quartz:start-on-load</param-name>
 *         <param-value>true</param-value>
 *     </context-param>
 *     
 *     <listener>
 *         <listener-class>
 *             org.quartz.ee.servlet.QuartzInitializerListener
 *         </listener-class>
 *     </listener>
 * 
*

* The init parameter 'quartz:config-file' can be used to specify the path (and * filename) of your Quartz properties file. If you leave out this parameter, * the default ("quartz.properties") will be used. *

* *

* The init parameter 'quartz:shutdown-on-unload' can be used to specify whether you * want scheduler.shutdown() called when the listener is unloaded (usually when * the application server is being shutdown). Possible values are "true" or * "false". The default is "true". *

* *

* The init parameter 'quartz:wait-on-shutdown' has effect when * 'quartz:shutdown-on-unload' is specified "true", and indicates whether you * want scheduler.shutdown(true) called when the listener is unloaded (usually when * the application server is being shutdown). Passing "true" to the shutdown() call * causes the scheduler to wait for existing jobs to complete. Possible values are * "true" or "false". The default is "false". *

* *

* The init parameter 'quartz:start-on-load' can be used to specify whether * you want the scheduler.start() method called when the listener is first loaded. * If set to false, your application will need to call the start() method before * the scheduler begins to run and process jobs. Possible values are "true" or * "false". The default is "true", which means the scheduler is started. *

* * A StdSchedulerFactory instance is stored into the ServletContext. You can gain access * to the factory from a ServletContext instance like this: *
*
 * StdSchedulerFactory factory = (StdSchedulerFactory) ctx
 *                .getAttribute(QuartzInitializerListener.QUARTZ_FACTORY_KEY);
*

* The init parameter 'quartz:servlet-context-factory-key' can be used to override the * name under which the StdSchedulerFactory is stored into the ServletContext, in * which case you will want to use this name rather than * QuartzInitializerListener.QUARTZ_FACTORY_KEY in the above example. *

* *

* The init parameter 'quartz:scheduler-context-servlet-context-key' if set, the * ServletContext will be stored in the SchedulerContext under the given key * name (and will therefore be available to jobs during execution). *

* *

* The init parameter 'quartz:start-delay-seconds' can be used to specify the amount * of time to wait after initializing the scheduler before scheduler.start() * is called. *

* * Once you have the factory instance, you can retrieve the Scheduler instance by calling * getScheduler() on the factory. * * @author James House * @author Chuck Cavaness * @author John Petrocik */ public class QuartzInitializerListener implements ServletContextListener { public static final String QUARTZ_FACTORY_KEY = "org.quartz.impl.StdSchedulerFactory.KEY"; private boolean performShutdown = true; private boolean waitOnShutdown = false; private Scheduler scheduler = null; private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public void contextInitialized(ServletContextEvent sce) { log.info("Quartz Initializer Servlet loaded, initializing Scheduler..."); ServletContext servletContext = sce.getServletContext(); StdSchedulerFactory factory; try { String configFile = servletContext.getInitParameter("quartz:config-file"); if(configFile == null) configFile = servletContext.getInitParameter("config-file"); // older name, for backward compatibility String shutdownPref = servletContext.getInitParameter("quartz:shutdown-on-unload"); if(shutdownPref == null) shutdownPref = servletContext.getInitParameter("shutdown-on-unload"); if (shutdownPref != null) { performShutdown = Boolean.parseBoolean(shutdownPref); } String shutdownWaitPref = servletContext.getInitParameter("quartz:wait-on-shutdown"); if (shutdownWaitPref != null) { waitOnShutdown = Boolean.parseBoolean(shutdownWaitPref); } factory = getSchedulerFactory(configFile); // Always want to get the scheduler, even if it isn't starting, // to make sure it is both initialized and registered. scheduler = factory.getScheduler(); // Should the Scheduler being started now or later String startOnLoad = servletContext.getInitParameter("quartz:start-on-load"); if(startOnLoad == null) startOnLoad = servletContext.getInitParameter("start-scheduler-on-load"); int startDelay = 0; String startDelayS = servletContext.getInitParameter("quartz:start-delay-seconds"); if(startDelayS == null) startDelayS = servletContext.getInitParameter("start-delay-seconds"); try { if(startDelayS != null && !startDelayS.trim().isEmpty()) startDelay = Integer.parseInt(startDelayS); } catch(Exception e) { log.error("Cannot parse value of 'start-delay-seconds' to an integer: {}, defaulting to 5 seconds.", startDelayS); startDelay = 5; } /* * If the "quartz:start-on-load" init-parameter is not specified, * the scheduler will be started. This is to maintain backwards * compatability. */ if (startOnLoad == null || (Boolean.parseBoolean(startOnLoad))) { if(startDelay <= 0) { // Start now scheduler.start(); log.info("Scheduler has been started..."); } else { // Start delayed scheduler.startDelayed(startDelay); log.info("Scheduler will start in {} seconds.", startDelay); } } else { log.info("Scheduler has not been started. Use scheduler.start()"); } String factoryKey = servletContext.getInitParameter("quartz:servlet-context-factory-key"); if(factoryKey == null) factoryKey = servletContext.getInitParameter("servlet-context-factory-key"); if (factoryKey == null) { factoryKey = QUARTZ_FACTORY_KEY; } log.info("Storing the Quartz Scheduler Factory in the servlet context at key: {}", factoryKey); servletContext.setAttribute(factoryKey, factory); String servletCtxKey = servletContext.getInitParameter("quartz:scheduler-context-servlet-context-key"); if(servletCtxKey == null) servletCtxKey = servletContext.getInitParameter("scheduler-context-servlet-context-key"); if (servletCtxKey != null) { log.info("Storing the ServletContext in the scheduler context at key: {}", servletCtxKey); scheduler.getContext().put(servletCtxKey, servletContext); } } catch (Exception e) { log.error("Quartz Scheduler failed to initialize: {}", String.valueOf(e)); e.printStackTrace(); } } protected StdSchedulerFactory getSchedulerFactory(String configFile) throws SchedulerException { StdSchedulerFactory factory; // get Properties if (configFile != null) { factory = new StdSchedulerFactory(configFile); } else { factory = new StdSchedulerFactory(); } return factory; } public void contextDestroyed(ServletContextEvent sce) { if (!performShutdown) { return; } try { if (scheduler != null) { scheduler.shutdown(waitOnShutdown); } } catch (Exception e) { log.error("Quartz Scheduler failed to shutdown cleanly: {}", String.valueOf(e)); e.printStackTrace(); } log.info("Quartz Scheduler successful shutdown."); } } ================================================ FILE: quartz/src/main/java/org/quartz/ee/servlet/QuartzInitializerServlet.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.ee.servlet; import java.io.IOException; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.impl.StdSchedulerFactory; /** *

* A Servlet that can be used to initialize Quartz, if configured as a * load-on-startup servlet in a web application. *

* *

Using this start-up servlet may be preferred to using the {@link QuartzInitializerListener} * in some situations - namely when you want to initialize more than one scheduler in the same * application.

* *

* You'll want to add something like this to your WEB-INF/web.xml file: *

*
 *     <servlet>
 *         <servlet-name>
 *             QuartzInitializer
 *         </servlet-name>
 *         <display-name>
 *             Quartz Initializer Servlet
 *         </display-name>
 *         <servlet-class>
 *             org.quartz.ee.servlet.QuartzInitializerServlet
 *         </servlet-class>
 *         <load-on-startup>
 *             1
 *         </load-on-startup>
 *         <init-param>
 *             <param-name>config-file</param-name>
 *             <param-value>/some/path/my_quartz.properties</param-value>
 *         </init-param>
 *         <init-param>
 *             <param-name>shutdown-on-unload</param-name>
 *             <param-value>true</param-value>
 *         </init-param>
 *         <init-param>
 *             <param-name>wait-on-shutdown</param-name>
 *             <param-value>true</param-value>
 *         </init-param>
 *         <init-param>
 *             <param-name>start-scheduler-on-load</param-name>
 *             <param-value>true</param-value>
 *         </init-param>
 *     </servlet>
 * 
*

* The init parameter 'config-file' can be used to specify the path (and * filename) of your Quartz properties file. If you leave out this parameter, * the default ("quartz.properties") will be used. *

* *

* The init parameter 'shutdown-on-unload' can be used to specify whether you * want scheduler.shutdown() called when the servlet is unloaded (usually when * the application server is being shutdown). Possible values are "true" or * "false". The default is "true". *

* *

* The init parameter 'wait-on-shutdown' has effect when * 'shutdown-on-unload' is specified "true", and indicates whether you * want scheduler.shutdown(true) called when the listener is unloaded (usually when * the application server is being shutdown). Passing "true" to the shutdown() call * causes the scheduler to wait for existing jobs to complete. Possible values are * "true" or "false". The default is "false". *

* *

* The init parameter 'start-scheduler-on-load' can be used to specify whether * you want the scheduler.start() method called when the servlet is first loaded. * If set to false, your application will need to call the start() method before * the scheduler begins to run and process jobs. Possible values are "true" or * "false". The default is "true", which means the scheduler is started. *

* * A StdSchedulerFactory instance is stored into the ServletContext. You can gain access * to the factory from a ServletContext instance like this: *
*
 *     StdSchedulerFactory factory = (StdSchedulerFactory) ctx
 *                .getAttribute(QuartzFactoryServlet.QUARTZ_FACTORY_KEY);
*

* The init parameter 'servlet-context-factory-key' can be used to override the * name under which the StdSchedulerFactory is stored into the ServletContext, in * which case you will want to use this name rather than * QuartzFactoryServlet.QUARTZ_FACTORY_KEY in the above example. *

* *

* The init parameter 'scheduler-context-servlet-context-key' if set, the * ServletContext will be stored in the SchedulerContext under the given key * name (and will therefore be available to jobs during execution). *

* *

* The init parameter 'start-delay-seconds' can be used to specify the amount * of time to wait after initializing the scheduler before scheduler.start() * is called. *

* * Once you have the factory instance, you can retrieve the Scheduler instance by calling * getScheduler() on the factory. * * @author James House * @author Chuck Cavaness */ public class QuartzInitializerServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; public static final String QUARTZ_FACTORY_KEY = "org.quartz.impl.StdSchedulerFactory.KEY"; private boolean performShutdown = true; private boolean waitOnShutdown = false; private transient Scheduler scheduler = null; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @Override public void init(ServletConfig cfg) throws jakarta.servlet.ServletException { super.init(cfg); log("Quartz Initializer Servlet loaded, initializing Scheduler..."); StdSchedulerFactory factory; try { String configFile = cfg.getInitParameter("config-file"); String shutdownPref = cfg.getInitParameter("shutdown-on-unload"); if (shutdownPref != null) { performShutdown = Boolean.parseBoolean(shutdownPref); } String shutdownWaitPref = cfg.getInitParameter("wait-on-shutdown"); if (shutdownPref != null) { waitOnShutdown = Boolean.parseBoolean(shutdownWaitPref); } factory = getSchedulerFactory(configFile); // Always want to get the scheduler, even if it isn't starting, // to make sure it is both initialized and registered. scheduler = factory.getScheduler(); // Should the Scheduler being started now or later String startOnLoad = cfg .getInitParameter("start-scheduler-on-load"); int startDelay = 0; String startDelayS = cfg.getInitParameter("start-delay-seconds"); try { if(startDelayS != null && !startDelayS.trim().isEmpty()) startDelay = Integer.parseInt(startDelayS); } catch(Exception e) { log("Cannot parse value of 'start-delay-seconds' to an integer: " + startDelayS + ", defaulting to 5 seconds.", e); startDelay = 5; } /* * If the "start-scheduler-on-load" init-parameter is not specified, * the scheduler will be started. This is to maintain backwards * compatability. */ if (startOnLoad == null || (Boolean.parseBoolean(startOnLoad))) { if(startDelay <= 0) { // Start now scheduler.start(); log("Scheduler has been started..."); } else { // Start delayed scheduler.startDelayed(startDelay); log("Scheduler will start in " + startDelay + " seconds."); } } else { log("Scheduler has not been started. Use scheduler.start()"); } String factoryKey = cfg.getInitParameter("servlet-context-factory-key"); if (factoryKey == null) { factoryKey = QUARTZ_FACTORY_KEY; } log("Storing the Quartz Scheduler Factory in the servlet context at key: " + factoryKey); cfg.getServletContext().setAttribute(factoryKey, factory); String servletCtxKey = cfg.getInitParameter("scheduler-context-servlet-context-key"); if (servletCtxKey != null) { log("Storing the ServletContext in the scheduler context at key: " + servletCtxKey); scheduler.getContext().put(servletCtxKey, cfg.getServletContext()); } } catch (Exception e) { log("Quartz Scheduler failed to initialize: " + e); throw new ServletException(e); } } protected StdSchedulerFactory getSchedulerFactory(String configFile) throws SchedulerException { StdSchedulerFactory factory; // get Properties if (configFile != null) { factory = new StdSchedulerFactory(configFile); } else { factory = new StdSchedulerFactory(); } return factory; } @Override public void destroy() { if (!performShutdown) { return; } try { if (scheduler != null) { scheduler.shutdown(waitOnShutdown); } } catch (Exception e) { log("Quartz Scheduler failed to shutdown cleanly: " + e); e.printStackTrace(); } log("Quartz Scheduler successful shutdown."); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.sendError(HttpServletResponse.SC_FORBIDDEN); } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.sendError(HttpServletResponse.SC_FORBIDDEN); } } ================================================ FILE: quartz/src/main/java/org/quartz/helpers/VersionPrinter.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.helpers; import 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()); } } ================================================ FILE: quartz/src/main/java/org/quartz/helpers/package.html ================================================ Package org.quartz.helpers

Contains helper classes to make working with Quartz easier.




See the Quartz project for more information. ================================================ FILE: quartz/src/main/java/org/quartz/impl/DefaultThreadExecutor.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import org.quartz.spi.ThreadExecutor; /** * Schedules work on a newly spawned thread. This is the default Quartz * behavior. * * @author matt.accola * @version $Revision$ $Date$ */ public class DefaultThreadExecutor implements ThreadExecutor { public void initialize() { } public void execute(Thread thread) { thread.start(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/DirectSchedulerFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.core.JobRunShellFactory; import org.quartz.core.QuartzScheduler; import org.quartz.core.QuartzSchedulerResources; import org.quartz.simpl.CascadingClassLoadHelper; import org.quartz.simpl.RAMJobStore; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.JobStore; import org.quartz.spi.SchedulerPlugin; import org.quartz.spi.ThreadExecutor; import org.quartz.spi.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* A singleton implementation of {@link 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();
 *
 *  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"));
 *
 *  JobStoreTX jdbcJobStore = new JobStoreTX(); jdbcJobStore.setDataSource("someDatasource"); jdbcJobStore.setPostgresStyleBlobs(true); jdbcJobStore.setTablePrefix("QRTZ_"); jdbcJobStore.setInstanceId("My Instance");
 * 
* * @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"; private static final boolean DEFAULT_JMX_EXPORT = false; private static final String DEFAULT_JMX_OBJECTNAME = null; private static final DefaultThreadExecutor DEFAULT_THREAD_EXECUTOR = new DefaultThreadExecutor(); private static final int DEFAULT_BATCH_MAX_SIZE = 1; private static final long DEFAULT_BATCH_TIME_WINDOW = 0L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private boolean initialized = false; private static final DirectSchedulerFactory instance = new DirectSchedulerFactory(); private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger 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); JobStore jobStore = new RAMJobStore(); this.createScheduler(threadPool, jobStore); } /** * 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); } /** * 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 { String uid = (rmiBindName != null) ? rmiBindName : QuartzSchedulerResources.getUniqueIdentifier( schedulerName, schedulerInstanceId); RemoteScheduler remoteScheduler = new RemoteScheduler(uid, rmiHost, rmiPort); SchedulerRepository schedRep = SchedulerRepository.getInstance(); schedRep.bind(remoteScheduler); initialized = true; } /** * 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); } /** * 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, DEFAULT_JMX_EXPORT, DEFAULT_JMX_OBJECTNAME); } /** * 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 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, boolean jmxExport, String jmxObjectName) throws SchedulerException { createScheduler(schedulerName, schedulerInstanceId, threadPool, DEFAULT_THREAD_EXECUTOR, jobStore, schedulerPluginMap, rmiRegistryHost, rmiRegistryPort, idleWaitTime, dbFailureRetryInterval, jmxExport, jmxObjectName); } /** * 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 threadExecutor * The thread executor for executing jobs * @param jobStore * The type of job store * @param schedulerPluginMap * Map from a String plugin names to * {@link 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, ThreadExecutor threadExecutor, JobStore jobStore, Map schedulerPluginMap, String rmiRegistryHost, int rmiRegistryPort, long idleWaitTime, long dbFailureRetryInterval, boolean jmxExport, String jmxObjectName) throws SchedulerException { createScheduler(schedulerName, schedulerInstanceId, threadPool, DEFAULT_THREAD_EXECUTOR, jobStore, schedulerPluginMap, rmiRegistryHost, rmiRegistryPort, idleWaitTime, dbFailureRetryInterval, jmxExport, jmxObjectName, DEFAULT_BATCH_MAX_SIZE, DEFAULT_BATCH_TIME_WINDOW); } /** * 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 threadExecutor * The thread executor for executing jobs * @param jobStore * The type of job store * @param schedulerPluginMap * Map from a String plugin names to * {@link 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. * @param maxBatchSize * The maximum batch size of triggers, when acquiring them * @param batchTimeWindow * The time window for which it is allowed to "pre-acquire" triggers to fire * @throws SchedulerException * if initialization failed */ public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, ThreadExecutor threadExecutor, JobStore jobStore, Map schedulerPluginMap, String rmiRegistryHost, int rmiRegistryPort, long idleWaitTime, long dbFailureRetryInterval, boolean jmxExport, String jmxObjectName, int maxBatchSize, long batchTimeWindow) throws SchedulerException { createScheduler(schedulerName, schedulerInstanceId, threadPool, threadExecutor, jobStore, schedulerPluginMap, rmiRegistryHost, rmiRegistryPort, idleWaitTime, dbFailureRetryInterval, jmxExport, jmxObjectName, maxBatchSize, batchTimeWindow, false); } /** * 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 threadExecutor * The thread executor for executing jobs * @param jobStore * The type of job store * @param schedulerPluginMap * Map from a String plugin names to * {@link 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. * @param maxBatchSize * The maximum batch size of triggers, when acquiring them * @param batchTimeWindow * The time window for which it is allowed to "pre-acquire" triggers to fire * @param makeSchedThreadDaemon * Make the SchedulerThread a daemon thread. * @throws SchedulerException * if initialization failed */ public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, ThreadExecutor threadExecutor, JobStore jobStore, Map schedulerPluginMap, String rmiRegistryHost, int rmiRegistryPort, long idleWaitTime, long dbFailureRetryInterval, boolean jmxExport, String jmxObjectName, int maxBatchSize, long batchTimeWindow, boolean makeSchedThreadDaemon) throws SchedulerException { // Currently only one run-shell factory is available... JobRunShellFactory jrsf = new StdJobRunShellFactory(); // Fire everything up // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ threadPool.setInstanceName(schedulerName); threadPool.initialize(); QuartzSchedulerResources qrs = new QuartzSchedulerResources(); qrs.setName(schedulerName); qrs.setInstanceId(schedulerInstanceId); qrs.setMakeSchedulerThreadDaemon(makeSchedThreadDaemon); SchedulerDetailsSetter.setDetails(threadPool, schedulerName, schedulerInstanceId); qrs.setJobRunShellFactory(jrsf); qrs.setThreadPool(threadPool); qrs.setThreadExecutor(threadExecutor); qrs.setJobStore(jobStore); qrs.setMaxBatchSize(maxBatchSize); qrs.setBatchTimeWindow(batchTimeWindow); qrs.setRMIRegistryHost(rmiRegistryHost); qrs.setRMIRegistryPort(rmiRegistryPort); qrs.setJMXExport(jmxExport); if (jmxObjectName != null) { qrs.setJMXObjectName(jmxObjectName); } // add plugins if (schedulerPluginMap != null) { for (SchedulerPlugin schedulerPlugin : schedulerPluginMap.values()) { qrs.addSchedulerPlugin(schedulerPlugin); } } QuartzScheduler qs = new QuartzScheduler(qrs, idleWaitTime, dbFailureRetryInterval); ClassLoadHelper cch = new CascadingClassLoadHelper(); cch.initialize(); SchedulerDetailsSetter.setDetails(jobStore, schedulerName, schedulerInstanceId); jobStore.initialize(cch, qs.getSchedulerSignaler()); Scheduler scheduler = new StdScheduler(qs); jrsf.initialize(scheduler); qs.initialize(); // Initialize plugins now that we have a Scheduler instance. if (schedulerPluginMap != null) { for (Entry pluginEntry : schedulerPluginMap.entrySet()) { pluginEntry.getValue().initialize(pluginEntry.getKey(), scheduler, cch); } } 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); initialized = true; } /* * 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(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/JobDetailImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.PersistJobDataAfterExecution; import org.quartz.Scheduler; import org.quartz.StatefulJob; import org.quartz.Trigger; import org.quartz.utils.ClassUtils; /** *

* 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 */ @SuppressWarnings("deprecation") public class JobDetailImpl implements Cloneable, java.io.Serializable, JobDetail { private static final long serialVersionUID = -6069784757781506897L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private String name; private String group = Scheduler.DEFAULT_GROUP; private String description; private Class jobClass; private JobDataMap jobDataMap; private boolean durability = false; private boolean shouldRecover = false; private transient JobKey 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 JobDetailImpl() { // do nothing... } /** *

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

* * @exception IllegalArgumentException * if name is null or empty, or the group is an empty string. * * @deprecated use {@link JobBuilder} */ @Deprecated public JobDetailImpl(String name, Class jobClass) { this(name, null, jobClass); } /** *

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

* * @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. * * @deprecated use {@link JobBuilder} */ @Deprecated public JobDetailImpl(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 name is null or empty, or the group is an empty string. * * @deprecated use {@link JobBuilder} */ @Deprecated public JobDetailImpl(String name, String group, Class jobClass, boolean durability, boolean recover) { setName(name); setGroup(group); setJobClass(jobClass); 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().isEmpty()) { throw new IllegalArgumentException("Job name cannot be empty."); } this.name = name; this.key = null; } /** *

* 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().isEmpty()) { throw new IllegalArgumentException( "Group name cannot be empty."); } if (group == null) { group = Scheduler.DEFAULT_GROUP; } this.group = group; this.key = null; } /** *

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

*/ public String getFullName() { return group + "." + name; } /* (non-Javadoc) * @see org.quartz.JobDetailI#getKey() */ public JobKey getKey() { if(key == null) { if(getName() == null) return null; key = new JobKey(getName(), getGroup()); } return key; } public void setKey(JobKey key) { if(key == null) throw new IllegalArgumentException("Key cannot be null!"); setName(key.getName()); setGroup(key.getGroup()); this.key = key; } /* (non-Javadoc) * @see org.quartz.JobDetailI#getDescription() */ 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; } /* (non-Javadoc) * @see org.quartz.JobDetailI#getJobClass() */ 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; } /* (non-Javadoc) * @see org.quartz.JobDetailI#getJobDataMap() */ 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; } /** *

* 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 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; } /* (non-Javadoc) * @see org.quartz.JobDetailI#isDurable() */ public boolean isDurable() { return durability; } /** * @return whether the associated Job class carries the {@link PersistJobDataAfterExecution} annotation. */ public boolean isPersistJobDataAfterExecution() { return ClassUtils.isAnnotationPresent(jobClass, PersistJobDataAfterExecution.class); } /** * @return whether the associated Job class carries the {@link DisallowConcurrentExecution} annotation. */ public boolean isConcurrentExecutionDisallowed() { return ClassUtils.isAnnotationPresent(jobClass, DisallowConcurrentExecution.class); } /* (non-Javadoc) * @see org.quartz.JobDetailI#requestsRecovery() */ public boolean requestsRecovery() { return shouldRecover; } /** *

* Return a simple string representation of this object. *

*/ @Override public String toString() { return "JobDetail '" + getFullName() + "': jobClass: '" + ((getJobClass() == null) ? null : getJobClass().getName()) + " concurrentExecutionDisallowed: " + isConcurrentExecutionDisallowed() + " persistJobDataAfterExecution: " + isPersistJobDataAfterExecution() + " isDurable: " + isDurable() + " requestsRecovers: " + requestsRecovery(); } @Override public boolean equals(Object obj) { if (!(obj instanceof JobDetail)) { return false; } JobDetail other = (JobDetail) obj; if(other.getKey() == null || getKey() == null) return false; return other.getKey().equals(getKey()); } @Override public int hashCode() { JobKey key = getKey(); return key == null ? 0 : getKey().hashCode(); } @Override public Object clone() { JobDetailImpl copy; try { copy = (JobDetailImpl) super.clone(); if (jobDataMap != null) { copy.jobDataMap = (JobDataMap) jobDataMap.clone(); } } catch (CloneNotSupportedException ex) { throw new IncompatibleClassChangeError("Not Cloneable."); } return copy; } public JobBuilder getJobBuilder() { return JobBuilder.newJob() .ofType(getJobClass()) .requestRecovery(requestsRecovery()) .storeDurably(isDurable()) .usingJobData(getJobDataMap()) .withDescription(getDescription()) .withIdentity(getKey()); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/JobExecutionContextImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.util.Date; import java.util.HashMap; import org.quartz.Calendar; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.spi.OperableTrigger; import org.quartz.spi.TriggerFiredBundle; public class JobExecutionContextImpl implements java.io.Serializable, JobExecutionContext { private static final long serialVersionUID = -8139417614523942021L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final transient Scheduler scheduler; private final Trigger trigger; private final JobDetail jobDetail; private final JobDataMap jobDataMap; private final transient Job job; private final Calendar calendar; private boolean recovering; private int numRefires = 0; private final Date fireTime; private final Date scheduledFireTime; private final Date prevFireTime; private final Date nextFireTime; private long jobRunTime = -1; private Object result; private final HashMap data = new HashMap<>(); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a JobExecutionContext with the given context data. *

*/ public JobExecutionContextImpl(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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * {@inheritDoc} */ public Scheduler getScheduler() { return scheduler; } /** * {@inheritDoc} */ public Trigger getTrigger() { return trigger; } /** * {@inheritDoc} */ public Calendar getCalendar() { return calendar; } /** * {@inheritDoc} */ public boolean isRecovering() { return recovering; } public TriggerKey getRecoveringTriggerKey() { if (isRecovering()) { return new TriggerKey(jobDataMap.getString(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_NAME), jobDataMap.getString(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_GROUP)); } else { throw new IllegalStateException("Not a recovering job"); } } public void incrementRefireCount() { numRefires++; } /** * {@inheritDoc} */ public int getRefireCount() { return numRefires; } /** * {@inheritDoc} */ public JobDataMap getMergedJobDataMap() { return jobDataMap; } /** * {@inheritDoc} */ public JobDetail getJobDetail() { return jobDetail; } /** * {@inheritDoc} */ public Job getJobInstance() { return job; } /** * {@inheritDoc} */ public Date getFireTime() { return fireTime; } /** * {@inheritDoc} */ public Date getScheduledFireTime() { return scheduledFireTime; } /** * {@inheritDoc} */ public Date getPreviousFireTime() { return prevFireTime; } /** * {@inheritDoc} */ public Date getNextFireTime() { return nextFireTime; } @Override public String toString() { return "JobExecutionContext:" + " trigger: '" + getTrigger().getKey() + " job: " + getJobDetail().getKey() + " fireTime: '" + getFireTime() + " scheduledFireTime: " + getScheduledFireTime() + " previousFireTime: '" + getPreviousFireTime() + " nextFireTime: " + getNextFireTime() + " isRecovering: " + isRecovering() + " refireCount: " + getRefireCount(); } /** * {@inheritDoc} */ public Object getResult() { return result; } /** * {@inheritDoc} */ public void setResult(Object result) { this.result = result; } /** * {@inheritDoc} */ public long getJobRunTime() { return jobRunTime; } /** * @param jobRunTime The jobRunTime to set. */ public void setJobRunTime(long jobRunTime) { this.jobRunTime = jobRunTime; } /** * {@inheritDoc} */ public void put(Object key, Object value) { data.put(key, value); } /** * {@inheritDoc} */ public Object get(Object key) { return data.get(key); } /** * {@inheritDoc} */ public String getFireInstanceId() { return ((OperableTrigger)trigger).getFireInstanceId(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/QuartzServer.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.io.BufferedReader; import java.io.InputStreamReader; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import 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 arguments, 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 org.quartz.JobListener}, * {@link org.quartz.TriggerListener} and {@link 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 * occurred 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. *

*/ @Override 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. *

*/ @Override 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 org.quartz.impl.StdSchedulerFactory(), false); } else if (args.length == 1 && args[0].equalsIgnoreCase("console")) { server.serve(new org.quartz.impl.StdSchedulerFactory(), true); } else { System.err.println("\nUsage: QuartzServer [console]"); } } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/RemoteMBeanScheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import org.quartz.Calendar; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.ListenerManager; import org.quartz.Scheduler; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.UnableToInterruptJobException; import org.quartz.Trigger.TriggerState; import org.quartz.core.jmx.JobDetailSupport; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.matchers.StringMatcher; import 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. *

* @see org.quartz.Scheduler * @see org.quartz.core.QuartzScheduler */ public abstract class RemoteMBeanScheduler implements Scheduler { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 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; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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", "StandbyMode", "Shutdown", "JobStoreClassName", "ThreadPoolClassName", "ThreadPoolSize", "Version", "PerformanceMetrics" }); try { return new SchedulerMetaData( (String)getAttribute(attributeList, 0).getValue(), (String)getAttribute(attributeList, 1).getValue(), getClass(), true, false, (Boolean)getAttribute(attributeList, 2).getValue(), (Boolean)getAttribute(attributeList, 3).getValue(), null, Integer.parseInt(((Map)getAttribute(attributeList, 8).getValue()).get("JobsExecuted").toString()), Class.forName((String)getAttribute(attributeList, 4).getValue()), false, false, Class.forName((String)getAttribute(attributeList, 5).getValue()), (Integer)getAttribute(attributeList, 6).getValue(), (String)getAttribute(attributeList, 7).getValue()); } catch (ClassNotFoundException e) { throw new SchedulerException(e); } } private Attribute getAttribute(AttributeList attributeList, int index) { return (Attribute)attributeList.get(index); } /** *

* Returns the SchedulerContext of the Scheduler. *

*/ public SchedulerContext getContext() throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /////////////////////////////////////////////////////////////////////////// /// /// Scheduler 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[] {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[] {}); } /** * 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 (Boolean) getAttribute("Started"); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean isInStandbyMode() throws SchedulerException { return (Boolean)getAttribute("StandbyMode"); } /** *

* 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 { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean isShutdown() throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ @SuppressWarnings("unchecked") public List getCurrentlyExecutingJobs() throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /////////////////////////////////////////////////////////////////////////// /// /// 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 { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public Date scheduleJob(Trigger trigger) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* 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[] { JobDetailSupport.toCompositeData(jobDetail), replace }, new String[] { CompositeData.class.getName(), boolean.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void addJob(JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) throws SchedulerException { invoke( "addJob", new Object[] { JobDetailSupport.toCompositeData(jobDetail), replace , storeNonDurableWhileAwaitingScheduling}, new String[] { CompositeData.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 deleteJob(JobKey jobKey) throws SchedulerException { return (Boolean)invoke( "deleteJob", new Object[] { jobKey.getName(), jobKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException { return (Boolean)invoke( "unscheduleJob", new Object[] { triggerKey.getName(), triggerKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } public boolean deleteJobs(List jobKeys) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } public void scheduleJobs(Map> triggersAndJobs, boolean replace) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } public void scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } public boolean unscheduleJobs(List triggerKeys) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void triggerJob(JobKey jobKey) throws SchedulerException { triggerJob(jobKey, null); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void pauseTrigger(TriggerKey triggerKey) throws SchedulerException { invoke( "pauseTrigger", new Object[] { triggerKey.getName(), triggerKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void pauseTriggers(GroupMatcher matcher) throws SchedulerException { String operation = null; switch (matcher.getCompareWithOperator()) { case EQUALS: operation = "pauseTriggerGroup"; break; case CONTAINS: operation = "pauseTriggersContaining"; break; case STARTS_WITH: operation = "pauseTriggersStartingWith"; break; case ENDS_WITH: operation = "pauseTriggersEndingWith"; case ANYTHING: operation = "pauseTriggersAll"; } if (operation != null) { invoke( operation, new Object[] { matcher.getCompareToValue() }, new String[] { String.class.getName() }); } else { throw new SchedulerException("Unsupported GroupMatcher kind for pausing triggers: " + matcher.getCompareWithOperator()); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void pauseJob(JobKey jobKey) throws SchedulerException { invoke( "pauseJob", new Object[] { jobKey.getName(), jobKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void pauseJobs(GroupMatcher matcher) throws SchedulerException { String operation = null; switch (matcher.getCompareWithOperator()) { case EQUALS: operation = "pauseJobGroup"; break; case STARTS_WITH: operation = "pauseJobsStartingWith"; break; case ENDS_WITH: operation = "pauseJobsEndingWith"; break; case CONTAINS: operation = "pauseJobsContaining"; case ANYTHING: operation = "pauseJobsAll"; } invoke( operation, new Object[] { matcher.getCompareToValue() }, new String[] { String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void resumeTrigger(TriggerKey triggerKey) throws SchedulerException { invoke( "resumeTrigger", new Object[] { triggerKey.getName(), triggerKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void resumeTriggers(GroupMatcher matcher) throws SchedulerException { String operation = null; switch (matcher.getCompareWithOperator()) { case EQUALS: operation = "resumeTriggerGroup"; break; case CONTAINS: operation = "resumeTriggersContaining"; break; case STARTS_WITH: operation = "resumeTriggersStartingWith"; break; case ENDS_WITH: operation = "resumeTriggersEndingWith"; case ANYTHING: operation = "resumeTriggersAll"; } if (operation != null) { invoke( operation, new Object[] { matcher.getCompareToValue() }, new String[] { String.class.getName() }); } else { throw new SchedulerException("Unsupported GroupMatcher kind for resuming triggers: " + matcher.getCompareWithOperator()); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void resumeJob(JobKey jobKey) throws SchedulerException { invoke( "resumeJob", new Object[] { jobKey.getName(), jobKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void resumeJobs(GroupMatcher matcher) throws SchedulerException { String operation = null; switch (matcher.getCompareWithOperator()) { case EQUALS: operation = "resumeJobGroup"; break; case STARTS_WITH: operation = "resumeJobsStartingWith"; break; case ENDS_WITH: operation = "resumeJobsEndingWith"; break; case CONTAINS: operation = "resumeJobsContaining"; case ANYTHING: operation = "resumeJobsAll"; } invoke( operation, new Object[] { matcher.getCompareToValue() }, new String[] { String.class.getName() }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void pauseAll() throws SchedulerException { invoke( "pauseAllTriggers", new Object[] { }, new String[] { }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void resumeAll() throws SchedulerException { invoke( "resumeAllTriggers", new Object[] { }, new String[] { }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ @SuppressWarnings("unchecked") public List getJobGroupNames() throws SchedulerException { return (List)getAttribute("JobGroupNames"); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ @SuppressWarnings("unchecked") public Set getJobKeys(GroupMatcher matcher) throws SchedulerException { if (matcher.getCompareWithOperator().equals(StringMatcher.StringOperatorName.EQUALS)) { List keys = (List)invoke( "getJobNames", new Object[] { matcher.getCompareToValue() }, new String[] { String.class.getName() }); return new HashSet<>(keys); } else { throw new SchedulerException("Only equals matcher are supported for looking up JobKeys"); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ @SuppressWarnings("unchecked") public List getTriggersOfJob(JobKey jobKey) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ @SuppressWarnings("unchecked") public List getTriggerGroupNames() throws SchedulerException { return (List)getAttribute("TriggerGroupNames"); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ @SuppressWarnings("unchecked") public Set getTriggerKeys(GroupMatcher matcher) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public JobDetail getJobDetail(JobKey jobKey) throws SchedulerException { try { return JobDetailSupport.newJobDetail((CompositeData)invoke( "getJobDetail", new Object[] { jobKey.getName(), jobKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() })); } catch (ClassNotFoundException e) { throw new SchedulerException("Unable to resolve job class", e); } } public List getJobDetails(GroupMatcher matcher) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean checkExists(JobKey jobKey) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean checkExists(TriggerKey triggerKey) throws SchedulerException { return (Boolean)invoke( "checkExists", new Object[] { triggerKey }, new String[] { TriggerKey.class.getName() }); } public void clear() throws SchedulerException { invoke( "clear", new Object[] { }, new String[] { }); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException { return TriggerState.valueOf((String)invoke( "getTriggerState", new Object[] { triggerKey.getName(), triggerKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() })); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public void resetTriggerFromErrorState(TriggerKey triggerKey) throws SchedulerException { invoke( "resetTriggerFromErrorState", new Object[] { triggerKey.getName(), triggerKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } /** *

* 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[] { calName, calendar, replace, updateTriggers }, new String[] { 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 { invoke("deleteCalendar", new Object[] { calName }, new String[] { String.class.getName() }); return true; } /** *

* Calls th0e equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ public Calendar getCalendar(String calName) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler, * passing the SchedulingContext associated with this * instance. *

*/ @SuppressWarnings("unchecked") public List getCalendarNames() throws SchedulerException { return (List)getAttribute("CalendarNames"); } /** * @see org.quartz.Scheduler#getPausedTriggerGroups() */ @SuppressWarnings("unchecked") public Set getPausedTriggerGroups() throws SchedulerException { return (Set)getAttribute("PausedTriggerGroups"); } /////////////////////////////////////////////////////////////////////////// /// /// Other Methods /// /////////////////////////////////////////////////////////////////////////// /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public ListenerManager getListenerManager() throws SchedulerException { throw new SchedulerException( "Operation not supported for remote schedulers."); } /** * @see org.quartz.Scheduler#interrupt(JobKey) */ public boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException { try { return (Boolean)invoke( "interruptJob", new Object[] { jobKey.getName(), jobKey.getGroup() }, new String[] { String.class.getName(), String.class.getName() }); } catch (SchedulerException se) { throw new UnableToInterruptJobException(se); } } public boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException { try { return (Boolean)invoke( "interruptJob", new Object[] { fireInstanceId }, new String[] { String.class.getName() }); } catch (SchedulerException se) { throw new UnableToInterruptJobException(se); } } /** * @see org.quartz.Scheduler#setJobFactory(org.quartz.spi.JobFactory) */ public void setJobFactory(JobFactory factory) throws SchedulerException { throw new SchedulerException("Operation not supported for remote schedulers."); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/RemoteScheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.Map; import java.util.Set; import org.quartz.Calendar; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.ListenerManager; import org.quartz.Scheduler; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.UnableToInterruptJobException; import org.quartz.Trigger.TriggerState; import org.quartz.core.RemotableQuartzScheduler; import org.quartz.impl.matchers.GroupMatcher; import 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 org.quartz.Scheduler * @see org.quartz.core.QuartzScheduler * * @author James House */ public class RemoteScheduler implements Scheduler { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private RemotableQuartzScheduler rsched; private final String schedId; private final String rmiHost; private final int rmiPort; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Construct a RemoteScheduler instance to proxy the given * RemotableQuartzScheduler instance, and with the given * SchedulingContext. *

*/ public RemoteScheduler(String schedId, String host, int port) { 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) { throw new SchedulerException( "Could not get handle to remote scheduler: " + e.getMessage(), e); } return rsched; } protected SchedulerException invalidateHandleCreateException(String msg, Exception cause) { rsched = null; return new SchedulerException(msg, cause); } /** *

* 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.isClustered(), 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); } } /////////////////////////////////////////////////////////////////////////// /// /// Scheduler 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); } } /** * 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); } } /** *

* 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. *

*/ public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException { try { return getRemoteScheduler().scheduleJob(jobDetail, trigger); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Date scheduleJob(Trigger trigger) throws SchedulerException { try { return getRemoteScheduler().scheduleJob(trigger); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void addJob(JobDetail jobDetail, boolean replace) throws SchedulerException { try { getRemoteScheduler().addJob(jobDetail, replace); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } public void addJob(JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) throws SchedulerException { try { getRemoteScheduler().addJob(jobDetail, replace, storeNonDurableWhileAwaitingScheduling); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } public boolean deleteJobs(List jobKeys) throws SchedulerException { try { return getRemoteScheduler().deleteJobs(jobKeys); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } public void scheduleJobs(Map> triggersAndJobs, boolean replace) throws SchedulerException { try { getRemoteScheduler().scheduleJobs(triggersAndJobs, replace); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } public void scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace) throws SchedulerException { try { getRemoteScheduler().scheduleJob(jobDetail, triggersForJob, replace); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } public boolean unscheduleJobs(List triggerKeys) throws SchedulerException { try { return getRemoteScheduler().unscheduleJobs(triggerKeys); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean deleteJob(JobKey jobKey) throws SchedulerException { try { return getRemoteScheduler() .deleteJob(jobKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException { try { return getRemoteScheduler().unscheduleJob(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException { try { return getRemoteScheduler().rescheduleJob(triggerKey, newTrigger); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void triggerJob(JobKey jobKey) throws SchedulerException { triggerJob(jobKey, null); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException { try { getRemoteScheduler().triggerJob(jobKey, data); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseTrigger(TriggerKey triggerKey) throws SchedulerException { try { getRemoteScheduler() .pauseTrigger(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseTriggers(GroupMatcher matcher) throws SchedulerException { try { getRemoteScheduler().pauseTriggers(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseJob(JobKey jobKey) throws SchedulerException { try { getRemoteScheduler().pauseJob(jobKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseJobs(GroupMatcher matcher) throws SchedulerException { try { getRemoteScheduler().pauseJobs(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeTrigger(TriggerKey triggerKey) throws SchedulerException { try { getRemoteScheduler().resumeTrigger(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeTriggers(GroupMatcher matcher) throws SchedulerException { try { getRemoteScheduler().resumeTriggers(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeJob(JobKey jobKey) throws SchedulerException { try { getRemoteScheduler().resumeJob(jobKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeJobs(GroupMatcher matcher) throws SchedulerException { try { getRemoteScheduler().resumeJobs(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseAll() throws SchedulerException { try { getRemoteScheduler().pauseAll(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeAll() throws SchedulerException { try { getRemoteScheduler().resumeAll(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getJobGroupNames() throws SchedulerException { try { return getRemoteScheduler().getJobGroupNames(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Set getJobKeys(GroupMatcher matcher) throws SchedulerException { try { return getRemoteScheduler().getJobKeys(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getTriggersOfJob(JobKey jobKey) throws SchedulerException { try { return getRemoteScheduler().getTriggersOfJob(jobKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getTriggerGroupNames() throws SchedulerException { try { return getRemoteScheduler().getTriggerGroupNames(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Set getTriggerKeys(GroupMatcher matcher) throws SchedulerException { try { return getRemoteScheduler().getTriggerKeys(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public JobDetail getJobDetail(JobKey jobKey) throws SchedulerException { try { return getRemoteScheduler().getJobDetail(jobKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getJobDetails(GroupMatcher matcher) throws SchedulerException { try { return getRemoteScheduler().getJobDetails(matcher); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean checkExists(JobKey jobKey) throws SchedulerException { try { return getRemoteScheduler().checkExists(jobKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean checkExists(TriggerKey triggerKey) throws SchedulerException { try { return getRemoteScheduler().checkExists(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void clear() throws SchedulerException { try { getRemoteScheduler().clear(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException { try { return getRemoteScheduler().getTrigger(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException { try { return getRemoteScheduler().getTriggerState(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resetTriggerFromErrorState(TriggerKey triggerKey) throws SchedulerException { try { getRemoteScheduler().resetTriggerFromErrorState(triggerKey); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException { try { getRemoteScheduler().addCalendar(calName, calendar, replace, updateTriggers); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean deleteCalendar(String calName) throws SchedulerException { try { return getRemoteScheduler().deleteCalendar(calName); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Calendar getCalendar(String calName) throws SchedulerException { try { return getRemoteScheduler().getCalendar(calName); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getCalendarNames() throws SchedulerException { try { return getRemoteScheduler().getCalendarNames(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /** * @see org.quartz.Scheduler#getPausedTriggerGroups() */ public Set getPausedTriggerGroups() throws SchedulerException { try { return getRemoteScheduler().getPausedTriggerGroups(); } catch (RemoteException re) { throw invalidateHandleCreateException( "Error communicating with remote scheduler.", re); } } /////////////////////////////////////////////////////////////////////////// /// /// Other Methods /// /////////////////////////////////////////////////////////////////////////// public ListenerManager getListenerManager() throws SchedulerException { throw new SchedulerException( "Operation not supported for remote schedulers."); } /** * @see org.quartz.Scheduler#interrupt(JobKey) */ public boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException { try { return getRemoteScheduler().interrupt(jobKey); } catch (RemoteException re) { throw new UnableToInterruptJobException(invalidateHandleCreateException( "Error communicating with remote scheduler.", re)); } catch (SchedulerException se) { throw new UnableToInterruptJobException(se); } } public boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException { try { return getRemoteScheduler().interrupt(fireInstanceId); } catch (RemoteException re) { throw new UnableToInterruptJobException(invalidateHandleCreateException( "Error communicating with remote scheduler.", re)); } catch (SchedulerException se) { throw new UnableToInterruptJobException(se); } } /** * @see org.quartz.Scheduler#setJobFactory(org.quartz.spi.JobFactory) */ public void setJobFactory(JobFactory factory) throws SchedulerException { throw new SchedulerException( "Operation not supported for remote schedulers."); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/SchedulerDetailsSetter.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.SchedulerException; /** * This utility calls methods reflectively on the given objects even though the * methods are likely on a proper interface (ThreadPool, JobStore, etc). The * motivation is to be tolerant of older implementations that have not been * updated for the changes in the interfaces (eg. LocalTaskExecutorThreadPool in * spring quartz helpers) * * @author teck */ class SchedulerDetailsSetter { private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerDetailsSetter.class); private SchedulerDetailsSetter() { // } static void setDetails(Object target, String schedulerName, String schedulerId) throws SchedulerException { set(target, "setInstanceName", schedulerName); set(target, "setInstanceId", schedulerId); } private static void set(Object target, String method, String value) throws SchedulerException { final Method setter; try { setter = target.getClass().getMethod(method, String.class); } catch (SecurityException e) { LOGGER.error("A SecurityException occurred: {}", e.getMessage(), e); return; } catch (NoSuchMethodException e) { // This probably won't happen since the interface has the method LOGGER.warn("{} does not contain public method {}(String)", target.getClass().getName(), method); return; } if (Modifier.isAbstract(setter.getModifiers())) { // expected if method not implemented (but is present on // interface) LOGGER.warn("{} does not implement {}(String)", target.getClass().getName(), method); return; } try { setter.invoke(target, value); } catch (InvocationTargetException ite) { throw new SchedulerException(ite.getTargetException()); } catch (Exception e) { throw new SchedulerException(e); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/SchedulerRepository.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.util.Collection; import java.util.HashMap; import org.quartz.Scheduler; import 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 final HashMap schedulers; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private SchedulerRepository() { schedulers = new HashMap<>(); } private static class Holder { private static final SchedulerRepository INSTANCE = new SchedulerRepository(); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static synchronized SchedulerRepository getInstance() { return Holder.INSTANCE; } public synchronized void bind(Scheduler sched) throws SchedulerException { if (schedulers.get(sched.getSchedulerName()) != null) { throw new SchedulerException("Scheduler with name '" + sched.getSchedulerName() + "' already exists."); } schedulers.put(sched.getSchedulerName(), sched); } public synchronized boolean remove(String schedName) { return (schedulers.remove(schedName) != null); } public synchronized Scheduler lookup(String schedName) { return schedulers.get(schedName); } public synchronized Collection lookupAll() { return java.util.Collections .unmodifiableCollection(schedulers.values()); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/StdJobRunShellFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.core.JobRunShell; import org.quartz.core.JobRunShellFactory; import org.quartz.spi.TriggerFiredBundle; /** *

* Responsible for creating the instances of {@link org.quartz.core.JobRunShell} * to be used within the {@link org.quartz.core.QuartzScheduler} instance. *

* * @author James House */ public class StdJobRunShellFactory implements JobRunShellFactory { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Scheduler scheduler; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Initialize the factory, providing a handle to the Scheduler * that should be made available within the JobRunShell and * the JobExecutionContext s within it. *

*/ public void initialize(Scheduler sched) { this.scheduler = sched; } /** *

* Called by the {@link org.quartz.core.QuartzSchedulerThread} * to obtain instances of {@link org.quartz.core.JobRunShell}. *

*/ public JobRunShell createJobRunShell(TriggerFiredBundle bundle) throws SchedulerException { return new JobRunShell(scheduler, bundle); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/StdScheduler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.quartz.Calendar; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.ListenerManager; import org.quartz.Scheduler; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.UnableToInterruptJobException; import org.quartz.Trigger.TriggerState; import org.quartz.core.QuartzScheduler; import org.quartz.impl.matchers.GroupMatcher; import 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 org.quartz.Scheduler * @see org.quartz.core.QuartzScheduler * * @author James House */ public class StdScheduler implements Scheduler { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final QuartzScheduler sched; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Construct a StdScheduler instance to proxy the given * QuartzScheduler instance, and with the given SchedulingContext. *

*/ public StdScheduler(QuartzScheduler sched) { this.sched = sched; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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.isClustered(), sched.getThreadPoolClass(), sched.getThreadPoolSize(), sched.getVersion()); } /** *

* Returns the SchedulerContext of the Scheduler. *

*/ public SchedulerContext getContext() throws SchedulerException { return sched.getSchedulerContext(); } /////////////////////////////////////////////////////////////////////////// /// /// Scheduler 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. *

*/ 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(); } /** *

* 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. *

*/ public void clear() throws SchedulerException { sched.clear(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException { return sched.scheduleJob(jobDetail, trigger); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Date scheduleJob(Trigger trigger) throws SchedulerException { return sched.scheduleJob(trigger); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void addJob(JobDetail jobDetail, boolean replace) throws SchedulerException { sched.addJob(jobDetail, replace); } public void addJob(JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) throws SchedulerException { sched.addJob(jobDetail, replace, storeNonDurableWhileAwaitingScheduling); } public boolean deleteJobs(List jobKeys) throws SchedulerException { return sched.deleteJobs(jobKeys); } public void scheduleJobs(Map> triggersAndJobs, boolean replace) throws SchedulerException { sched.scheduleJobs(triggersAndJobs, replace); } public void scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace) throws SchedulerException { sched.scheduleJob(jobDetail, triggersForJob, replace); } public boolean unscheduleJobs(List triggerKeys) throws SchedulerException { return sched.unscheduleJobs(triggerKeys); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean deleteJob(JobKey jobKey) throws SchedulerException { return sched.deleteJob(jobKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException { return sched.unscheduleJob(triggerKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException { return sched.rescheduleJob(triggerKey, newTrigger); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void triggerJob(JobKey jobKey) throws SchedulerException { triggerJob(jobKey, null); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException { sched.triggerJob(jobKey, data); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseTrigger(TriggerKey triggerKey) throws SchedulerException { sched.pauseTrigger(triggerKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseTriggers(GroupMatcher matcher) throws SchedulerException { sched.pauseTriggers(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseJob(JobKey jobKey) throws SchedulerException { sched.pauseJob(jobKey); } /** * @see org.quartz.Scheduler#getPausedTriggerGroups() */ public Set getPausedTriggerGroups() throws SchedulerException { return sched.getPausedTriggerGroups(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseJobs(GroupMatcher matcher) throws SchedulerException { sched.pauseJobs(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeTrigger(TriggerKey triggerKey) throws SchedulerException { sched.resumeTrigger(triggerKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeTriggers(GroupMatcher matcher) throws SchedulerException { sched.resumeTriggers(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeJob(JobKey jobKey) throws SchedulerException { sched.resumeJob(jobKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeJobs(GroupMatcher matcher) throws SchedulerException { sched.resumeJobs(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void pauseAll() throws SchedulerException { sched.pauseAll(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void resumeAll() throws SchedulerException { sched.resumeAll(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getJobGroupNames() throws SchedulerException { return sched.getJobGroupNames(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getTriggersOfJob(JobKey jobKey) throws SchedulerException { return sched.getTriggersOfJob(jobKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Set getJobKeys(GroupMatcher matcher) throws SchedulerException { return sched.getJobKeys(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getTriggerGroupNames() throws SchedulerException { return sched.getTriggerGroupNames(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Set getTriggerKeys(GroupMatcher matcher) throws SchedulerException { return sched.getTriggerKeys(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public JobDetail getJobDetail(JobKey jobKey) throws SchedulerException { return sched.getJobDetail(jobKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getJobDetails(GroupMatcher matcher) throws SchedulerException { return sched.getJobDetails(matcher); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException { return sched.getTrigger(triggerKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException { return sched.getTriggerState(triggerKey); } /** * Reset the current state of the identified {@link Trigger} * from {@link TriggerState#ERROR} to {@link TriggerState#NORMAL} or * {@link TriggerState#PAUSED} as appropriate. * *

Only affects triggers that are in ERROR state - if identified trigger is not * in that state then the result is a no-op.

* *

The result will be the trigger returning to the normal, waiting to * be fired state, unless the trigger's group has been paused, in which * case it will go into the PAUSED state.

* * @see Trigger.TriggerState */ public void resetTriggerFromErrorState(TriggerKey triggerKey) throws SchedulerException { sched.resetTriggerFromErrorState(triggerKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException { sched.addCalendar(calName, calendar, replace, updateTriggers); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean deleteCalendar(String calName) throws SchedulerException { return sched.deleteCalendar(calName); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public Calendar getCalendar(String calName) throws SchedulerException { return sched.getCalendar(calName); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public List getCalendarNames() throws SchedulerException { return sched.getCalendarNames(); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean checkExists(JobKey jobKey) throws SchedulerException { return sched.checkExists(jobKey); } /** *

* Calls the equivalent method on the 'proxied' QuartzScheduler. *

*/ public boolean checkExists(TriggerKey triggerKey) throws SchedulerException { return sched.checkExists(triggerKey); } /////////////////////////////////////////////////////////////////////////// /// /// Other Methods /// /////////////////////////////////////////////////////////////////////////// /** * @see org.quartz.Scheduler#setJobFactory(org.quartz.spi.JobFactory) */ public void setJobFactory(JobFactory factory) throws SchedulerException { sched.setJobFactory(factory); } /** * @see org.quartz.Scheduler#getListenerManager() */ public ListenerManager getListenerManager() throws SchedulerException { return sched.getListenerManager(); } public boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException { return sched.interrupt(jobKey); } public boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException { return sched.interrupt(fireInstanceId); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/StdSchedulerFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import org.quartz.JobListener; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.TriggerListener; import org.quartz.core.JobRunShellFactory; import org.quartz.core.QuartzScheduler; import org.quartz.core.QuartzSchedulerResources; import org.quartz.ee.jta.JTAAnnotationAwareJobRunShellFactory; import org.quartz.ee.jta.JTAJobRunShellFactory; import org.quartz.ee.jta.UserTransactionHelper; import org.quartz.impl.jdbcjobstore.JobStoreSupport; import org.quartz.impl.jdbcjobstore.Semaphore; import org.quartz.impl.jdbcjobstore.TablePrefixAware; import org.quartz.impl.matchers.EverythingMatcher; import org.quartz.management.ManagementRESTServiceConfiguration; import org.quartz.simpl.RAMJobStore; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.InstanceIdGenerator; import org.quartz.spi.JobFactory; import org.quartz.spi.JobStore; import org.quartz.spi.SchedulerPlugin; import org.quartz.spi.ThreadExecutor; import org.quartz.spi.ThreadPool; import org.quartz.utils.ConnectionProvider; import org.quartz.utils.DBConnectionManager; import org.quartz.utils.JNDIConnectionProvider; import org.quartz.utils.C3p0PoolingConnectionProvider; import org.quartz.utils.PoolingConnectionProvider; import org.quartz.utils.PropertiesParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.Constructor; import java.lang.reflect.Method; import java.security.AccessControlException; import java.util.Collection; import java.util.Enumeration; import java.util.Locale; import java.util.Properties; /** *

* An implementation of {@link org.quartz.SchedulerFactory} that * does all of its work of creating a QuartzScheduler instance * based on the contents 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 'org.quartz.properties' to point to the file you want. *

* *

* Alternatively, you can explicitly initialize the factory by calling one of * the initialize(xx) methods before calling getScheduler(). *

* *

* See the sample properties files that are distributed with Quartz for * information about the various settings available within the file. * Full configuration documentation can be found at * http://www.quartz-scheduler.org/docs/index.html *

* *

* Instances of the specified {@link org.quartz.spi.JobStore}, * {@link org.quartz.spi.ThreadPool}, and other SPI 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 * '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. *

* *

* One property can reference another property's value by specifying a value * following the convention of "$@other.property.name", for example, to reference * the scheduler's instance name as the value for some other property, you * would use "$@org.quartz.scheduler.instanceName". *

* * @author James House * @author Anthony Eden * @author Mohammad Rezaei */ public class StdSchedulerFactory implements SchedulerFactory { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static final String PROPERTIES_FILE = "org.quartz.properties"; public static final String PROP_SCHED_INSTANCE_NAME = "org.quartz.scheduler.instanceName"; public static final String PROP_SCHED_INSTANCE_ID = "org.quartz.scheduler.instanceId"; public static final String PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX = "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 = "org.quartz.scheduler.threadName"; public static final String PROP_SCHED_BATCH_TIME_WINDOW = "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow"; public static final String PROP_SCHED_MAX_BATCH_SIZE = "org.quartz.scheduler.batchTriggerAcquisitionMaxCount"; public static final String PROP_SCHED_JMX_EXPORT = "org.quartz.scheduler.jmx.export"; public static final String PROP_SCHED_JMX_OBJECT_NAME = "org.quartz.scheduler.jmx.objectName"; public static final String PROP_SCHED_JMX_PROXY = "org.quartz.scheduler.jmx.proxy"; public static final String PROP_SCHED_JMX_PROXY_CLASS = "org.quartz.scheduler.jmx.proxy.class"; public static final String PROP_SCHED_RMI_EXPORT = "org.quartz.scheduler.rmi.export"; public static final String PROP_SCHED_RMI_PROXY = "org.quartz.scheduler.rmi.proxy"; public static final String PROP_SCHED_RMI_HOST = "org.quartz.scheduler.rmi.registryHost"; public static final String PROP_SCHED_RMI_PORT = "org.quartz.scheduler.rmi.registryPort"; public static final String PROP_SCHED_RMI_SERVER_PORT = "org.quartz.scheduler.rmi.serverPort"; public static final String PROP_SCHED_RMI_CREATE_REGISTRY = "org.quartz.scheduler.rmi.createRegistry"; public static final String PROP_SCHED_RMI_BIND_NAME = "org.quartz.scheduler.rmi.bindName"; public static final String PROP_SCHED_WRAP_JOB_IN_USER_TX = "org.quartz.scheduler.wrapJobExecutionInUserTransaction"; public static final String PROP_SCHED_USER_TX_URL = "org.quartz.scheduler.userTransactionURL"; public static final String PROP_SCHED_IDLE_WAIT_TIME = "org.quartz.scheduler.idleWaitTime"; public static final String PROP_SCHED_DB_FAILURE_RETRY_INTERVAL = "org.quartz.scheduler.dbFailureRetryInterval"; public static final String PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON = "org.quartz.scheduler.makeSchedulerThreadDaemon"; public static final String PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD = "org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer"; public static final String PROP_SCHED_CLASS_LOAD_HELPER_CLASS = "org.quartz.scheduler.classLoadHelper.class"; public static final String PROP_SCHED_JOB_FACTORY_CLASS = "org.quartz.scheduler.jobFactory.class"; public static final String PROP_SCHED_JOB_FACTORY_PREFIX = "org.quartz.scheduler.jobFactory"; public static final String PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN = "org.quartz.scheduler.interruptJobsOnShutdown"; public static final String PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT = "org.quartz.scheduler.interruptJobsOnShutdownWithWait"; public static final String PROP_SCHED_CONTEXT_PREFIX = "org.quartz.context.key"; public static final String PROP_THREAD_POOL_PREFIX = "org.quartz.threadPool"; public static final String PROP_THREAD_POOL_CLASS = "org.quartz.threadPool.class"; public static final String PROP_JOB_STORE_PREFIX = "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_SCHED_NAME = "schedName"; public static final String PROP_JOB_STORE_CLASS = "org.quartz.jobStore.class"; public static final String PROP_JOB_STORE_USE_PROP = "org.quartz.jobStore.useProperties"; public static final String PROP_DATASOURCE_PREFIX = "org.quartz.dataSource"; public static final String PROP_CONNECTION_PROVIDER_CLASS = "connectionProvider.class"; /** * @deprecated Replaced with {@link PoolingConnectionProvider#DB_DRIVER} */ @Deprecated public static final String PROP_DATASOURCE_DRIVER = "driver"; /** * @deprecated Replaced with {@link PoolingConnectionProvider#DB_URL} */ @Deprecated public static final String PROP_DATASOURCE_URL = "URL"; /** * @deprecated Replaced with {@link PoolingConnectionProvider#DB_USER} */ @Deprecated public static final String PROP_DATASOURCE_USER = "user"; /** * @deprecated Replaced with {@link PoolingConnectionProvider#DB_PASSWORD} */ @Deprecated public static final String PROP_DATASOURCE_PASSWORD = "password"; /** * @deprecated Replaced with {@link PoolingConnectionProvider#DB_MAX_CONNECTIONS} */ @Deprecated public static final String PROP_DATASOURCE_MAX_CONNECTIONS = "maxConnections"; /** * @deprecated Replaced with {@link PoolingConnectionProvider#DB_VALIDATION_QUERY} */ @Deprecated 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_PROVIDER = "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 = "org.quartz.plugin"; public static final String PROP_PLUGIN_CLASS = "class"; public static final String PROP_JOB_LISTENER_PREFIX = "org.quartz.jobListener"; public static final String PROP_TRIGGER_LISTENER_PREFIX = "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"; public static final String PROP_THREAD_EXECUTOR = "org.quartz.threadExecutor"; public static final String PROP_THREAD_EXECUTOR_CLASS = "org.quartz.threadExecutor.class"; public static final String SYSTEM_PROPERTY_AS_INSTANCE_ID = "SYS_PROP"; public static final String MANAGEMENT_REST_SERVICE_ENABLED = "org.quartz.managementRESTService.enabled"; public static final String MANAGEMENT_REST_SERVICE_HOST_PORT = "org.quartz.managementRESTService.bind"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private SchedulerException initException = null; private String propSrc = null; private PropertiesParser cfg; private final Logger log = LoggerFactory.getLogger(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 Logger getLog() { return log; } /** *

* Initialize the {@link 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 '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'"; ClassLoader cl = getClass().getClassLoader(); if(cl == null) cl = findClassLoader(); if(cl == null) throw new SchedulerConfigException("Unable to find a class loader on the current thread or class."); in = cl.getResourceAsStream( "quartz.properties"); if (in == null) { in = cl.getResourceAsStream( "/quartz.properties"); } if (in == null) { in = cl.getResourceAsStream( "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: '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, getLog())); } /** * Add all System properties to the given props. Will override * any properties that already exist in the given props. */ // Visible for testing static Properties overrideWithSysProps(Properties props, Logger log) { Properties sysProps = null; try { sysProps = System.getProperties(); } catch (AccessControlException e) { log.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) { // Use the propertyNames to iterate to avoid // a possible ConcurrentModificationException Enumeration en = sysProps.propertyNames(); while (en.hasMoreElements()) { Object name = en.nextElement(); Object value = sysProps.get(name); if (name instanceof String && value instanceof String) { // Properties javadoc discourages use of put so we use setProperty props.setProperty((String) name, (String) value); } } } return props; } /** *

* Initialize the {@link org.quartz.SchedulerFactory} with * the contents 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; 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 org.quartz.SchedulerFactory} with * the contents 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 org.quartz.SchedulerFactory} with * the contents of the given Properties object. *

*/ public void initialize(Properties props) { 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; ThreadPool tp; QuartzScheduler qs = null; DBConnectionManager dbMgr = null; String instanceIdGeneratorClass = null; Properties tProps; String userTXLocation = null; boolean wrapJobInTx = false; boolean autoId = false; long idleWaitTime = -1; long dbFailureRetry = 15000L; // 15 secs String classLoadHelperClass; String jobFactoryClass; ThreadExecutor threadExecutor; 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, "org.quartz.simpl.SimpleInstanceIdGenerator"); } else if (schedInstId.equals(SYSTEM_PROPERTY_AS_INSTANCE_ID)) { autoId = true; instanceIdGeneratorClass = "org.quartz.simpl.SystemPropertyInstanceIdGenerator"; } userTXLocation = cfg.getStringProperty(PROP_SCHED_USER_TX_URL, userTXLocation); if (userTXLocation != null && userTXLocation.trim().isEmpty()) { userTXLocation = null; } classLoadHelperClass = cfg.getStringProperty( PROP_SCHED_CLASS_LOAD_HELPER_CLASS, "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); if(idleWaitTime > -1 && idleWaitTime < 1000) { throw new SchedulerException("org.quartz.scheduler.idleWaitTime of less than 1000ms is not legal."); } dbFailureRetry = cfg.getLongProperty(PROP_SCHED_DB_FAILURE_RETRY_INTERVAL, dbFailureRetry); if (dbFailureRetry < 0) { throw new SchedulerException(PROP_SCHED_DB_FAILURE_RETRY_INTERVAL + " of less than 0 ms is not legal."); } boolean makeSchedulerThreadDaemon = cfg.getBooleanProperty(PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON); boolean threadsInheritInitializersClassLoader = cfg.getBooleanProperty(PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD); long batchTimeWindow = cfg.getLongProperty(PROP_SCHED_BATCH_TIME_WINDOW, 0L); int maxBatchSize = cfg.getIntProperty(PROP_SCHED_MAX_BATCH_SIZE, 1); boolean interruptJobsOnShutdown = cfg.getBooleanProperty(PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN, false); boolean interruptJobsOnShutdownWithWait = cfg.getBooleanProperty(PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT, false); boolean jmxExport = cfg.getBooleanProperty(PROP_SCHED_JMX_EXPORT); String jmxObjectName = cfg.getStringProperty(PROP_SCHED_JMX_OBJECT_NAME); boolean jmxProxy = cfg.getBooleanProperty(PROP_SCHED_JMX_PROXY); String jmxProxyClass = cfg.getStringProperty(PROP_SCHED_JMX_PROXY_CLASS); 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."); } boolean managementRESTServiceEnabled = cfg.getBooleanProperty(MANAGEMENT_REST_SERVICE_ENABLED, false); String managementRESTServiceHostAndPort = cfg.getStringProperty(MANAGEMENT_REST_SERVICE_HOST_PORT, "0.0.0.0:9889"); Properties schedCtxProps = cfg.getPropertyGroup(PROP_SCHED_CONTEXT_PREFIX, true); // If Proxying to remote scheduler, short-circuit here... // ~~~~~~~~~~~~~~~~~~ if (rmiProxy) { if (autoId) { schedInstId = DEFAULT_INSTANCE_ID; } String uid = (rmiBindName == null) ? QuartzSchedulerResources.getUniqueIdentifier( schedName, schedInstId) : rmiBindName; RemoteScheduler remoteScheduler = new RemoteScheduler(uid, rmiHost, rmiPort); schedRep.bind(remoteScheduler); return remoteScheduler; } // Create class load helper ClassLoadHelper loadHelper; try { loadHelper = (ClassLoadHelper) loadClass(classLoadHelperClass).getDeclaredConstructor().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; try { jmxScheduler = (RemoteMBeanScheduler) loadHelper.loadClass(jmxProxyClass).getDeclaredConstructor() .newInstance(); } catch (Exception e) { throw new SchedulerConfigException( "Unable to instantiate RemoteMBeanScheduler class.", e); } if (jmxObjectName == null) { jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId); } 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); throw initException; } jmxScheduler.initialize(); schedRep.bind(jmxScheduler); return jmxScheduler; } JobFactory jobFactory = null; if(jobFactoryClass != null) { try { jobFactory = (JobFactory) loadHelper.loadClass(jobFactoryClass).getDeclaredConstructor().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); throw initException; } } InstanceIdGenerator instanceIdGenerator = null; if(instanceIdGeneratorClass != null) { try { instanceIdGenerator = (InstanceIdGenerator) loadHelper.loadClass(instanceIdGeneratorClass) .getDeclaredConstructor() .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); throw initException; } } // Get ThreadPool Properties // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ String tpClass = cfg.getStringProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); if (tpClass == null) { initException = new SchedulerException( "ThreadPool class not specified. "); throw initException; } try { tp = (ThreadPool) loadHelper.loadClass(tpClass).getDeclaredConstructor().newInstance(); } catch (Exception e) { initException = new SchedulerException("ThreadPool class '" + tpClass + "' could not be instantiated.", e); 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); 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. "); throw initException; } try { js = (JobStore) loadHelper.loadClass(jsClass).getDeclaredConstructor().newInstance(); } catch (Exception e) { initException = new SchedulerException("JobStore class '" + jsClass + "' could not be instantiated.", e); throw initException; } SchedulerDetailsSetter.setDetails(js, schedName, schedInstId); 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); throw initException; } if (js instanceof JobStoreSupport) { // 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) .getDeclaredConstructor().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()); tProps.setProperty( PROP_SCHED_NAME, schedName); } try { setBeanProps(lockHandler, tProps); } catch (Exception e) { initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass + "' props could not be configured.", e); 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); throw initException; } } } // Set up any DataSources // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ String[] dsNames = cfg.getPropertyGroups(PROP_DATASOURCE_PREFIX); for (String dsName : dsNames) { PropertiesParser pp = new PropertiesParser(cfg.getPropertyGroup( PROP_DATASOURCE_PREFIX + "." + dsName, true)); String cpClass = pp.getStringProperty(PROP_CONNECTION_PROVIDER_CLASS, null); // custom connectionProvider... if (cpClass != null) { ConnectionProvider cp; try { cp = (ConnectionProvider) loadHelper.loadClass(cpClass).getDeclaredConstructor().newInstance(); } catch (Exception e) { initException = new SchedulerException("ConnectionProvider class '" + cpClass + "' could not be instantiated.", e); throw initException; } try { // remove the class name, so it isn't attempted to be set pp.getUnderlyingProperties().remove( PROP_CONNECTION_PROVIDER_CLASS); if (cp instanceof PoolingConnectionProvider) { populateProviderWithExtraProps((PoolingConnectionProvider) cp, pp.getUnderlyingProperties()); } else { setBeanProps(cp, pp.getUnderlyingProperties()); } cp.initialize(); } catch (Exception e) { initException = new SchedulerException("ConnectionProvider class '" + cpClass + "' props could not be configured.", e); throw initException; } dbMgr = DBConnectionManager.getInstance(); dbMgr.addConnectionProvider(dsName, 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_PROVIDER); 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_PROVIDER, 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(dsName, cp); } else { String poolingProvider = pp.getStringProperty(PoolingConnectionProvider.POOLING_PROVIDER); String dsDriver = pp.getStringProperty(PoolingConnectionProvider.DB_DRIVER); String dsURL = pp.getStringProperty(PoolingConnectionProvider.DB_URL); if (dsDriver == null) { initException = new SchedulerException( "Driver not specified for DataSource: " + dsName); throw initException; } if (dsURL == null) { initException = new SchedulerException( "DB URL not specified for DataSource: " + dsName); throw initException; } // we load even these "core" providers by class name in order to avoid a static dependency on // the c3p0 and hikaricp libraries if (poolingProvider != null && poolingProvider.equals(PoolingConnectionProvider.POOLING_PROVIDER_HIKARICP)) { cpClass = "org.quartz.utils.HikariCpPoolingConnectionProvider"; } else { cpClass = "org.quartz.utils.C3p0PoolingConnectionProvider"; } log.info("Using ConnectionProvider class '" + cpClass + "' for data source '" + dsName + "'"); try { ConnectionProvider cp; try { Constructor constructor = loadHelper.loadClass(cpClass).getConstructor(Properties.class); cp = (ConnectionProvider) constructor.newInstance(pp.getUnderlyingProperties()); } catch (Exception e) { initException = new SchedulerException("ConnectionProvider class '" + cpClass + "' could not be instantiated.", e); throw initException; } dbMgr = DBConnectionManager.getInstance(); dbMgr.addConnectionProvider(dsName, cp); // Populate the underlying C3P0/HikariCP data source pool properties populateProviderWithExtraProps((PoolingConnectionProvider) cp, pp.getUnderlyingProperties()); } catch (Exception sqle) { initException = new SchedulerException( "Could not initialize DataSource: " + dsName, 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] + "'"); throw initException; } SchedulerPlugin plugin; try { plugin = (SchedulerPlugin) loadHelper.loadClass(plugInClass).getDeclaredConstructor().newInstance(); } catch (Exception e) { initException = new SchedulerException( "SchedulerPlugin class '" + plugInClass + "' could not be instantiated.", e); throw initException; } try { setBeanProps(plugin, pp); } catch (Exception e) { initException = new SchedulerException( "JobStore SchedulerPlugin '" + plugInClass + "' props could not be configured.", e); 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] + "'"); throw initException; } JobListener listener; try { listener = (JobListener) loadHelper.loadClass(listenerClass).getDeclaredConstructor().newInstance(); } catch (Exception e) { initException = new SchedulerException( "JobListener class '" + listenerClass + "' could not be instantiated.", e); throw initException; } try { Method nameSetter = null; try { nameSetter = listener.getClass().getMethod("setName", strArg); } catch(NoSuchMethodException ignore) { /* do nothing */ } 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); 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] + "'"); throw initException; } TriggerListener listener; try { listener = (TriggerListener) loadHelper.loadClass(listenerClass).getDeclaredConstructor().newInstance(); } catch (Exception e) { initException = new SchedulerException( "TriggerListener class '" + listenerClass + "' could not be instantiated.", e); throw initException; } try { Method nameSetter = null; try { nameSetter = listener.getClass().getMethod("setName", strArg); } catch(NoSuchMethodException ignore) { /* do nothing */ } 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); throw initException; } triggerListeners[i] = listener; } boolean tpInited = false; boolean qsInited = false; // Get ThreadExecutor Properties // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ String threadExecutorClass = cfg.getStringProperty(PROP_THREAD_EXECUTOR_CLASS); if (threadExecutorClass != null) { tProps = cfg.getPropertyGroup(PROP_THREAD_EXECUTOR, true); try { threadExecutor = (ThreadExecutor) loadHelper.loadClass(threadExecutorClass) .getDeclaredConstructor() .newInstance(); log.info("Using custom implementation for ThreadExecutor: {}", threadExecutorClass); setBeanProps(threadExecutor, tProps); } catch (Exception e) { initException = new SchedulerException( "ThreadExecutor class '" + threadExecutorClass + "' could not be instantiated.", e); throw initException; } } else { log.info("Using default implementation for ThreadExecutor"); threadExecutor = new DefaultThreadExecutor(); } // Fire everything up // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ try { JobRunShellFactory jrsf; // Create correct run-shell factory... if (userTXLocation != null) { UserTransactionHelper.setUserTxLocation(userTXLocation); } if (wrapJobInTx) { jrsf = new JTAJobRunShellFactory(); } else { jrsf = new JTAAnnotationAwareJobRunShellFactory(); } if (autoId) { try { schedInstId = DEFAULT_INSTANCE_ID; if (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.getClass().getName().startsWith("org.terracotta.quartz")) { try { String uuid = (String) js.getClass().getMethod("getUUID").invoke(js); if(schedInstId.equals(DEFAULT_INSTANCE_ID)) { schedInstId = "TERRACOTTA_CLUSTERED,node=" + uuid; if (jmxObjectName == null) { jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId); } } else if(jmxObjectName == null) { jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId + ",node=" + uuid); } } catch(Exception e) { throw new RuntimeException("Problem obtaining node id from TerracottaJobStore.", e); } if(null == cfg.getStringProperty(PROP_SCHED_JMX_EXPORT)) { jmxExport = true; } } if (js instanceof JobStoreSupport) { JobStoreSupport jjs = (JobStoreSupport)js; jjs.setDbRetryInterval(dbFailureRetry); if(threadsInheritInitializersClassLoader) jjs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitializersClassLoader); jjs.setThreadExecutor(threadExecutor); } QuartzSchedulerResources rsrcs = new QuartzSchedulerResources(); rsrcs.setName(schedName); rsrcs.setThreadName(threadName); rsrcs.setInstanceId(schedInstId); rsrcs.setJobRunShellFactory(jrsf); rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon); rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitializersClassLoader); rsrcs.setBatchTimeWindow(batchTimeWindow); rsrcs.setMaxBatchSize(maxBatchSize); rsrcs.setInterruptJobsOnShutdown(interruptJobsOnShutdown); rsrcs.setInterruptJobsOnShutdownWithWait(interruptJobsOnShutdownWithWait); rsrcs.setJMXExport(jmxExport); rsrcs.setJMXObjectName(jmxObjectName); if (managementRESTServiceEnabled) { ManagementRESTServiceConfiguration managementRESTServiceConfiguration = new ManagementRESTServiceConfiguration(); managementRESTServiceConfiguration.setBind(managementRESTServiceHostAndPort); managementRESTServiceConfiguration.setEnabled(managementRESTServiceEnabled); rsrcs.setManagementRESTServiceConfiguration(managementRESTServiceConfiguration); } if (rmiExport) { rsrcs.setRMIRegistryHost(rmiHost); rsrcs.setRMIRegistryPort(rmiPort); rsrcs.setRMIServerPort(rmiServerPort); rsrcs.setRMICreateRegistryStrategy(rmiCreateRegistry); rsrcs.setRMIBindName(rmiBindName); } SchedulerDetailsSetter.setDetails(tp, schedName, schedInstId); rsrcs.setThreadExecutor(threadExecutor); threadExecutor.initialize(); rsrcs.setThreadPool(tp); if(tp instanceof SimpleThreadPool) { if(threadsInheritInitializersClassLoader) ((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitializersClassLoader); } tp.initialize(); tpInited = true; rsrcs.setJobStore(js); // add plugins for (SchedulerPlugin plugin : plugins) { rsrcs.addSchedulerPlugin(plugin); } qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry); qsInited = true; // 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, loadHelper); } // add listeners for (JobListener jobListener : jobListeners) { qs.getListenerManager().addJobListener(jobListener, EverythingMatcher.allJobs()); } for (TriggerListener triggerListener : triggerListeners) { qs.getListenerManager().addTriggerListener(triggerListener, EverythingMatcher.allTriggers()); } // set scheduler context data... for(Object key: schedCtxProps.keySet()) { String val = schedCtxProps.getProperty((String) key); scheduler.getContext().put((String)key, val); } // fire up job store, and runshell factory js.setInstanceId(schedInstId); js.setInstanceName(schedName); js.setThreadPoolSize(tp.getPoolSize()); js.initialize(loadHelper, qs.getSchedulerSignaler()); jrsf.initialize(scheduler); qs.initialize(); getLog().info("Quartz scheduler '{}' initialized from {}", scheduler.getSchedulerName(), 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; } catch(SchedulerException | Error | RuntimeException e) { shutdownFromInstantiateException(tp, qs, tpInited, qsInited); throw e; } } private void populateProviderWithExtraProps(PoolingConnectionProvider cp, Properties props) throws Exception { Properties copyProps = new Properties(); copyProps.putAll(props); // Remove all the default properties first (they don't always match to setter name, and they are already // been set!) copyProps.remove(PoolingConnectionProvider.DB_DRIVER); copyProps.remove(PoolingConnectionProvider.DB_URL); copyProps.remove(PoolingConnectionProvider.DB_USER); copyProps.remove(PoolingConnectionProvider.DB_PASSWORD); copyProps.remove(PoolingConnectionProvider.DB_MAX_CONNECTIONS); copyProps.remove(PoolingConnectionProvider.DB_VALIDATION_QUERY); copyProps.remove(PoolingConnectionProvider.POOLING_PROVIDER); if (cp instanceof C3p0PoolingConnectionProvider) { copyProps.remove(C3p0PoolingConnectionProvider.DB_MAX_CACHED_STATEMENTS_PER_CONNECTION); copyProps.remove(C3p0PoolingConnectionProvider.DB_VALIDATE_ON_CHECKOUT); copyProps.remove(C3p0PoolingConnectionProvider.DB_IDLE_VALIDATION_SECONDS); copyProps.remove(C3p0PoolingConnectionProvider.DB_DISCARD_IDLE_CONNECTIONS_SECONDS); } setBeanProps(cp.getDataSource(), copyProps); } private void shutdownFromInstantiateException(ThreadPool tp, QuartzScheduler qs, boolean tpInited, boolean qsInited) { try { if(qsInited) qs.shutdown(false); else if(tpInited) tp.shutdown(false); } catch (Exception e) { getLog().error("Got another exception while shutting down after instantiation exception", e); } } protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) { return new StdScheduler(qs); } private void setBeanProps(Object obj, Properties props) throws NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException, IntrospectionException, SchedulerConfigException { props.remove("class"); props.remove(PoolingConnectionProvider.POOLING_PROVIDER); 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 + "'"); } // does the property value reference another property's value? If so, swap to look at its value PropertiesParser refProps = pp; String refName = pp.getStringProperty(name); if(refName != null && refName.startsWith("$@")) { refName = refName.substring(2); refProps = cfg; } else refName = name; if (params[0].equals(int.class)) { setMeth.invoke(obj, new Object[]{refProps.getIntProperty(refName)}); } else if (params[0].equals(long.class)) { setMeth.invoke(obj, new Object[]{refProps.getLongProperty(refName)}); } else if (params[0].equals(float.class)) { setMeth.invoke(obj, new Object[]{refProps.getFloatProperty(refName)}); } else if (params[0].equals(double.class)) { setMeth.invoke(obj, new Object[]{refProps.getDoubleProperty(refName)}); } else if (params[0].equals(boolean.class)) { setMeth.invoke(obj, new Object[]{refProps.getBooleanProperty(refName)}); } else if (params[0].equals(String.class)) { setMeth.invoke(obj, new Object[]{refProps.getStringProperty(refName)}); } 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); } } } private java.lang.reflect.Method getSetMethod(String name, PropertyDescriptor[] props) { for (PropertyDescriptor prop : props) { Method wMeth = prop.getWriteMethod(); if (wMeth != null && wMeth.getName().equals(name)) { return wMeth; } } return null; } private Class loadClass(String className) throws ClassNotFoundException, SchedulerConfigException { try { ClassLoader cl = findClassLoader(); if(cl != null) return cl.loadClass(className); throw new SchedulerConfigException("Unable to find a class loader on the current thread or class."); } catch (ClassNotFoundException e) { if(getClass().getClassLoader() != null) return getClass().getClassLoader().loadClass(className); throw e; } } private ClassLoader findClassLoader() { // work-around set context loader for windows-service started jvms (QUARTZ-748) if(Thread.currentThread().getContextClassLoader() == null && getClass().getClassLoader() != null) { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); } return Thread.currentThread().getContextClassLoader(); } private String getSchedulerName() { return cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME, "QuartzScheduler"); } /** *

* 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(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/AnnualCalendar.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.calendar; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.TimeZone; import 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 org.quartz.Calendar * @see org.quartz.impl.calendar.BaseCalendar * * @author Juergen Donnerstag */ public class AnnualCalendar extends BaseCalendar implements Calendar, Serializable { private 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); } @Override public Object clone() { AnnualCalendar clone = (AnnualCalendar) super.clone(); clone.excludeDays = new ArrayList<>(excludeDays); return clone; } /** *

* 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 excluded. *

*/ 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) { Collections.sort(excludeDays, new CalendarComparator()); dataSorted = true; } for (java.util.Calendar cl : excludeDays) { // 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 the day to exclude */ 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 for (java.util.Calendar cl : excludeDays) { 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. *

*/ @Override 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)) { 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. *

*/ @Override 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)) { return timeStamp; // return the original value } while (isDayExcluded(day)) { day.add(java.util.Calendar.DATE, 1); } return day.getTime().getTime(); } } class CalendarComparator implements Comparator, Serializable { private static final long serialVersionUID = 7346867105876610961L; public CalendarComparator() { } public int compare(java.util.Calendar c1, java.util.Calendar c2) { 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; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/BaseCalendar.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.calendar; import java.io.Serializable; import java.util.Date; import java.util.TimeZone; import 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 org.quartz.Calendar * * @author Juergen Donnerstag * @author James House */ public class BaseCalendar implements Calendar, Serializable, Cloneable { private 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); } @Override public Object clone() { try { BaseCalendar clone = (BaseCalendar) super.clone(); if (getBaseCalendar() != null) { clone.baseCalendar = (Calendar) getBaseCalendar().clone(); } if(getTimeZone() != null) clone.timeZone = (TimeZone) getTimeZone().clone(); return clone; } catch (CloneNotSupportedException ex) { throw new IncompatibleClassChangeError("Not Cloneable."); } } /** *

* 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 org.quartz.Calendar#isTimeIncluded(long) */ public boolean isTimeIncluded(long timeStamp) { if (timeStamp <= 0) { throw new IllegalArgumentException( "timeStamp must be greater 0"); } if (baseCalendar != null) { return baseCalendar.isTimeIncluded(timeStamp); } 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 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; } /** * 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; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/CronCalendar.java ================================================ package org.quartz.impl.calendar; import java.text.ParseException; import java.util.Date; import java.util.TimeZone; import org.quartz.Calendar; import org.quartz.CronExpression; /** * This implementation of the Calendar excludes the set of times expressed by a * given {@link 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 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 { private static final long serialVersionUID = -8172103999750856831L; 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 expression, * 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); } @Override public Object clone() { CronCalendar clone = (CronCalendar) super.clone(); clone.cronExpression = new CronExpression(cronExpression); return clone; } /** * Returns the time zone for which the CronExpression of * this CronCalendar will be resolved. *

* Overrides {@link BaseCalendar#getTimeZone()} to * defer to its CronExpression. *

*/ @Override 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. *

*/ @Override public void setTimeZone(TimeZone timeZone) { cronExpression.setTimeZone(timeZone); } /** * 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 */ @Override public boolean isTimeIncluded(long timeInMillis) { if ((getBaseCalendar() != null) && (!getBaseCalendar().isTimeIncluded(timeInMillis))) { 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. */ @Override 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 properties of the CronCalendar in a String format */ @Override public String toString() { StringBuffer buffer = new StringBuffer(); 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 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 { this.cronExpression = new CronExpression(expression); } /** * 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; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/DailyCalendar.java ================================================ package 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 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 { private 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 = ":"; 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(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(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(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(org.quartz.Calendar, java.util.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(org.quartz.Calendar, java.util.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(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(org.quartz.Calendar baseCalendar, TimeZone timeZone, long rangeStartingTimeInMillis, long rangeEndingTimeInMillis) { super(baseCalendar, timeZone); setTimeRange(rangeStartingTimeInMillis, rangeEndingTimeInMillis); } @Override public Object clone() { return (DailyCalendar) super.clone(); } /** * 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 */ @Override public boolean isTimeIncluded(long timeInMillis) { if ((getBaseCalendar() != null) && (!getBaseCalendar().isTimeIncluded(timeInMillis))) { 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. */ @Override 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 properties of the DailyCalendar in a String format */ @Override public String toString() { NumberFormat numberFormatter = NumberFormat.getNumberInstance(); numberFormatter.setMaximumFractionDigits(0); numberFormatter.setMinimumIntegerDigits(2); StringBuffer buffer = new StringBuffer(); 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: ").append(invertTimeRange).append("]"); 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 rStartingHourOfDay; int rStartingMinute; int rStartingSecond; int rStartingMillis; String[] rEndingTime; int rEndingHourOfDay; int rEndingMinute; int rEndingSecond; int rEndingMillis; rangeStartingTime = split(rangeStartingTimeString, colon); if ((rangeStartingTime.length < 2) || (rangeStartingTime.length > 4)) { throw new IllegalArgumentException("Invalid time string '" + rangeStartingTimeString + "'"); } rStartingHourOfDay = Integer.parseInt(rangeStartingTime[0]); rStartingMinute = Integer.parseInt(rangeStartingTime[1]); if (rangeStartingTime.length > 2) { rStartingSecond = Integer.parseInt(rangeStartingTime[2]); } else { rStartingSecond = 0; } if (rangeStartingTime.length == 4) { rStartingMillis = Integer.parseInt(rangeStartingTime[3]); } else { rStartingMillis = 0; } rEndingTime = split(rangeEndingTimeString, colon); if ((rEndingTime.length < 2) || (rEndingTime.length > 4)) { throw new IllegalArgumentException("Invalid time string '" + rangeEndingTimeString + "'"); } rEndingHourOfDay = Integer.parseInt(rEndingTime[0]); rEndingMinute = Integer.parseInt(rEndingTime[1]); if (rEndingTime.length > 2) { rEndingSecond = Integer.parseInt(rEndingTime[2]); } else { rEndingSecond = 0; } if (rEndingTime.length == 4) { rEndingMillis = Integer.parseInt(rEndingTime[3]); } else { rEndingMillis = 0; } setTimeRange(rStartingHourOfDay, rStartingMinute, rStartingSecond, rStartingMillis, rEndingHourOfDay, rEndingMinute, rEndingSecond, rEndingMillis); } /** * 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); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/HolidayCalendar.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 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 { private 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); } @Override public Object clone() { HolidayCalendar clone = (HolidayCalendar) super.clone(); clone.dates = new TreeSet<>(dates); return clone; } /** *

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

* *

* Note that this Calendar is only has full-day precision. *

*/ @Override public boolean isTimeIncluded(long timeStamp) { if (!super.isTimeIncluded(timeStamp)) { 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. *

*/ @Override 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())) { 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); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/MonthlyCalendar.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.calendar; import java.io.Serializable; import java.util.TimeZone; import 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 org.quartz.Calendar * @see org.quartz.impl.calendar.BaseCalendar * * @author Juergen Donnerstag */ public class MonthlyCalendar extends BaseCalendar implements Calendar, Serializable { private 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(); } @Override public Object clone() { MonthlyCalendar clone = (MonthlyCalendar) super.clone(); clone.excludeDays = excludeDays.clone(); return clone; } /** *

* 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)) { 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. *

*/ @Override public boolean isTimeIncluded(long timeStamp) { if (excludeAll) { 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)) { 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. *

*/ @Override public long getNextIncludedTime(long timeStamp) { if (excludeAll) { 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)) { cl.add(java.util.Calendar.DATE, 1); day = cl.get(java.util.Calendar.DAY_OF_MONTH); } return cl.getTime().getTime(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/calendar/WeeklyCalendar.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.calendar; import java.io.Serializable; import java.util.TimeZone; import 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 org.quartz.Calendar * @see org.quartz.impl.calendar.BaseCalendar * * @author Juergen Donnerstag */ public class WeeklyCalendar extends BaseCalendar implements Calendar, Serializable { private 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(); } @Override public Object clone() { WeeklyCalendar clone = (WeeklyCalendar) super.clone(); clone.excludeDays = excludeDays.clone(); return clone; } /** *

* Get the array with the week days *

*/ public boolean[] getDaysExcluded() { return excludeDays; } /** *

* Return true, if wday (see Calendar.get()) is defined to be excluded. 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. *

*/ @Override public boolean isTimeIncluded(long timeStamp) { if (excludeAll) { 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)) { 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. *

*/ @Override public long getNextIncludedTime(long timeStamp) { if (excludeAll) { 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)) { cl.add(java.util.Calendar.DATE, 1); wday = cl.get(java.util.Calendar.DAY_OF_WEEK); } return cl.getTime().getTime(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/AttributeRestoringConnectionInvocationHandler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* Protects a {@link java.sql.Connection}'s attributes from being permanently modified. *

* *

* 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 org.quartz.impl.jdbcjobstore.JobStoreSupport#getConnection() * @see org.quartz.impl.jdbcjobstore.JobStoreCMT#getNonManagedTXConnection() */ public class AttributeRestoringConnectionInvocationHandler implements InvocationHandler { private final 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 Logger getLog() { return LoggerFactory.getLogger(getClass()); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "setAutoCommit": setAutoCommit((Boolean) args[0]); break; case "setTransactionIsolation": setTransactionIsolation((Integer) args[0]); break; case "close": close(); break; default: try { return method.invoke(conn, args); } catch (InvocationTargetException ite) { throw (ite.getCause() != null ? ite.getCause() : ite); } } 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) { 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) { 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 restoreOriginalAttributes() { 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 { restoreOriginalAttributes(); conn.close(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/CUBRIDDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import com.mchange.v2.c3p0.C3P0ProxyConnection; import java.io.*; import java.lang.reflect.Method; import java.sql.*; /** *

This is a driver delegate for the CUBRID JDBC driver. For Quartz 2.x

* Blob handling instructions at * http://www.cubrid.org/manual/831/en/Using%20BLOB|CLOB Also at * http://www.cubrid.org/wiki_tutorials/entry/working-with-cubrid-blob-clob-data-types * * @author Timothy Anyona */ public class CUBRIDDelegate extends StdJDBCDelegate { /** *

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 */ @Override protected Object getObjectFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { Object obj = null; InputStream binaryInput; Blob blob = rs.getBlob(colName); byte[] bytes = blob.getBytes(1, (int) blob.length()); if (bytes != null && bytes.length != 0) { binaryInput = new ByteArrayInputStream(bytes); try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { InputStream binaryInput; Blob blob = rs.getBlob(colName); byte[] bytes = blob.getBytes(1, (int) blob.length()); if (bytes == null || bytes.length == 0) { return null; } binaryInput = new ByteArrayInputStream(bytes); return binaryInput; } return getObjectFromBlob(rs, colName); } /** * 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. */ @Override protected void setBytes(PreparedStatement ps, int index, ByteArrayOutputStream baos) throws SQLException { byte[] byteArray; if (baos == null) { //saving 0 byte blob may cause error? like http://dev.naver.com/projects/cubrid/issue/13710 - (0 byte bit) //alternatively store null since blob not null columns are not allowed (cubrid 8.4.1). may be allowed in future versions? byteArray = new byte[0]; } else { byteArray = baos.toByteArray(); } //quartz 2.x uses c3p0, c3p0 doesn't support createBlob method as of 0.9.2 Connection conn = ps.getConnection(); if (conn instanceof C3P0ProxyConnection) { try { C3P0ProxyConnection c3p0Conn = (C3P0ProxyConnection) conn; Method m = Connection.class.getMethod("createBlob", new Class[]{}); //will call createBlob method on the underlying connection Object[] args = new Object[]{}; //arguments to be passed to the method. none in this case Blob blob = (Blob) c3p0Conn.rawConnectionOperation(m, C3P0ProxyConnection.RAW_CONNECTION, args); blob.setBytes(1, byteArray); ps.setBlob(index, blob); } catch (Exception ex) { ex.printStackTrace(); } } else { Blob blob = ps.getConnection().createBlob(); blob.setBytes(1, byteArray); ps.setBlob(index, blob); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/CacheDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.sql.Blob; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** *

* This is a driver delegate for InterSystems Caché database. *

* *

* Works with the Oracle table creation scripts / schema. *

* * @author Franck Routier * @author Franck Routier */ public class CacheDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // protected methods that can be overridden by subclasses //--------------------------------------------------------------------------- /** * 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. */ @Override protected void setBytes(PreparedStatement ps, int index, ByteArrayOutputStream baos) throws SQLException { ps.setObject(index, ((baos == null) ? null : baos.toByteArray()), java.sql.Types.BLOB); } /** * {@inheritDoc} *

* Caché requires {@code java.sql.Blob} instances to be explicitly freed. */ @Override protected Object getObjectFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { Blob blob = rs.getBlob(colName); if (blob == null) { return null; } else { try { if (blob.length() == 0) { return null; } else { InputStream binaryInput = blob.getBinaryStream(); if (binaryInput == null) { return null; } else if (binaryInput instanceof ByteArrayInputStream && ((ByteArrayInputStream) binaryInput).available() == 0 ) { return null; } else { try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { return in.readObject(); } } } } finally { blob.free(); } } } /** * {@inheritDoc} *

* Caché requires {@code java.sql.Blob} instances to be explicitly freed. */ @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { Blob blob = rs.getBlob(colName); if (blob == null) { return null; } else { return new BlobFreeingStream(blob, blob.getBinaryStream()); } } else { return getObjectFromBlob(rs, colName); } } private static class BlobFreeingStream extends InputStream { private final Blob source; private final InputStream delegate; private BlobFreeingStream(Blob blob, InputStream stream) { this.source = blob; this.delegate = stream; } @Override public int read() throws IOException { return delegate.read(); } @Override public int read(byte[] b) throws IOException { return delegate.read(b); } @Override public int read(byte[] b, int off, int len) throws IOException { return delegate.read(b, off, len); } @Override public long skip(long n) throws IOException { return delegate.skip(n); } @Override public int available() throws IOException { return delegate.available(); } @Override public void close() throws IOException { try { delegate.close(); } finally { try { source.free(); } catch (SQLException ex) { throw new IOException(ex); } } } @Override public synchronized void mark(int readlimit) { delegate.mark(readlimit); } @Override public synchronized void reset() throws IOException { delegate.reset(); } @Override public boolean markSupported() { return delegate.markSupported(); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/CalendarIntervalTriggerPersistenceDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.util.TimeZone; import org.quartz.CalendarIntervalScheduleBuilder; import org.quartz.ScheduleBuilder; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.triggers.CalendarIntervalTriggerImpl; import org.quartz.spi.OperableTrigger; public class CalendarIntervalTriggerPersistenceDelegate extends SimplePropertiesTriggerPersistenceDelegateSupport { public boolean canHandleTriggerType(OperableTrigger trigger) { return ((trigger instanceof CalendarIntervalTriggerImpl) && !((CalendarIntervalTriggerImpl)trigger).hasAdditionalProperties()); } public String getHandledTriggerTypeDiscriminator() { return TTYPE_CAL_INT; } @Override protected SimplePropertiesTriggerProperties getTriggerProperties(OperableTrigger trigger) { CalendarIntervalTriggerImpl calTrig = (CalendarIntervalTriggerImpl)trigger; SimplePropertiesTriggerProperties props = new SimplePropertiesTriggerProperties(); props.setInt1(calTrig.getRepeatInterval()); props.setString1(calTrig.getRepeatIntervalUnit().name()); props.setInt2(calTrig.getTimesTriggered()); props.setString2(calTrig.getTimeZone().getID()); props.setBoolean1(calTrig.isPreserveHourOfDayAcrossDaylightSavings()); props.setBoolean2(calTrig.isSkipDayIfHourDoesNotExist()); return props; } @Override protected TriggerPropertyBundle getTriggerPropertyBundle(SimplePropertiesTriggerProperties props) { TimeZone tz = null; // if we use null, that's ok as system default tz will be used String tzId = props.getString2(); if(tzId != null && !tzId.trim().isEmpty()) // there could be null entries from previously released versions tz = TimeZone.getTimeZone(tzId); ScheduleBuilder sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule() .withInterval(props.getInt1(), IntervalUnit.valueOf(props.getString1())) .inTimeZone(tz) .preserveHourOfDayAcrossDaylightSavings(props.isBoolean1()) .skipDayIfHourDoesNotExist(props.isBoolean2()); int timesTriggered = props.getInt2(); String[] statePropertyNames = { "timesTriggered" }; Object[] statePropertyValues = { timesTriggered }; return new TriggerPropertyBundle(sb, statePropertyNames, statePropertyValues); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/Constants.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; /** *

* This interface can be implemented by any {@link * 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_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_SCHEDULER_NAME = "SCHED_NAME"; 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_NONCONCURRENT = "IS_NONCONCURRENT"; String COL_IS_UPDATE_DATA = "IS_UPDATE_DATA"; String COL_REQUESTS_RECOVERY = "REQUESTS_RECOVERY"; String COL_JOB_DATAMAP = "JOB_DATA"; String COL_JOB_CLASS = "JOB_CLASS_NAME"; String COL_DESCRIPTION = "DESCRIPTION"; // 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_FIRED_TRIGGERS columns names String COL_INSTANCE_NAME = "INSTANCE_NAME"; String COL_FIRED_TIME = "FIRED_TIME"; String COL_SCHED_TIME = "SCHED_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. */ @Deprecated String STATE_MISFIRED = "MISFIRED"; String ALL_GROUPS_PAUSED = "_$_ALL_GROUPS_PAUSED_$_"; // TRIGGER TYPES /** Simple Trigger type. */ String TTYPE_SIMPLE = "SIMPLE"; /** Cron Trigger type. */ String TTYPE_CRON = "CRON"; /** Calendar Interval Trigger type. */ String TTYPE_CAL_INT = "CAL_INT"; /** Daily Time Interval Trigger type. */ String TTYPE_DAILY_TIME_INT = "DAILY_I"; /** A general blob Trigger type. */ String TTYPE_BLOB = "BLOB"; } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/CronTriggerPersistenceDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.TimeZone; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.TriggerKey; import org.quartz.impl.triggers.CronTriggerImpl; import org.quartz.spi.OperableTrigger; public class CronTriggerPersistenceDelegate implements TriggerPersistenceDelegate, StdJDBCConstants { protected String tablePrefix; protected String schedNameLiteral; public void initialize(String theTablePrefix, String schedName) { this.tablePrefix = theTablePrefix; this.schedNameLiteral = "'" + schedName + "'"; } public String getHandledTriggerTypeDiscriminator() { return TTYPE_CRON; } public boolean canHandleTriggerType(OperableTrigger trigger) { return ((trigger instanceof CronTriggerImpl) && !((CronTriggerImpl)trigger).hasAdditionalProperties()); } public int deleteExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(DELETE_CRON_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } public int insertExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { CronTrigger cronTrigger = (CronTrigger)trigger; PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(INSERT_CRON_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setString(3, cronTrigger.getCronExpression()); ps.setString(4, cronTrigger.getTimeZone().getID()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } public TriggerPropertyBundle loadExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(Util.rtp(SELECT_CRON_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); rs = ps.executeQuery(); if (rs.next()) { return loadExtendedTriggerPropertiesFromResultSet(rs, triggerKey); } throw new NoRecordFoundException(triggerKey, schedNameLiteral, Util.rtp(SELECT_CRON_TRIGGER, tablePrefix, schedNameLiteral)); } finally { Util.closeResultSet(rs); Util.closeStatement(ps); } } public TriggerPropertyBundle loadExtendedTriggerPropertiesFromResultSet(ResultSet rs, TriggerKey triggerKey) throws SQLException { if (Util.areNull(rs, COL_CRON_EXPRESSION, COL_TIME_ZONE_ID)) { throw new NoRecordFoundException(triggerKey, schedNameLiteral, this.getClass()); } String cronExpr = rs.getString(COL_CRON_EXPRESSION); String timeZoneId = rs.getString(COL_TIME_ZONE_ID); CronScheduleBuilder cb = CronScheduleBuilder.cronSchedule(cronExpr); if (timeZoneId != null) cb.inTimeZone(TimeZone.getTimeZone(timeZoneId)); return new TriggerPropertyBundle(cb, null, null); } public boolean hasInlinedResultSetProperties() { return true; } public int updateExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { CronTrigger cronTrigger = (CronTrigger)trigger; PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(UPDATE_CRON_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, cronTrigger.getCronExpression()); ps.setString(2, cronTrigger.getTimeZone().getID()); ps.setString(3, trigger.getKey().getName()); ps.setString(4, trigger.getKey().getGroup()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/DB2v6Delegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.quartz.JobKey; /** * 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 { @SuppressWarnings("hiding") public static final String SELECT_NUM_JOBS = "SELECT COUNT(*) FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; @SuppressWarnings("hiding") public static final String SELECT_NUM_TRIGGERS_FOR_JOB = "SELECT COUNT(*) FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; @SuppressWarnings("hiding") public static final String SELECT_NUM_TRIGGERS = "SELECT COUNT(*) FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; @SuppressWarnings("hiding") public static final String SELECT_NUM_CALENDARS = "SELECT COUNT(*) FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; @Override 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); } } @Override public int selectNumTriggersForJob(Connection conn, JobKey jobKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS_FOR_JOB)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); if (rs.next()) { return rs.getInt(1); } else { return 0; } } finally { closeResultSet(rs); closeStatement(ps); } } @Override 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); } } @Override 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 ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/DB2v7Delegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.ByteArrayOutputStream; import java.sql.PreparedStatement; import java.sql.SQLException; /** * 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 { /** * 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. */ @Override 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. */ @Override protected void setBoolean(PreparedStatement ps, int index, boolean val) throws SQLException { ps.setString(index, ((val) ? "1" : "0")); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/DB2v8Delegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.PreparedStatement; import java.sql.SQLException; /** * 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 { /** * Sets the designated parameter to the given Java boolean value. * This translates the boolean to 1/0 for true/false. */ @Override protected void setBoolean(PreparedStatement ps, int index, boolean val) throws SQLException { ps.setInt(index, ((val) ? 1 : 0)); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/DBSemaphore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.Connection; import java.util.HashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 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 Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ final ThreadLocal> lockOwners = new ThreadLocal<>(); private String sql; private String insertSql; private String tablePrefix; private String schedName; private String expandedSQL; private String expandedInsertSQL; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public DBSemaphore(String tablePrefix, String schedName, String defaultSQL, String defaultInsertSQL) { this.tablePrefix = tablePrefix; this.schedName = schedName; setSQL(defaultSQL); setInsertSQL(defaultInsertSQL); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger getLog() { return log; } private HashSet getThreadLocks() { HashSet threadLocks = 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 theExpandedSQL, String theExpandedInsertSQL) 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 { if(log.isDebugEnabled()) { log.debug("Lock '{}' is desired by: {}", lockName, Thread.currentThread().getName()); } if (!isLockOwner(lockName)) { executeSQL(conn, lockName, expandedSQL, expandedInsertSQL); if(log.isDebugEnabled()) { log.debug("Lock '{}' given to: {}", lockName, Thread.currentThread().getName()); } getThreadLocks().add(lockName); //getThreadLocksObtainer().put(lockName, new // Exception("Obtainer...")); } else if(log.isDebugEnabled()) { log.debug("Lock '{}' Is already owned by: {}", lockName, Thread.currentThread().getName()); } return true; } /** * Release the lock on the identified resource if it is held by the calling * thread. */ public void releaseLock(String lockName) { if (isLockOwner(lockName)) { if(getLog().isDebugEnabled()) { getLog().debug("Lock '{}' returned by: {}", lockName, Thread.currentThread().getName()); } getThreadLocks().remove(lockName); //getThreadLocksObtainer().remove(lockName); } else if (getLog().isDebugEnabled()) { getLog().warn("Lock '{}' attempt to return by: {} -- but not owner!", lockName, Thread.currentThread().getName(), new Exception("stack-trace of wrongful returner")); } } /** * Determine whether the calling thread owns a lock on the identified * resource. */ public boolean isLockOwner(String lockName) { 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().isEmpty())) { this.sql = sql.trim(); } setExpandedSQL(); } protected void setInsertSQL(String insertSql) { if ((insertSql != null) && (!insertSql.trim().isEmpty())) { this.insertSql = insertSql.trim(); } setExpandedSQL(); } private void setExpandedSQL() { if (getTablePrefix() != null && getSchedName() != null && sql != null && insertSql != null) { expandedSQL = Util.rtp(this.sql, getTablePrefix(), getSchedulerNameLiteral()); expandedInsertSQL = Util.rtp(this.insertSql, getTablePrefix(), getSchedulerNameLiteral()); } } private String schedNameLiteral = null; protected String getSchedulerNameLiteral() { if(schedNameLiteral == null) schedNameLiteral = "'" + schedName + "'"; return schedNameLiteral; } public String getSchedName() { return schedName; } public void setSchedName(String schedName) { this.schedName = schedName; setExpandedSQL(); } protected String getTablePrefix() { return tablePrefix; } public void setTablePrefix(String tablePrefix) { this.tablePrefix = tablePrefix; setExpandedSQL(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/DailyTimeIntervalTriggerPersistenceDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.quartz.DailyTimeIntervalScheduleBuilder; import org.quartz.DailyTimeIntervalTrigger; import org.quartz.TimeOfDay; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.triggers.DailyTimeIntervalTriggerImpl; import org.quartz.spi.OperableTrigger; /** * Persist a DailyTimeIntervalTrigger by converting internal fields to and from * SimplePropertiesTriggerProperties. * * @see DailyTimeIntervalScheduleBuilder * @see DailyTimeIntervalTrigger * * @since 2.1.0 * * @author Zemian Deng <saltnlight5@gmail.com> */ public class DailyTimeIntervalTriggerPersistenceDelegate extends SimplePropertiesTriggerPersistenceDelegateSupport { public boolean canHandleTriggerType(OperableTrigger trigger) { return ((trigger instanceof DailyTimeIntervalTrigger) && !((DailyTimeIntervalTriggerImpl)trigger).hasAdditionalProperties()); } public String getHandledTriggerTypeDiscriminator() { return TTYPE_DAILY_TIME_INT; } @Override protected SimplePropertiesTriggerProperties getTriggerProperties(OperableTrigger trigger) { DailyTimeIntervalTriggerImpl dailyTrigger = (DailyTimeIntervalTriggerImpl)trigger; SimplePropertiesTriggerProperties props = new SimplePropertiesTriggerProperties(); props.setInt1(dailyTrigger.getRepeatInterval()); props.setString1(dailyTrigger.getRepeatIntervalUnit().name()); props.setInt2(dailyTrigger.getTimesTriggered()); Set days = dailyTrigger.getDaysOfWeek(); String daysStr = join(days, ","); props.setString2(daysStr); StringBuilder timeOfDayBuffer = new StringBuilder(); TimeOfDay startTimeOfDay = dailyTrigger.getStartTimeOfDay(); if (startTimeOfDay != null) { timeOfDayBuffer.append(startTimeOfDay.getHour()).append(","); timeOfDayBuffer.append(startTimeOfDay.getMinute()).append(","); timeOfDayBuffer.append(startTimeOfDay.getSecond()).append(","); } else { timeOfDayBuffer.append(",,,"); } TimeOfDay endTimeOfDay = dailyTrigger.getEndTimeOfDay(); if (endTimeOfDay != null) { timeOfDayBuffer.append(endTimeOfDay.getHour()).append(","); timeOfDayBuffer.append(endTimeOfDay.getMinute()).append(","); timeOfDayBuffer.append(endTimeOfDay.getSecond()); } else { timeOfDayBuffer.append(",,,"); } props.setString3(timeOfDayBuffer.toString()); props.setLong1(dailyTrigger.getRepeatCount()); return props; } private String join(Set days, String sep) { StringBuilder sb = new StringBuilder(); if (days == null || days.size() <= 0) return ""; Iterator itr = days.iterator(); sb.append(itr.next()); while(itr.hasNext()) { sb.append(sep).append(itr.next()); } return sb.toString(); } @Override protected TriggerPropertyBundle getTriggerPropertyBundle(SimplePropertiesTriggerProperties props) { int repeatCount = (int)props.getLong1(); int interval = props.getInt1(); String intervalUnitStr = props.getString1(); String daysOfWeekStr = props.getString2(); String timeOfDayStr = props.getString3(); IntervalUnit intervalUnit = IntervalUnit.valueOf(intervalUnitStr); DailyTimeIntervalScheduleBuilder scheduleBuilder = DailyTimeIntervalScheduleBuilder .dailyTimeIntervalSchedule() .withInterval(interval, intervalUnit) .withRepeatCount(repeatCount); if (daysOfWeekStr != null) { Set daysOfWeek = new HashSet<>(); String[] nums = daysOfWeekStr.split(","); if (nums.length > 0) { for (String num : nums) { daysOfWeek.add(Integer.parseInt(num)); } scheduleBuilder.onDaysOfTheWeek(daysOfWeek); } } else { scheduleBuilder.onDaysOfTheWeek(DailyTimeIntervalScheduleBuilder.ALL_DAYS_OF_THE_WEEK); } if (timeOfDayStr != null) { String[] nums = timeOfDayStr.split(","); TimeOfDay startTimeOfDay; if (nums.length >= 3) { int hour = Integer.parseInt(nums[0]); int min = Integer.parseInt(nums[1]); int sec = Integer.parseInt(nums[2]); startTimeOfDay = new TimeOfDay(hour, min, sec); } else { startTimeOfDay = TimeOfDay.hourMinuteAndSecondOfDay(0, 0, 0); } scheduleBuilder.startingDailyAt(startTimeOfDay); TimeOfDay endTimeOfDay; if (nums.length >= 6) { int hour = Integer.parseInt(nums[3]); int min = Integer.parseInt(nums[4]); int sec = Integer.parseInt(nums[5]); endTimeOfDay = new TimeOfDay(hour, min, sec); } else { endTimeOfDay = TimeOfDay.hourMinuteAndSecondOfDay(23, 59, 59); } scheduleBuilder.endingDailyAt(endTimeOfDay); } else { scheduleBuilder.startingDailyAt(TimeOfDay.hourMinuteAndSecondOfDay(0, 0, 0)); scheduleBuilder.endingDailyAt(TimeOfDay.hourMinuteAndSecondOfDay(23, 59, 59)); } int timesTriggered = props.getInt2(); String[] statePropertyNames = { "timesTriggered" }; Object[] statePropertyValues = { timesTriggered }; return new TriggerPropertyBundle(scheduleBuilder, statePropertyNames, statePropertyValues); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/DriverDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 org.quartz.Calendar; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.JobPersistenceException; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.OperableTrigger; import org.quartz.utils.Key; import org.slf4j.Logger; /** *

* This is the base interface for all driver delegate classes. *

* *

* This interface is very similar to the {@link * 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 org.quartz.impl.jdbcjobstore.StdJDBCDelegate} class. *

* * @author Jeffrey Wescott * @author James House */ public interface DriverDelegate { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * @param initString of the format: settingName=settingValue|otherSettingName=otherSettingValue|... * @throws NoSuchDelegateException */ void initialize(Logger logger, String tablePrefix, String schedName, String instanceId, ClassLoadHelper classLoadHelper, boolean useProperties, String initString) throws NoSuchDelegateException; //--------------------------------------------------------------------------- // 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 * org.quartz.utils.Key} objects */ List 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 * org.quartz.utils.Key} objects */ List 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 org.quartz.utils.Key} objects. Must not be null. * * @return Whether there are more misfired triggers left to find beyond * the given count. */ boolean hasMisfiredTriggersInState(Connection conn, String state1, long ts, int count, List resultList) throws SQLException; /** *

* Get the number of triggers in the given state that have * misfired - according to the given timestamp. *

* * @param conn the DB Connection */ int countMisfiredTriggersInState( Connection conn, String state1, 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 * org.quartz.utils.Key} objects */ List 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 * 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 org.quartz.Trigger} objects */ List 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; //--------------------------------------------------------------------------- // 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 * * @return an array of {@link * org.quartz.utils.Key} objects */ List selectTriggerKeysForJob(Connection conn, JobKey jobKey) throws SQLException; /** *

* Delete the job detail record for the given job. *

* * @param conn * the DB Connection * * @return the number of rows deleted */ int deleteJobDetail(Connection conn, JobKey jobKey) throws SQLException; /** *

* Check whether or not the given job disallows concurrent execution. *

* * @param conn * the DB Connection * * @return true if the job exists and disallows concurrent execution, false otherwise */ boolean isJobNonConcurrent(Connection conn, JobKey jobKey) throws SQLException; /** *

* Check whether or not the given job exists. *

* * @param conn * the DB Connection * * @return true if the job exists, false otherwise */ boolean jobExists(Connection conn, JobKey jobKey) 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; /** *

* Select the JobDetail object for a given job name / group name. *

* * @param conn * the DB Connection * * @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, JobKey jobKey, ClassLoadHelper loadHelper) throws ClassNotFoundException, IOException, SQLException; /** *

* Select all the JobDetail object for a given job name / group name. *

* * @param conn * the DB Connection * @param matcher * the group matcher to evaluate against the known jobs * @return A list of populated JobDetail objects * @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 */ List selectJobDetails(Connection conn, GroupMatcher matcher, 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 */ List selectJobGroups(Connection conn) throws SQLException; /** *

* Select all of the jobs contained in a given group. *

* * @param conn * the DB Connection * @param matcher * the group matcher to evaluate against the known jobs * @return an array of String job names */ Set selectJobsInGroup(Connection conn, GroupMatcher matcher) 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, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException; /** *

* 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, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException; /** *

* Check whether or not a trigger exists. *

* * @param conn * the DB Connection * * @return the number of rows updated */ boolean triggerExists(Connection conn, TriggerKey triggerKey) throws SQLException; /** *

* Update the state for a given trigger. *

* * @param conn * the DB Connection * * @param state * the new state for the trigger * @return the number of rows updated */ int updateTriggerState(Connection conn, TriggerKey triggerKey, 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 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, TriggerKey triggerKey, 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 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, TriggerKey triggerKey, String newState, String oldState1, String oldState2, String oldState3) 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 matcher * the group matcher to evaluate against the known triggers * @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, GroupMatcher matcher, 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 matcher * the matcher to evaluate against the known 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, GroupMatcher matcher, String newState, String oldState) throws SQLException; /** *

* Update the states of all triggers associated with the given job. *

* * @param conn * the DB Connection * * @param state * the new state for the triggers * @return the number of rows updated */ int updateTriggerStatesForJob(Connection conn, JobKey jobKey, 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 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, JobKey jobKey, String state, String oldState) throws SQLException; /** *

* Delete the base trigger data for a trigger. *

* * @param conn * the DB Connection * * @return the number of rows deleted */ int deleteTrigger(Connection conn, TriggerKey triggerKey) throws SQLException; /** *

* Select the number of triggers associated with a given job. *

* * @param conn * the DB Connection * @return the number of triggers for the given job */ int selectNumTriggersForJob(Connection conn, JobKey jobKey) throws SQLException; /** *

* Select the job to which the trigger is associated. *

* * @param conn * the DB Connection * * @return the {@link org.quartz.JobDetail} object * associated with the given trigger */ JobDetail selectJobForTrigger(Connection conn, ClassLoadHelper loadHelper, TriggerKey triggerKey) throws ClassNotFoundException, SQLException, IOException; /** *

* Select the job to which the trigger is associated. Allow option to load actual job class or not. When case of * remove, we do not need to load the class, which in many cases, it's no longer exists. *

*/ JobDetail selectJobForTrigger(Connection conn, ClassLoadHelper loadHelper, TriggerKey triggerKey, boolean loadJobClass) throws ClassNotFoundException, SQLException, IOException; /** *

* Select the triggers for a job *

* * @param conn * the DB Connection * * @return an array of (@link org.quartz.Trigger) objects * associated with a given job. * @throws SQLException * @throws JobPersistenceException */ List selectTriggersForJob(Connection conn, JobKey jobKey) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException; /** *

* Select the triggers for a calendar *

* * @param conn * the DB Connection * @param calName * the name of the calendar * @return an array of (@link org.quartz.Trigger) objects * associated with the given calendar. * @throws SQLException * @throws JobPersistenceException */ List selectTriggersForCalendar(Connection conn, String calName) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException; /** *

* Select a trigger. *

* * @param conn * the DB Connection * * @return the {@link org.quartz.Trigger} object * @throws JobPersistenceException */ OperableTrigger selectTrigger(Connection conn, TriggerKey triggerKey) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException; /** * Gets a list of trigger by job and trigger group matchers *
* Note: This requires the use of enhanced statements * @param conn the connection * @param jobMatcher Job Group Matcher * @param triggerMatcher Trigger group matcher * @return list of trigger, or empty list if no trigger found * @throws SQLException * @throws ClassNotFoundException * @throws IOException * @throws JobPersistenceException */ List getTriggersByJobAndTriggerGroup(Connection conn, GroupMatcher jobMatcher, GroupMatcher triggerMatcher) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException; /** *

* 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 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 * * @return the {@link org.quartz.Trigger} object */ String selectTriggerState(Connection conn, TriggerKey triggerKey) throws SQLException; /** *

* Select a trigger' status (state and next fire time). *

* * @param conn * the DB Connection * * @return a TriggerStatus object, or null */ TriggerStatus selectTriggerStatus(Connection conn, TriggerKey triggerKey) 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 */ List selectTriggerGroups(Connection conn) throws SQLException; List selectTriggerGroups(Connection conn, GroupMatcher matcher) throws SQLException; /** *

* Select all of the triggers contained in a given group. *

* * @param conn * the DB Connection * @param matcher * to evaluate against known triggers * @return a Set of TriggerKeys */ Set selectTriggersInGroup(Connection conn, GroupMatcher matcher) 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 */ List selectTriggersInState(Connection conn, String state) throws SQLException; int insertPausedTriggerGroup(Connection conn, String groupName) throws SQLException; int deletePausedTriggerGroup(Connection conn, String groupName) throws SQLException; int deletePausedTriggerGroup(Connection conn, GroupMatcher matcher) 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 */ List 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. */ @Deprecated 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 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 * lowest 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. * * @deprecated - This remained for compatibility reason. Use {@link #selectTriggerToAcquire(Connection, long, long, int)} instead. */ @Deprecated List selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan) 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) * @param maxCount * maximum number of trigger keys allow to acquired in the returning list. * * @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, int maxCount) 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, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException; /** *

* Update a fired trigger record. Will update the fields * "firing instance", "fire time", and "state". *

* * @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 updateFiredTrigger(Connection conn, OperableTrigger 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, JobKey jobKey) 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; /** * Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws SQLException */ void clearData(Connection conn) throws SQLException; /** * Returns true if enhanced statements for the database operations is enabled * @return true if using enhanced statements */ default boolean isUsingEnhancedStatements() { return false; } /** * Set to true to use enhanced statements for the database operations * @param useEnhancedStatements true to use enhanced statements */ default void setUseEnhancedStatements(boolean useEnhancedStatements) { //no-op } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/FiredTriggerRecord.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.quartz.JobKey; import org.quartz.TriggerKey; /** *

* Conveys the state of a fired-trigger record. *

* * @author James House */ public class FiredTriggerRecord implements java.io.Serializable { private static final long serialVersionUID = -7183096398865657533L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private String fireInstanceId; private long fireTimestamp; private long scheduleTimestamp; private String schedulerInstanceId; private TriggerKey triggerKey; private String fireInstanceState; private JobKey jobKey; private boolean jobDisallowsConcurrentExecution; private boolean jobRequestsRecovery; private int priority; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public String getFireInstanceId() { return fireInstanceId; } public long getFireTimestamp() { return fireTimestamp; } public long getScheduleTimestamp() { return scheduleTimestamp; } public boolean isJobDisallowsConcurrentExecution() { return jobDisallowsConcurrentExecution; } public JobKey getJobKey() { return jobKey; } public String getSchedulerInstanceId() { return schedulerInstanceId; } public TriggerKey getTriggerKey() { return triggerKey; } public String getFireInstanceState() { return fireInstanceState; } public void setFireInstanceId(String string) { fireInstanceId = string; } public void setFireTimestamp(long l) { fireTimestamp = l; } public void setScheduleTimestamp(long l) { scheduleTimestamp = l; } public void setJobDisallowsConcurrentExecution(boolean b) { jobDisallowsConcurrentExecution = b; } public void setJobKey(JobKey key) { jobKey = key; } public void setSchedulerInstanceId(String string) { schedulerInstanceId = string; } public void setTriggerKey(TriggerKey key) { triggerKey = key; } public void setFireInstanceState(String string) { fireInstanceState = string; } public boolean isJobRequestsRecovery() { return jobRequestsRecovery; } public void setJobRequestsRecovery(boolean b) { jobRequestsRecovery = b; } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/GaussDBDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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; /** *

* This is a driver delegate for the GaussDB JDBC driver. * Notes: Original code of this file is based on PostgreSQLDelegate. * *

* * @author chen zhida */ public class GaussDBDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override protected Object getObjectFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { InputStream binaryInput; byte[] bytes = rs.getBytes(colName); Object obj = null; if(bytes != null && bytes.length != 0) { binaryInput = new ByteArrayInputStream(bytes); try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { InputStream binaryInput; byte[] bytes = rs.getBytes(colName); if(bytes == null || bytes.length == 0) { return null; } binaryInput = new ByteArrayInputStream(bytes); return binaryInput; } return getObjectFromBlob(rs, colName); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/HSQLDBDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.sql.ResultSet; import java.sql.SQLException; /** *

* This is a driver delegate for the HSQLDB database. *

* * @author James House * @author Jeffrey Wescott */ public class HSQLDBDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override 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; try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { return rs.getBinaryStream(colName); } return getObjectFromBlob(rs, colName); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/InvalidConfigurationException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 { private static final long serialVersionUID = 1836325935209404611L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public InvalidConfigurationException(String msg) { super(msg); } public InvalidConfigurationException() { super(); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/JTANonClusteredSemaphore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.Connection; import java.util.HashSet; import javax.naming.InitialContext; import javax.naming.NamingException; import jakarta.transaction.Synchronization; import jakarta.transaction.SystemException; import jakarta.transaction.Transaction; import jakarta.transaction.TransactionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides in memory thread/resource locking that is JTA * {@link jakarta.transaction.Transaction} aware. * It is most appropriate for use when using * {@link 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 jakarta.transaction.Transaction}, then this Semaphore operates * just like {@link org.quartz.impl.jdbcjobstore.SimpleSemaphore}. *

* *

* By default, this class looks for the {@link jakarta.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 org.quartz.impl.jdbcjobstore.SimpleSemaphore */ public class JTANonClusteredSemaphore implements Semaphore { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static final String DEFAULT_TRANSACTION_MANAGER_LOCATION = "java:TransactionManager"; final ThreadLocal> lockOwners = new ThreadLocal<>(); final HashSet locks = new HashSet<>(); private final Logger log = LoggerFactory.getLogger(getClass()); private String transactionManagerJNDIName = DEFAULT_TRANSACTION_MANAGER_LOCATION; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger getLog() { return log; } public void setTransactionManagerJNDIName(String transactionManagerJNDIName) { this.transactionManagerJNDIName = transactionManagerJNDIName; } private HashSet getThreadLocks() { HashSet threadLocks = 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(); if(log.isDebugEnabled()) { log.debug("Lock '{}' is desired by: {}", lockName, Thread.currentThread().getName()); } if (!isLockOwner(conn, lockName)) { if(log.isDebugEnabled()) { log.debug("Lock '{}' is being obtained: {}", lockName, Thread.currentThread().getName()); } while (locks.contains(lockName)) { try { this.wait(); } catch (InterruptedException ie) { if(log.isDebugEnabled()) { log.debug("Lock '{}' was not obtained by: {}", lockName, 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 '{}' given to: {}", lockName, Thread.currentThread().getName()); } getThreadLocks().add(lockName); locks.add(lockName); } else if(log.isDebugEnabled()) { log.debug("Lock '{}' already owned by: {} -- but not owner!", lockName, Thread.currentThread().getName(), new Exception("stack-trace of wrongful returner")); } return true; } /** * Helper method to get the current {@link jakarta.transaction.Transaction} * from the {@link jakarta.transaction.TransactionManager} in JNDI. * * @return The current {@link jakarta.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(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) { Transaction t = getTransaction(); if (t != null) { if(getLog().isDebugEnabled()) { getLog().debug("Lock '{}' is in a JTA transaction. Return deferred by: {}", lockName, 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 '{}' returned by: {}", lockName, Thread.currentThread().getName()); } getThreadLocks().remove(lockName); locks.remove(lockName); this.notify(); } else if (getLog().isDebugEnabled()) { getLog().debug("Lock '{}' attempt to return by: {} -- but not owner!", lockName, Thread.currentThread().getName(), 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 jakarta.transaction.Transaction} so that the lock * will be released when the transaction completes. */ private class SemaphoreSynchronization implements Synchronization { private final 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 } } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/JobStoreCMT.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.Connection; import java.sql.SQLException; import org.quartz.JobPersistenceException; import org.quartz.SchedulerConfigException; import org.quartz.impl.jdbcjobstore.JobStoreSupport; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.SchedulerSignaler; import org.quartz.utils.DBConnectionManager; /** *

* JobStoreCMT is meant to be used in an application-server * environment that provides container-managed-transactions. No commit / * rollback will be handled by this class. *

* *

* If you need commit / rollback, use {@link * 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 helpful 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; } @Override public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException { if (nonManagedTxDsName == null) { throw new SchedulerConfigException( "Non-ManagedTX DataSource name not set! " + "If your 'org.quartz.jobStore.dataSource' is XA, then set " + "'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."); } @Override public void shutdown() { super.shutdown(); try { DBConnectionManager.getInstance().shutdown(getNonManagedTXDataSource()); } catch (SQLException sqle) { getLog().warn("Database connection shutdown unsuccessful.", sqle); } } @Override protected Connection getNonManagedTXConnection() throws JobPersistenceException { Connection conn; try { conn = DBConnectionManager.getInstance().getConnection( getNonManagedTXDataSource()); } catch (Throwable e) { throw new JobPersistenceException( "Failed to obtain DB connection from data source '" + getNonManagedTXDataSource() + "': " + e, e); } 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 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 acquired 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 acquire, for example * "TRIGGER_ACCESS". If null, then no lock is acquired, but the * txCallback is still executed in a transaction. * * @see JobStoreSupport#executeInNonManagedTXLock(java.lang.String, org.quartz.impl.jdbcjobstore.JobStoreSupport.TransactionCallback, org.quartz.impl.jdbcjobstore.JobStoreSupport.TransactionValidator) * @see JobStoreTX#executeInLock(java.lang.String, org.quartz.impl.jdbcjobstore.JobStoreSupport.TransactionCallback) * @see JobStoreSupport#getNonManagedTXConnection() * @see JobStoreSupport#getConnection() */ @Override 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 acquiring 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(lockName, transOwner); } finally { cleanupConnection(conn); } } } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/JobStoreSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; 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.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.quartz.Calendar; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.JobPersistenceException; import org.quartz.ObjectAlreadyExistsException; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.Trigger.TriggerState; import org.quartz.TriggerKey; import org.quartz.impl.DefaultThreadExecutor; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.matchers.StringMatcher; import org.quartz.impl.matchers.StringMatcher.StringOperatorName; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.JobStore; import org.quartz.spi.OperableTrigger; import org.quartz.spi.SchedulerSignaler; import org.quartz.spi.ThreadExecutor; import org.quartz.spi.TriggerFiredBundle; import org.quartz.spi.TriggerFiredResult; import org.quartz.utils.DBConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* 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_STATE_ACCESS = "STATE_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 String delegateInitString; protected Class delegateClass = StdJDBCDelegate.class; protected final 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 schedSignaler; protected int maxToRecoverAtATime = 20; private boolean useEnhancedStatements = false; private boolean setTxIsolationLevelSequential = false; private boolean acquireTriggersWithinLock = false; private long dbRetryInterval = 15000L; // 15 secs private boolean makeThreadsDaemons = false; private boolean threadsInheritInitializersClassLoadContext = false; private ClassLoader initializersLoader = null; private boolean doubleCheckLockMisfireHandler = true; private final Logger log = LoggerFactory.getLogger(getClass()); private ThreadExecutor threadExecutor = new DefaultThreadExecutor(); private volatile boolean schedulerRunning = false; private volatile boolean shutdown = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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. *

*/ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setUseProperties(String useProp) { if (useProp == null) { useProp = "false"; } this.useProperties = Boolean.parseBoolean(useProp); } /** *

* 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; } public void setThreadPoolSize(final int poolSize) { // } public void setThreadExecutor(ThreadExecutor threadExecutor) { this.threadExecutor = threadExecutor; } public ThreadExecutor getThreadExecutor() { return threadExecutor; } /** * Get the instance name of the Scheduler (must be unique within this server instance). */ public String getInstanceName() { return instanceName; } public long getEstimatedTimeToReleaseAndAcquireTrigger() { return 70; } /** *

* Set whether this instance is part of a cluster. *

*/ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ 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. *

*/ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ 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. *

*/ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ 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 databases (such as * MS SQLServer) seem to require this to avoid deadlocks under high load, * while others seem to do fine without. Settings this to false means * isolation guarantees between job scheduling and trigger acquisition are * entirely enforced by the database. Depending on the database and it's * configuration this may cause unusual scheduling behaviors. * *

Setting this property to false will provide a * significant performance increase during the addition of new jobs * and triggers.

* * @param lockOnInsert whether locking should be used when inserting new jobs/triggers */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ 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 the misfire threshold to use, in millis */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setMisfireThreshold(long misfireThreshold) { if (misfireThreshold < 1) { throw new IllegalArgumentException( "Misfire threshold 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 helpful in a few situations, such as if you * have a driver that complains if it is called when it is already off. * * @param b whether or not autocommit should be set to false on db connections */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setDontSetAutoCommitFalse(boolean b) { dontSetAutoCommitFalse = b; } public boolean isTxIsolationLevelSerializable() { return setTxIsolationLevelSequential; } /** * Set the transaction isolation level of DB connections to sequential. * * @param b whether isolation level should be set to sequential. */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ 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. * * However, if batch acquisition is used, it is important for this behavior * to be used for all dbs. */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setAcquireTriggersWithinLock(boolean acquireTriggersWithinLock) { this.acquireTriggersWithinLock = acquireTriggersWithinLock; } /** *

* Set the JDBC driver delegate class. *

* * @param delegateClassName * the delegate class name */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setDriverDelegateClass(String delegateClassName) throws InvalidConfigurationException { synchronized(this) { this.delegateClassName = delegateClassName; } } /** *

* Get the JDBC driver delegate class name. *

* * @return the delegate class name */ public String getDriverDelegateClass() { return delegateClassName; } /** *

* Set the JDBC driver delegate's initialization string. *

* * @param delegateInitString * the delegate init string */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setDriverDelegateInitString(String delegateInitString) throws InvalidConfigurationException { this.delegateInitString = delegateInitString; } /** *

* Get the JDBC driver delegate's initialization string. *

* * @return the delegate init string */ public String getDriverDelegateInitString() { return delegateInitString; } 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) */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ 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 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 misfired * Triggers. */ @SuppressWarnings("UnusedDeclaration") /* called reflectively */ public void setDoubleCheckLockMisfireHandler( boolean doubleCheckLockMisfireHandler) { this.doubleCheckLockMisfireHandler = doubleCheckLockMisfireHandler; } @Override public long getAcquireRetryDelay(int failureCount) { return dbRetryInterval; } //--------------------------------------------------------------------------- // interface methods //--------------------------------------------------------------------------- protected Logger 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.schedSignaler = 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()) { if(getDriverDelegateClass() != null && getDriverDelegateClass().contains(MSSQLDelegate.class.getSimpleName())) { if(getSelectWithLockSQL() == null) { String msSqlDflt = "SELECT * FROM {0}LOCKS WITH (UPDLOCK,ROWLOCK) WHERE " + COL_SCHEDULER_NAME + " = {1} AND LOCK_NAME = ?"; getLog().info("Detected usage of MSSQLDelegate class - defaulting 'selectWithLockSQL' to '{}'.", msSqlDflt); setSelectWithLockSQL(msSqlDflt); } } getLog().info("Using db table-based data access locking (synchronization)."); setLockHandler(new StdRowLockSemaphore(getTablePrefix(), getInstanceName(), getSelectWithLockSQL())); } else { getLog().info( "Using thread monitor-based data access locking (synchronization)."); setLockHandler(new SimpleSemaphore()); } } } /** * @see 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 occurred during job recovery.", se); } } misfireHandler = new MisfireHandler(); if(initializersLoader != null) misfireHandler.setContextClassLoader(initializersLoader); misfireHandler.initialize(); schedulerRunning = true; getLog().debug("JobStore background threads started (as scheduler was started)."); } public void schedulerPaused() { schedulerRunning = false; } public void schedulerResumed() { schedulerRunning = true; } /** *

* 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() { shutdown = true; if (misfireHandler != null) { misfireHandler.shutdown(); try { misfireHandler.join(); } catch (InterruptedException ignore) { } } if (clusterManagementThread != null) { clusterManagementThread.shutdown(); try { clusterManagementThread.join(); } catch (InterruptedException ignore) { } } try { DBConnectionManager.getInstance().shutdown(getDataSource()); } catch (SQLException sqle) { getLog().warn("Database connection shutdown unsuccessful.", sqle); } getLog().debug("JobStore background threads shutdown."); } 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; try { conn = DBConnectionManager.getInstance().getConnection( getDataSource()); } catch (Throwable e) { throw new JobPersistenceException( "Failed to obtain DB connection from data source '" + getDataSource() + "': " + e, e); } 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 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 ignored) {} throw new JobPersistenceException( "Failure setting up connection.", e); } return conn; } protected void releaseLock(String lockName, boolean doIt) { if (doIt) { try { getLockHandler().releaseLock(lockName); } catch (LockException le) { getLog().error("Error returning lock: {}", le.getMessage(), le); } } } /** * 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 executeVoid(Connection conn) throws JobPersistenceException { recoverJobs(conn); } }, null); } /** *

* 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 {} triggers from 'acquired' / 'blocked' state.", rows); // clean up misfired jobs recoverMisfiredJobs(conn, true); // recover jobs marked for recovery that were not fully executed List recoveringJobTriggers = getDelegate() .selectTriggersForRecoveringJobs(conn); getLog() .info("Recovering {} jobs that were in-progress at the time of the last shut-down.", recoveringJobTriggers.size()); for (OperableTrigger recoveringJobTrigger: recoveringJobTriggers) { if (jobExists(conn, recoveringJobTrigger.getJobKey())) { recoveringJobTrigger.computeFirstFireTime(null); storeTrigger(conn, recoveringJobTrigger, null, false, STATE_WAITING, false, true); } } getLog().info("Recovery complete."); // remove lingering 'complete' triggers... List cts = getDelegate().selectTriggersInState(conn, STATE_COMPLETE); for(TriggerKey ct: cts) { removeTrigger(conn, ct); } getLog().info("Removed {} 'complete' triggers.", cts.size()); // clean up any fired trigger entries int n = getDelegate().deleteFiredTriggers(conn); getLog().info("Removed {} stale fired job entries.", n); } 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 final boolean _hasMoreMisfiredTriggers; private final int _processedMisfiredTriggerCount; private final 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 LinkedList<>(); 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().hasMisfiredTriggersInState( conn, STATE_WAITING, getMisfireTime(), maxMisfiresToHandleAtATime, misfiredTriggers); if (hasMoreMisfiredTriggers) { getLog().info("Handling the first {} triggers that missed their scheduled fire-time. More misfired triggers remain to be processed.", misfiredTriggers.size()); } else if (!misfiredTriggers.isEmpty()) { getLog().info("Handling {} trigger(s) that missed their scheduled fire-time.", misfiredTriggers.size()); } else { getLog().debug( "Found 0 triggers that missed their scheduled fire-time."); return RecoverMisfiredJobsResult.NO_OP; } for (TriggerKey triggerKey: misfiredTriggers) { OperableTrigger trig; try { trig = retrieveTrigger(conn, triggerKey); } catch (Exception e) { getLog().error("Error retrieving the misfired trigger: {}", triggerKey, e); continue; } if (trig == null) { continue; } try { doUpdateOfMisfiredTrigger(conn, trig, false, STATE_WAITING, recovering); } catch (Exception e) { getLog().error("Error updating misfired trigger: {}", trig.getKey(), e); continue; } if(trig.getNextFireTime() != null && trig.getNextFireTime().getTime() < earliestNewTime) earliestNewTime = trig.getNextFireTime().getTime(); } return new RecoverMisfiredJobsResult( hasMoreMisfiredTriggers, misfiredTriggers.size(), earliestNewTime); } protected boolean updateMisfiredTrigger(Connection conn, TriggerKey triggerKey, String newStateIfNotComplete, boolean forceState) throws JobPersistenceException { try { OperableTrigger trig = retrieveTrigger(conn, triggerKey); long misfireTime = System.currentTimeMillis(); if (getMisfireThreshold() > 0) { misfireTime -= getMisfireThreshold(); } if (trig.getNextFireTime().getTime() > misfireTime) { return false; } doUpdateOfMisfiredTrigger(conn, trig, forceState, newStateIfNotComplete, false); return true; } catch (Exception e) { throw new JobPersistenceException( "Couldn't update misfired trigger '" + triggerKey + "': " + e.getMessage(), e); } } private void doUpdateOfMisfiredTrigger(Connection conn, OperableTrigger trig, boolean forceState, String newStateIfNotComplete, boolean recovering) throws JobPersistenceException { Calendar cal = null; if (trig.getCalendarName() != null) { cal = retrieveCalendar(conn, trig.getCalendarName()); } schedSignaler.notifyTriggerListenersMisfired(trig); trig.updateAfterMisfire(cal); if (trig.getNextFireTime() == null) { storeTrigger(conn, trig, null, true, STATE_COMPLETE, forceState, recovering); schedSignaler.notifySchedulerListenersFinalized(trig); } else { storeTrigger(conn, trig, null, true, newStateIfNotComplete, forceState, recovering); } } /** *

* Store the given {@link org.quartz.JobDetail} and {@link 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 JobDetail newJob, final OperableTrigger newTrigger) throws JobPersistenceException { executeInLock( (isLockOnInsert()) ? LOCK_TRIGGER_ACCESS : null, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { storeJob(conn, newJob, false); storeTrigger(conn, newTrigger, newJob, false, Constants.STATE_WAITING, false, false); } }); } /** *

* Store the given {@link org.quartz.JobDetail}. *

* * @param newJob * The JobDetail to be stored. * @param replaceExisting * If true, any Job existing in the * JobStore with the same name and 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 JobDetail newJob, final boolean replaceExisting) throws JobPersistenceException { executeInLock( (isLockOnInsert() || replaceExisting) ? LOCK_TRIGGER_ACCESS : null, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { storeJob(conn, newJob, replaceExisting); } }); } /** *

* Insert or update a job. *

*/ protected void storeJob(Connection conn, JobDetail newJob, boolean replaceExisting) throws JobPersistenceException { boolean existingJob = jobExists(conn, newJob.getKey()); try { if (existingJob) { if (!replaceExisting) { throw new ObjectAlreadyExistsException(newJob); } getDelegate().updateJobDetail(conn, newJob); } else if (getDelegate().insertJobDetail(conn, newJob) < 1) { throw new JobPersistenceException("Couldn't store job. Insert failed."); } } catch (IOException | SQLException e) { throw new JobPersistenceException("Couldn't store job: " + e.getMessage(), e); } } /** *

* Check existence of a given job. *

*/ protected boolean jobExists(Connection conn, JobKey jobKey) throws JobPersistenceException { try { return getDelegate().jobExists(conn, jobKey); } catch (SQLException e) { throw new JobPersistenceException( "Couldn't determine job existence (" + jobKey + "): " + e.getMessage(), e); } } /** *

* Store the given {@link org.quartz.Trigger}. *

* * @param newTrigger * The Trigger to be stored. * @param replaceExisting * If true, any Trigger existing in * the JobStore with the same name and 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 OperableTrigger newTrigger, final boolean replaceExisting) throws JobPersistenceException { executeInLock( (isLockOnInsert() || replaceExisting) ? LOCK_TRIGGER_ACCESS : null, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { storeTrigger(conn, newTrigger, null, replaceExisting, STATE_WAITING, false, false); } }); } /** *

* Insert or update a trigger. *

*/ @SuppressWarnings("ConstantConditions") protected void storeTrigger(Connection conn, OperableTrigger newTrigger, JobDetail job, boolean replaceExisting, String state, boolean forceState, boolean recovering) throws JobPersistenceException { boolean existingTrigger = triggerExists(conn, newTrigger.getKey()); if ((existingTrigger) && (!replaceExisting)) { throw new ObjectAlreadyExistsException(newTrigger); } try { boolean shouldBePaused; if (!forceState) { shouldBePaused = getDelegate().isTriggerGroupPaused( conn, newTrigger.getKey().getGroup()); if(!shouldBePaused) { shouldBePaused = getDelegate().isTriggerGroupPaused(conn, ALL_GROUPS_PAUSED); if (shouldBePaused) { getDelegate().insertPausedTriggerGroup(conn, newTrigger.getKey().getGroup()); } } if (shouldBePaused && (state.equals(STATE_WAITING) || state.equals(STATE_ACQUIRED))) { state = STATE_PAUSED; } } if(job == null) { job = retrieveJob(conn, newTrigger.getJobKey()); } if (job == null) { throw new JobPersistenceException("The job (" + newTrigger.getJobKey() + ") referenced by the trigger does not exist."); } if (job.isConcurrentExecutionDisallowed() && !recovering) { state = checkBlockedState(conn, job.getKey(), state); } if (existingTrigger) { getDelegate().updateTrigger(conn, newTrigger, state, job); } else { getDelegate().insertTrigger(conn, newTrigger, state, job); } } catch (Exception e) { throw new JobPersistenceException("Couldn't store trigger '" + newTrigger.getKey() + "' for '" + newTrigger.getJobKey() + "' job:" + e.getMessage(), e); } } /** *

* Check existence of a given trigger. *

*/ protected boolean triggerExists(Connection conn, TriggerKey key) throws JobPersistenceException { try { return getDelegate().triggerExists(conn, key); } catch (SQLException e) { throw new JobPersistenceException( "Couldn't determine trigger existence (" + key + "): " + e.getMessage(), e); } } /** *

* Remove (delete) the {@link org.quartz.Job} with the given * name, and any {@link 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. *

* * @return true if a Job with the given name and * group was found and removed from the store. */ public boolean removeJob(final JobKey jobKey) throws JobPersistenceException { return (Boolean) executeInLock( LOCK_TRIGGER_ACCESS, new TransactionCallback() { public Object execute(Connection conn) throws JobPersistenceException { return removeJob(conn, jobKey) ? Boolean.TRUE : Boolean.FALSE; } }); } protected boolean removeJob(Connection conn, final JobKey jobKey) throws JobPersistenceException { try { List jobTriggers = getDelegate().selectTriggerKeysForJob(conn, jobKey); for (TriggerKey jobTrigger: jobTriggers) { deleteTriggerAndChildren(conn, jobTrigger); } return deleteJobAndChildren(conn, jobKey); } catch (SQLException e) { throw new JobPersistenceException("Couldn't remove job: " + e.getMessage(), e); } } public boolean removeJobs(final List jobKeys) throws JobPersistenceException { return (Boolean) executeInLock( LOCK_TRIGGER_ACCESS, new TransactionCallback() { public Object execute(Connection conn) throws JobPersistenceException { boolean allFound = true; // FUTURE_TODO: make this more efficient with a true bulk operation... for (JobKey jobKey : jobKeys) allFound = removeJob(conn, jobKey) && allFound; return allFound ? Boolean.TRUE : Boolean.FALSE; } }); } public boolean removeTriggers(final List triggerKeys) throws JobPersistenceException { return executeInLock( LOCK_TRIGGER_ACCESS, conn -> { boolean allFound = true; // FUTURE_TODO: make this more efficient with a true bulk operation... for (TriggerKey triggerKey : triggerKeys) allFound &= removeTrigger(conn, triggerKey); return allFound; }); } public void storeJobsAndTriggers( final Map> triggersAndJobs, final boolean replace) throws JobPersistenceException { executeInLock( (isLockOnInsert() || replace) ? LOCK_TRIGGER_ACCESS : null, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { // FUTURE_TODO: make this more efficient with a true bulk operation... for(JobDetail job: triggersAndJobs.keySet()) { storeJob(conn, job, replace); for(Trigger trigger: triggersAndJobs.get(job)) { storeTrigger(conn, (OperableTrigger) trigger, job, replace, Constants.STATE_WAITING, false, false); } } } }); } /** * Delete a job and its listeners. * * @see #removeJob(java.sql.Connection, org.quartz.JobKey) * @see #removeTrigger(Connection, TriggerKey) */ private boolean deleteJobAndChildren(Connection conn, JobKey key) throws NoSuchDelegateException, SQLException { return (getDelegate().deleteJobDetail(conn, key) > 0); } /** * Delete a trigger, its listeners, and its Simple/Cron/BLOB sub-table entry. * * @see #removeJob(java.sql.Connection, org.quartz.JobKey) * @see #removeTrigger(Connection, TriggerKey) * @see #replaceTrigger(Connection, TriggerKey, OperableTrigger) */ private boolean deleteTriggerAndChildren(Connection conn, TriggerKey key) throws SQLException, NoSuchDelegateException { return (getDelegate().deleteTrigger(conn, key) > 0); } /** *

* Retrieve the {@link org.quartz.JobDetail} for the given * {@link org.quartz.Job}. *

* * @return The desired Job, or null if there is no match. */ public JobDetail retrieveJob(final JobKey jobKey) throws JobPersistenceException { return (JobDetail)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> retrieveJob(conn, jobKey)); } public List getJobDetails(GroupMatcher matcher) throws JobPersistenceException { return (List) executeWithoutLock( (TransactionCallback) conn -> retrieveJobs(conn, matcher)); } protected List retrieveJobs(Connection conn, GroupMatcher matcher) throws JobPersistenceException { try { return getDelegate().selectJobDetails(conn, matcher, getClassLoadHelper()); } catch (ClassNotFoundException e) { throw new JobPersistenceException( "Couldn't retrieve job because a required class was not found: " + e.getMessage(), e); } catch (IOException e) { throw new JobPersistenceException( "Couldn't retrieve job because the BLOB couldn't be deserialized: " + e.getMessage(), e); } catch (SQLException e) { throw new JobPersistenceException("Couldn't retrieve job: " + e.getMessage(), e); } } protected JobDetail retrieveJob(Connection conn, JobKey key) throws JobPersistenceException { try { return getDelegate().selectJobDetail(conn, key, getClassLoadHelper()); } catch (ClassNotFoundException e) { throw new JobPersistenceException( "Couldn't retrieve job because a required class was not found: " + e.getMessage(), e); } catch (IOException e) { throw new JobPersistenceException( "Couldn't retrieve job because the BLOB couldn't be deserialized: " + e.getMessage(), e); } catch (SQLException e) { throw new JobPersistenceException("Couldn't retrieve job: " + e.getMessage(), e); } } /** *

* Remove (delete) the {@link 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. *

* * @return true if a Trigger with the given * name and group was found and removed from the store. */ public boolean removeTrigger(final TriggerKey triggerKey) throws JobPersistenceException { return (Boolean) executeInLock( LOCK_TRIGGER_ACCESS, new TransactionCallback() { public Object execute(Connection conn) throws JobPersistenceException { return removeTrigger(conn, triggerKey) ? Boolean.TRUE : Boolean.FALSE; } }); } protected boolean removeTrigger(Connection conn, TriggerKey key) throws JobPersistenceException { boolean removedTrigger; try { // this must be called before we delete the trigger, obviously JobDetail job = getDelegate().selectJobForTrigger(conn, getClassLoadHelper(), key, false); removedTrigger = deleteTriggerAndChildren(conn, key); if (null != job && !job.isDurable()) { int numTriggers = getDelegate().selectNumTriggersForJob(conn, job.getKey()); if (numTriggers == 0) { // Don't call removeJob() because we don't want to check for // triggers again. deleteJobAndChildren(conn, job.getKey()); } } } catch (ClassNotFoundException | SQLException | IOException e) { throw new JobPersistenceException("Couldn't remove trigger: " + e.getMessage(), e); } return removedTrigger; } /** * @see org.quartz.spi.JobStore#replaceTrigger(TriggerKey, OperableTrigger) */ public boolean replaceTrigger(final TriggerKey triggerKey, final OperableTrigger newTrigger) throws JobPersistenceException { return (Boolean) executeInLock( LOCK_TRIGGER_ACCESS, new TransactionCallback() { public Object execute(Connection conn) throws JobPersistenceException { return replaceTrigger(conn, triggerKey, newTrigger) ? Boolean.TRUE : Boolean.FALSE; } }); } protected boolean replaceTrigger(Connection conn, TriggerKey key, OperableTrigger newTrigger) throws JobPersistenceException { try { // this must be called before we delete the trigger, obviously JobDetail job = getDelegate().selectJobForTrigger(conn, getClassLoadHelper(), key); if (job == null) { return false; } if (!newTrigger.getJobKey().equals(job.getKey())) { throw new JobPersistenceException("New trigger is not related to the same job as the old trigger."); } boolean removedTrigger = deleteTriggerAndChildren(conn, key); storeTrigger(conn, newTrigger, job, false, STATE_WAITING, false, false); return removedTrigger; } catch (ClassNotFoundException | SQLException | IOException e) { throw new JobPersistenceException("Couldn't remove trigger: " + e.getMessage(), e); } } /** *

* Retrieve the given {@link org.quartz.Trigger}. *

* * @return The desired Trigger, or null if there is no * match. */ public OperableTrigger retrieveTrigger(final TriggerKey triggerKey) throws JobPersistenceException { return (OperableTrigger)executeWithoutLock( // no locks necessary for read... new TransactionCallback() { public Object execute(Connection conn) throws JobPersistenceException { return retrieveTrigger(conn, triggerKey); } }); } protected OperableTrigger retrieveTrigger(Connection conn, TriggerKey key) throws JobPersistenceException { try { return getDelegate().selectTrigger(conn, key); } catch (Exception e) { throw new JobPersistenceException("Couldn't retrieve trigger: " + e.getMessage(), e); } } /** *

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

* * @see TriggerState#NORMAL * @see TriggerState#PAUSED * @see TriggerState#COMPLETE * @see TriggerState#ERROR * @see TriggerState#NONE */ public TriggerState getTriggerState(final TriggerKey triggerKey) throws JobPersistenceException { return (TriggerState)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> getTriggerState(conn, triggerKey)); } public TriggerState getTriggerState(Connection conn, TriggerKey key) throws JobPersistenceException { try { String ts = getDelegate().selectTriggerState(conn, key); if (ts == null) { return TriggerState.NONE; } switch (ts) { case STATE_DELETED: return TriggerState.NONE; case STATE_COMPLETE: return TriggerState.COMPLETE; case STATE_PAUSED: return TriggerState.PAUSED; case STATE_PAUSED_BLOCKED: return TriggerState.PAUSED; case STATE_ERROR: return TriggerState.ERROR; case STATE_BLOCKED: return TriggerState.BLOCKED; } return TriggerState.NORMAL; } catch (SQLException e) { throw new JobPersistenceException( "Couldn't determine state of trigger (" + key + "): " + e.getMessage(), e); } } /** * Reset the current state of the identified {@link Trigger} * from {@link TriggerState#ERROR} to {@link TriggerState#NORMAL} or * {@link TriggerState#PAUSED} as appropriate. * *

Only affects triggers that are in ERROR state - if identified trigger is not * in that state then the result is a no-op.

* *

The result will be the trigger returning to the normal, waiting to * be fired state, unless the trigger's group has been paused, in which * case it will go into the PAUSED state.

*/ public void resetTriggerFromErrorState(final TriggerKey triggerKey) throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { resetTriggerFromErrorState(conn, triggerKey); } }); } void resetTriggerFromErrorState(Connection conn, final TriggerKey triggerKey) throws JobPersistenceException { try { String newState = STATE_WAITING; if(getDelegate().isTriggerGroupPaused(conn, triggerKey.getGroup())) { newState = STATE_PAUSED; } getDelegate().updateTriggerStateFromOtherState(conn, triggerKey, newState, STATE_ERROR); getLog().info("Trigger {} reset from ERROR state to: {}", triggerKey, newState); } catch (SQLException e) { throw new JobPersistenceException( "Couldn't reset from error state of trigger (" + triggerKey + "): " + e.getMessage(), e); } } /** *

* Store the given {@link 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 and 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 String calName, final Calendar calendar, final boolean replaceExisting, final boolean updateTriggers) throws JobPersistenceException { executeInLock( (isLockOnInsert() || updateTriggers) ? LOCK_TRIGGER_ACCESS : null, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { storeCalendar(conn, calName, calendar, replaceExisting, updateTriggers); } }); } protected void storeCalendar(Connection conn, String calName, Calendar calendar, boolean replaceExisting, boolean updateTriggers) throws 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) { // FUTURE_TODO: make this more efficient with a true bulk operation... List trigs; trigs = getDelegate().selectTriggersForCalendar(conn, calName); for(OperableTrigger trigger: trigs) { trigger.updateWithNewCalendar(calendar, getMisfireThreshold()); storeTrigger(conn, trigger, 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) { 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 | 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 org.quartz.Calendar} with the * given name. *

* *

* If removal of the Calendar would result in * Triggers pointing to nonexistent 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 String calName) throws JobPersistenceException { return (Boolean) executeInLock( LOCK_TRIGGER_ACCESS, new TransactionCallback() { public Object execute(Connection conn) throws JobPersistenceException { return removeCalendar(conn, calName) ? Boolean.TRUE : Boolean.FALSE; } }); } protected boolean removeCalendar(Connection conn, 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) { 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 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 String calName) throws JobPersistenceException { return (Calendar)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> retrieveCalendar(conn, calName)); } protected Calendar retrieveCalendar(Connection conn, 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 : calendarCache.get(calName); if (cal != null) { return cal; } try { cal = getDelegate().selectCalendar(conn, calName); if (!isClustered) { 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 org.quartz.Job} s that are * stored in the JobStore. *

*/ public int getNumberOfJobs() throws JobPersistenceException { return (Integer) executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getNumberOfJobs); } protected int getNumberOfJobs(Connection conn) 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 org.quartz.Trigger} s that are * stored in the JobsStore. *

*/ public int getNumberOfTriggers() throws JobPersistenceException { return (Integer) executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getNumberOfTriggers); } protected int getNumberOfTriggers(Connection conn) 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 org.quartz.Calendar} s that are * stored in the JobsStore. *

*/ public int getNumberOfCalendars() throws JobPersistenceException { return (Integer) executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getNumberOfCalendars); } protected int getNumberOfCalendars(Connection conn) 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 org.quartz.Job} s that * matcher the given groupMatcher. *

* *

* If there are no jobs in the given group name, the result should be an empty Set *

*/ @SuppressWarnings("unchecked") public Set getJobKeys(final GroupMatcher matcher) throws JobPersistenceException { return (Set)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> getJobNames(conn, matcher)); } protected Set getJobNames(Connection conn, GroupMatcher matcher) throws JobPersistenceException { Set jobNames; try { jobNames = getDelegate().selectJobsInGroup(conn, matcher); } catch (SQLException e) { throw new JobPersistenceException("Couldn't obtain job names: " + e.getMessage(), e); } return jobNames; } /** * Determine whether a {@link Job} with the given identifier already * exists within the scheduler. * * @param jobKey the identifier to check for * @return true if a Job exists with the given identifier * @throws JobPersistenceException */ public boolean checkExists(final JobKey jobKey) throws JobPersistenceException { return (Boolean)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> checkExists(conn, jobKey)); } protected boolean checkExists(Connection conn, JobKey jobKey) throws JobPersistenceException { try { return getDelegate().jobExists(conn, jobKey); } catch (SQLException e) { throw new JobPersistenceException("Couldn't check for existence of job: " + e.getMessage(), e); } } /** * Determine whether a {@link Trigger} with the given identifier already * exists within the scheduler. * * @param triggerKey the identifier to check for * @return true if a Trigger exists with the given identifier * @throws JobPersistenceException */ public boolean checkExists(final TriggerKey triggerKey) throws JobPersistenceException { return (Boolean)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> checkExists(conn, triggerKey)); } protected boolean checkExists(Connection conn, TriggerKey triggerKey) throws JobPersistenceException { try { return getDelegate().triggerExists(conn, triggerKey); } catch (SQLException e) { throw new JobPersistenceException("Couldn't check for existence of job: " + e.getMessage(), e); } } /** * Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws JobPersistenceException */ public void clearAllSchedulingData() throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { clearAllSchedulingData(conn); } }); } protected void clearAllSchedulingData(Connection conn) throws JobPersistenceException { try { getDelegate().clearData(conn); } catch (SQLException e) { throw new JobPersistenceException("Error clearing scheduling data: " + e.getMessage(), e); } } /** *

* Get the names of all of the {@link org.quartz.Trigger} s * that match the given group Matcher. *

* *

* If there are no triggers in the given group name, the result should be a * an empty Set (not null). *

*/ @SuppressWarnings("unchecked") public Set getTriggerKeys(final GroupMatcher matcher) throws JobPersistenceException { return (Set)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> getTriggerNames(conn, matcher)); } protected Set getTriggerNames(Connection conn, GroupMatcher matcher) throws JobPersistenceException { Set trigNames; try { trigNames = getDelegate().selectTriggersInGroup(conn, matcher); } catch (SQLException e) { throw new JobPersistenceException("Couldn't obtain trigger names: " + e.getMessage(), e); } return trigNames; } /** *

* Get the names of all of the {@link org.quartz.Job} * groups. *

* *

* If there are no known group names, the result should be a zero-length * array (not null). *

*/ @SuppressWarnings("unchecked") public List getJobGroupNames() throws JobPersistenceException { return (List)executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getJobGroupNames); } protected List getJobGroupNames(Connection conn) throws JobPersistenceException { List groupNames; 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 org.quartz.Trigger} * groups. *

* *

* If there are no known group names, the result should be a zero-length * array (not null). *

*/ @SuppressWarnings("unchecked") public List getTriggerGroupNames() throws JobPersistenceException { return (List)executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getTriggerGroupNames); } protected List getTriggerGroupNames(Connection conn) throws JobPersistenceException { List groupNames; 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 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). *

*/ @SuppressWarnings("unchecked") public List getCalendarNames() throws JobPersistenceException { return (List)executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getCalendarNames); } protected List getCalendarNames(Connection conn) 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. *

*/ @SuppressWarnings("unchecked") public List getTriggersForJob(final JobKey jobKey) throws JobPersistenceException { return (List)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> getTriggersForJob(conn, jobKey)); } protected List getTriggersForJob(Connection conn, JobKey key) throws JobPersistenceException { List list; try { list = getDelegate() .selectTriggersForJob(conn, key); } catch (Exception e) { throw new JobPersistenceException( "Couldn't obtain triggers for job: " + e.getMessage(), e); } return list; } public List getTriggersByJobAndTriggerGroup(GroupMatcher jobMatcher, GroupMatcher triggerMatcher) throws JobPersistenceException { return (List)executeWithoutLock( // no locks necessary for read... (TransactionCallback) conn -> getTriggersByJobAndTriggerGroup(conn, jobMatcher, triggerMatcher)); } protected List getTriggersByJobAndTriggerGroup(Connection conn, GroupMatcher jobMatcher, GroupMatcher triggerMatcher) throws JobPersistenceException { GroupMatcher jMatcher = jobMatcher == null ? GroupMatcher.anyGroup() : jobMatcher; GroupMatcher tMatcher = triggerMatcher == null ? GroupMatcher.anyGroup() : triggerMatcher; try { return getDelegate().getTriggersByJobAndTriggerGroup(conn, jMatcher, tMatcher); } catch (JobPersistenceException jpe) { throw jpe; } catch (Exception e) { throw new JobPersistenceException("Couldn't obtain triggers for job: " + e.getMessage(), e); } } /** *

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

* * @see #resumeTrigger(TriggerKey) */ public void pauseTrigger(final TriggerKey triggerKey) throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { pauseTrigger(conn, triggerKey); } }); } /** *

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

* * @see #resumeTrigger(Connection, TriggerKey) */ public void pauseTrigger(Connection conn, TriggerKey triggerKey) throws JobPersistenceException { try { String oldState = getDelegate().selectTriggerState(conn, triggerKey); if (oldState.equals(STATE_WAITING) || oldState.equals(STATE_ACQUIRED)) { getDelegate().updateTriggerState(conn, triggerKey, STATE_PAUSED); } else if (oldState.equals(STATE_BLOCKED)) { getDelegate().updateTriggerState(conn, triggerKey, STATE_PAUSED_BLOCKED); } } catch (SQLException e) { throw new JobPersistenceException("Couldn't pause trigger '" + triggerKey + "': " + e.getMessage(), e); } } /** *

* Pause the {@link org.quartz.Job} with the given name - by * pausing all of its current Triggers. *

* * @see #resumeJob(JobKey) */ public void pauseJob(final JobKey jobKey) throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { List triggers = getTriggersForJob(conn, jobKey); for (OperableTrigger trigger: triggers) { pauseTrigger(conn, trigger.getKey()); } } }); } /** *

* Pause all of the {@link org.quartz.Job}s matching the given * groupMatcher - by pausing all of their Triggers. *

* * @see #resumeJobs(org.quartz.impl.matchers.GroupMatcher) */ @SuppressWarnings("unchecked") public Set pauseJobs(final GroupMatcher matcher) throws JobPersistenceException { return (Set) executeInLock( LOCK_TRIGGER_ACCESS, (TransactionCallback) conn -> { Set groupNames = new HashSet<>(); Set jobNames = getJobNames(conn, matcher); for (JobKey jobKey : jobNames) { List triggers = getTriggersForJob(conn, jobKey); for (OperableTrigger trigger : triggers) { pauseTrigger(conn, trigger.getKey()); } groupNames.add(jobKey.getGroup()); } return groupNames; } ); } /** * Determines if a Trigger for the given job should be blocked. * State can only transition to STATE_PAUSED_BLOCKED/BLOCKED from * PAUSED/STATE_WAITING respectively. * * @return STATE_PAUSED_BLOCKED, BLOCKED, or the currentState. */ protected String checkBlockedState( Connection conn, JobKey jobKey, String currentState) throws JobPersistenceException { // State can only transition to BLOCKED from PAUSED or WAITING. if ((!currentState.equals(STATE_WAITING)) && (!currentState.equals(STATE_PAUSED))) { return currentState; } try { List lst = getDelegate().selectFiredTriggerRecordsByJob(conn, jobKey.getName(), jobKey.getGroup()); if (!lst.isEmpty()) { FiredTriggerRecord rec = lst.get(0); if (rec.isJobDisallowsConcurrentExecution()) { // OLD_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 '" + jobKey + "': " + e.getMessage(), e); } } /** *

* Resume (un-pause) the {@link 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(TriggerKey) */ public void resumeTrigger(final TriggerKey triggerKey) throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { resumeTrigger(conn, triggerKey); } }); } /** *

* Resume (un-pause) the {@link 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, TriggerKey) */ public void resumeTrigger(Connection conn, TriggerKey key) throws JobPersistenceException { try { TriggerStatus status = getDelegate().selectTriggerStatus(conn, key); if (status == null || status.getNextFireTime() == null) { return; } boolean blocked = STATE_PAUSED_BLOCKED.equals(status.getStatus()); String newState = checkBlockedState(conn, status.getJobKey(), STATE_WAITING); boolean misfired = false; if (schedulerRunning && status.getNextFireTime().before(new Date())) { misfired = updateMisfiredTrigger(conn, key, newState, true); } if(!misfired) { if(blocked) { getDelegate().updateTriggerStateFromOtherState(conn, key, newState, STATE_PAUSED_BLOCKED); } else { getDelegate().updateTriggerStateFromOtherState(conn, key, newState, STATE_PAUSED); } } } catch (SQLException e) { throw new JobPersistenceException("Couldn't resume trigger '" + key + "': " + e.getMessage(), e); } } /** *

* Resume (un-pause) the {@link 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(JobKey) */ public void resumeJob(final JobKey jobKey) throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { List triggers = getTriggersForJob(conn, jobKey); for (OperableTrigger trigger: triggers) { resumeTrigger(conn, trigger.getKey()); } } }); } /** *

* Resume (un-pause) all of the {@link 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 #pauseJobs(org.quartz.impl.matchers.GroupMatcher) */ @SuppressWarnings("unchecked") public Set resumeJobs(final GroupMatcher matcher) throws JobPersistenceException { return (Set) executeInLock( LOCK_TRIGGER_ACCESS, (TransactionCallback) conn -> { Set jobKeys = getJobNames(conn, matcher); Set groupNames = new HashSet<>(); for (JobKey jobKey: jobKeys) { List triggers = getTriggersForJob(conn, jobKey); for (OperableTrigger trigger: triggers) { resumeTrigger(conn, trigger.getKey()); } groupNames.add(jobKey.getGroup()); } return groupNames; }); } /** *

* Pause all of the {@link org.quartz.Trigger}s matching the * given groupMatcher. *

* * @see #resumeTriggerGroup(java.sql.Connection, org.quartz.impl.matchers.GroupMatcher) */ @SuppressWarnings("unchecked") public Set pauseTriggers(final GroupMatcher matcher) throws JobPersistenceException { return (Set) executeInLock( LOCK_TRIGGER_ACCESS, (TransactionCallback) conn -> pauseTriggerGroup(conn, matcher)); } /** *

* Pause all of the {@link org.quartz.Trigger}s matching the * given groupMatcher. *

* * @see #resumeTriggerGroup(java.sql.Connection, org.quartz.impl.matchers.GroupMatcher) */ public Set pauseTriggerGroup(Connection conn, GroupMatcher matcher) throws JobPersistenceException { try { getDelegate().updateTriggerGroupStateFromOtherStates( conn, matcher, STATE_PAUSED, STATE_ACQUIRED, STATE_WAITING, STATE_WAITING); getDelegate().updateTriggerGroupStateFromOtherState( conn, matcher, STATE_PAUSED_BLOCKED, STATE_BLOCKED); List groups = getDelegate().selectTriggerGroups(conn, matcher); // make sure to account for an exact group match for a group that doesn't yet exist StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); if (operator.equals(StringOperatorName.EQUALS) && !groups.contains(matcher.getCompareToValue())) { groups.add(matcher.getCompareToValue()); } for (String group : groups) { if (!getDelegate().isTriggerGroupPaused(conn, group)) { getDelegate().insertPausedTriggerGroup(conn, group); } } return new HashSet<>(groups); } catch (SQLException e) { throw new JobPersistenceException("Couldn't pause trigger group '" + matcher + "': " + e.getMessage(), e); } } @SuppressWarnings("unchecked") public Set getPausedTriggerGroups() throws JobPersistenceException { return (Set)executeWithoutLock( // no locks necessary for read... (TransactionCallback) this::getPausedTriggerGroups); } /** *

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

* * @see #resumeTriggers(org.quartz.impl.matchers.GroupMatcher) */ public Set getPausedTriggerGroups(Connection conn) 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 org.quartz.Trigger}s * matching the given groupMatcher. *

* *

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

* * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) */ @SuppressWarnings("unchecked") public Set resumeTriggers(final GroupMatcher matcher) throws JobPersistenceException { return (Set) executeInLock( LOCK_TRIGGER_ACCESS, (TransactionCallback) conn -> resumeTriggerGroup(conn, matcher)); } /** *

* Resume (un-pause) all of the {@link org.quartz.Trigger}s * matching the given groupMatcher. *

* *

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

* * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) */ public Set resumeTriggerGroup(Connection conn, GroupMatcher matcher) throws JobPersistenceException { try { getDelegate().deletePausedTriggerGroup(conn, matcher); HashSet groups = new HashSet<>(); Set keys = getDelegate().selectTriggersInGroup(conn, matcher); for (TriggerKey key: keys) { resumeTrigger(conn, key); groups.add(key.getGroup()); } return groups; // FUTURE_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, 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, * groupName); * * Iterator itr = blockedTriggers.iterator(); while(itr.hasNext()) { * Key key = (Key)itr.next(); * getDelegate().updateTriggerState(conn, key.getName(), * key.getGroup(), BLOCKED); } * * for(int i=0; i < misfires.length; i++) { String * newState = STATE_WAITING; * if(blockedTriggers.contains(misfires[i])) newState = * BLOCKED; updateMisfiredTrigger(conn, * misfires[i].getName(), misfires[i].getGroup(), newState, true); } } */ } catch (SQLException e) { throw new JobPersistenceException("Couldn't pause trigger group '" + matcher + "': " + 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() * @see #pauseTriggerGroup(java.sql.Connection, org.quartz.impl.matchers.GroupMatcher) */ public void pauseAll() throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { pauseAll(conn); } }); } /** *

* 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(Connection) * @see #pauseTriggerGroup(java.sql.Connection, org.quartz.impl.matchers.GroupMatcher) */ public void pauseAll(Connection conn) throws JobPersistenceException { List names = getTriggerGroupNames(conn); for (String name: names) { pauseTriggerGroup(conn, GroupMatcher.triggerGroupEquals(name)); } 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() */ public void resumeAll() throws JobPersistenceException { executeInLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { resumeAll(conn); } }); } /** * 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(Connection) */ public void resumeAll(Connection conn) throws JobPersistenceException { List names = getTriggerGroupNames(conn); for (String name: names) { resumeTriggerGroup(conn, GroupMatcher.triggerGroupEquals(name)); } 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(OperableTrigger) */ @SuppressWarnings("unchecked") public List acquireNextTriggers(final long noLaterThan, final int maxCount, final long timeWindow) throws JobPersistenceException { String lockName; if(isAcquireTriggersWithinLock() || maxCount > 1) { lockName = LOCK_TRIGGER_ACCESS; } else { lockName = null; } return executeInNonManagedTXLock(lockName, conn -> acquireNextTrigger(conn, noLaterThan, maxCount, timeWindow), (conn, result) -> { try { List acquired = getDelegate().selectInstancesFiredTriggerRecords(conn, getInstanceId()); Set fireInstanceIds = new HashSet<>(); for (FiredTriggerRecord ft : acquired) { fireInstanceIds.add(ft.getFireInstanceId()); } for (OperableTrigger tr : result) { if (fireInstanceIds.contains(tr.getFireInstanceId())) { return true; } } return false; } catch (SQLException e) { throw new JobPersistenceException("error validating trigger acquisition", e); } }); } // FUTURE_TODO: this really ought to return something like a FiredTriggerBundle, // so that the fireInstanceId doesn't have to be on the trigger... protected List acquireNextTrigger(Connection conn, long noLaterThan, int maxCount, long timeWindow) throws JobPersistenceException { if (timeWindow < 0) { throw new IllegalArgumentException(); } List acquiredTriggers = new ArrayList<>(); Set acquiredJobKeysForNoConcurrentExec = new HashSet<>(); final int MAX_DO_LOOP_RETRY = 3; int currentLoopCount = 0; do { currentLoopCount ++; try { List keys = getDelegate().selectTriggerToAcquire(conn, noLaterThan + timeWindow, getMisfireTime(), maxCount); // No trigger is ready to fire yet. if (keys == null || keys.isEmpty()) return acquiredTriggers; long batchEnd = noLaterThan; for(TriggerKey triggerKey: keys) { // If our trigger is no longer available, try a new one. OperableTrigger nextTrigger = retrieveTrigger(conn, triggerKey); if(nextTrigger == null) { continue; // next trigger } // If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then // put it back into the timeTriggers set and continue to search for next trigger. JobKey jobKey = nextTrigger.getJobKey(); JobDetail job; try { job = retrieveJob(conn, jobKey); } catch (JobPersistenceException jpe) { try { getLog().error("Error retrieving job, setting trigger state to ERROR.", jpe); getDelegate().updateTriggerState(conn, triggerKey, STATE_ERROR); } catch (SQLException sqle) { getLog().error("Unable to set trigger state to ERROR.", sqle); } continue; } if (job.isConcurrentExecutionDisallowed()) { if (acquiredJobKeysForNoConcurrentExec.contains(jobKey)) { continue; // next trigger } else { acquiredJobKeysForNoConcurrentExec.add(jobKey); } } Date nextFireTime = nextTrigger.getNextFireTime(); // A trigger should not return NULL on nextFireTime when fetched from DB. // But for whatever reason if we do have this (BAD trigger implementation or // data?), we then should log a warning and continue to next trigger. // User would need to manually fix these triggers from DB as they will not // able to be clean up by Quartz since we are not returning it to be processed. if (nextFireTime == null) { log.warn("Trigger {} returned null on nextFireTime and yet still exists in DB!", nextTrigger.getKey()); continue; } if (nextFireTime.getTime() > batchEnd) { break; } // We now have a acquired trigger, let's add to return list. // If our trigger was no longer in the expected state, try a new one. int rowsUpdated = getDelegate().updateTriggerStateFromOtherState(conn, triggerKey, STATE_ACQUIRED, STATE_WAITING); if (rowsUpdated <= 0) { continue; // next trigger } nextTrigger.setFireInstanceId(getFiredTriggerRecordId()); getDelegate().insertFiredTrigger(conn, nextTrigger, STATE_ACQUIRED, null); if(acquiredTriggers.isEmpty()) { batchEnd = Math.max(nextFireTime.getTime(), System.currentTimeMillis()) + timeWindow; } acquiredTriggers.add(nextTrigger); } // if we didn't end up with any trigger to fire from that first // batch, try again for another batch. We allow with a max retry count. if(acquiredTriggers.isEmpty() && currentLoopCount < MAX_DO_LOOP_RETRY) { continue; } // We are done with the while loop. break; } catch (Exception e) { throw new JobPersistenceException( "Couldn't acquire next trigger: " + e.getMessage(), e); } } while (true); // Return the acquired trigger list return acquiredTriggers; } /** *

* Inform the JobStore that the scheduler no longer plans to * fire the given Trigger, that it had previously acquired * (reserved). *

*/ public void releaseAcquiredTrigger(final OperableTrigger trigger) { retryExecuteInNonManagedTXLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { releaseAcquiredTrigger(conn, trigger); } }); } protected void releaseAcquiredTrigger(Connection conn, OperableTrigger trigger) throws JobPersistenceException { try { getDelegate().updateTriggerStateFromOtherState(conn, trigger.getKey(), STATE_WAITING, STATE_ACQUIRED); getDelegate().updateTriggerStateFromOtherState(conn, trigger.getKey(), STATE_WAITING, STATE_BLOCKED); 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. */ @SuppressWarnings("unchecked") public List triggersFired(final List triggers) throws JobPersistenceException { return executeInNonManagedTXLock(LOCK_TRIGGER_ACCESS, conn -> { List results = new ArrayList<>(); TriggerFiredResult result; for (OperableTrigger trigger : triggers) { try { TriggerFiredBundle bundle = triggerFired(conn, trigger); result = new TriggerFiredResult(bundle); } catch (JobPersistenceException | RuntimeException jpe) { result = new TriggerFiredResult(jpe); } results.add(result); } return results; }, (conn, result) -> { try { List acquired = getDelegate().selectInstancesFiredTriggerRecords(conn, getInstanceId()); Set executingTriggers = new HashSet<>(); for (FiredTriggerRecord ft : acquired) { if (STATE_EXECUTING.equals(ft.getFireInstanceState())) { executingTriggers.add(ft.getFireInstanceId()); } } for (TriggerFiredResult tr : result) { if (tr.getTriggerFiredBundle() != null && executingTriggers.contains(tr.getTriggerFiredBundle().getTrigger().getFireInstanceId())) { return true; } } return false; } catch (SQLException e) { throw new JobPersistenceException("error validating trigger acquisition", e); } }); } protected TriggerFiredBundle triggerFired(Connection conn, OperableTrigger trigger) throws JobPersistenceException { JobDetail job; 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.getKey()); 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, trigger.getJobKey()); if (job == null) { return null; } } catch (JobPersistenceException jpe) { try { getLog().error("Error retrieving job, setting trigger state to ERROR.", jpe); getDelegate().updateTriggerState(conn, trigger.getKey(), STATE_ERROR); } catch (SQLException sqle) { getLog().error("Unable to set trigger state to ERROR.", sqle); } throw jpe; } if (trigger.getCalendarName() != null) { cal = retrieveCalendar(conn, trigger.getCalendarName()); if (cal == null) { return null; } } try { getDelegate().updateFiredTrigger(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.isConcurrentExecutionDisallowed()) { state = STATE_BLOCKED; force = false; try { getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(), STATE_BLOCKED, STATE_WAITING); getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(), STATE_BLOCKED, STATE_ACQUIRED); getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(), 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, trigger, job, true, state, force, false); job.getJobDataMap().clearDirtyFlag(); return new TriggerFiredBundle(job, trigger, cal, trigger.getKey().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 org.quartz.JobDataMap} * in the given JobDetail should be updated if the Job * is stateful. *

*/ public void triggeredJobComplete(final OperableTrigger trigger, final JobDetail jobDetail, final CompletedExecutionInstruction triggerInstCode) { retryExecuteInNonManagedTXLock( LOCK_TRIGGER_ACCESS, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { triggeredJobComplete(conn, trigger, jobDetail,triggerInstCode); } }); } protected void triggeredJobComplete(Connection conn, OperableTrigger trigger, JobDetail jobDetail, CompletedExecutionInstruction triggerInstCode) throws JobPersistenceException { try { if (triggerInstCode == CompletedExecutionInstruction.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.getKey()); if(stat != null && stat.getNextFireTime() == null) { removeTrigger(conn, trigger.getKey()); } } else{ removeTrigger(conn, trigger.getKey()); signalSchedulingChangeOnTxCompletion(0L); } } else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) { getDelegate().updateTriggerState(conn, trigger.getKey(), STATE_COMPLETE); signalSchedulingChangeOnTxCompletion(0L); } else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_ERROR) { getLog().info("Trigger {} set to ERROR state.", trigger.getKey()); getDelegate().updateTriggerState(conn, trigger.getKey(), STATE_ERROR); signalSchedulingChangeOnTxCompletion(0L); } else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) { getDelegate().updateTriggerStatesForJob(conn, trigger.getJobKey(), STATE_COMPLETE); signalSchedulingChangeOnTxCompletion(0L); } else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR) { getLog().info("All triggers of Job {} set to ERROR state.", trigger.getKey()); getDelegate().updateTriggerStatesForJob(conn, trigger.getJobKey(), STATE_ERROR); signalSchedulingChangeOnTxCompletion(0L); } if (jobDetail.isConcurrentExecutionDisallowed()) { getDelegate().updateTriggerStatesForJobFromOtherState(conn, jobDetail.getKey(), STATE_WAITING, STATE_BLOCKED); getDelegate().updateTriggerStatesForJobFromOtherState(conn, jobDetail.getKey(), STATE_PAUSED, STATE_PAUSED_BLOCKED); signalSchedulingChangeOnTxCompletion(0L); } if (jobDetail.isPersistJobDataAfterExecution()) { 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 { synchronized(this) { if(null == delegate) { try { if(delegateClassName != null) { delegateClass = getClassLoadHelper().loadClass(delegateClassName, DriverDelegate.class); } delegate = delegateClass.getDeclaredConstructor().newInstance(); delegate.initialize(getLog(), tablePrefix, instanceName, instanceId, getClassLoadHelper(), canUseProperties(), getDriverDelegateInitString()); delegate.setUseEnhancedStatements(this.useEnhancedStatements); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new NoSuchDelegateException("Couldn't create delegate: " + e.getMessage(), e); } catch (ClassNotFoundException e) { throw new NoSuchDelegateException("Couldn't load delegate class: " + e.getMessage(), e); } } 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().countMisfiredTriggersInState( conn, 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(LOCK_TRIGGER_ACCESS, transOwner); } finally { cleanupConnection(conn); } } } protected final ThreadLocal sigChangeForTxCompletion = new ThreadLocal<>(); protected void signalSchedulingChangeOnTxCompletion(long candidateNewNextFireTime) { Long sigTime = sigChangeForTxCompletion.get(); if(sigTime == null && candidateNewNextFireTime >= 0L) sigChangeForTxCompletion.set(candidateNewNextFireTime); else { if(sigTime == null || candidateNewNextFireTime < sigTime) sigChangeForTxCompletion.set(candidateNewNextFireTime); } } protected Long clearAndGetSignalSchedulingChangeOnTxCompletion() { Long t = sigChangeForTxCompletion.get(); sigChangeForTxCompletion.set(null); return t; } protected void signalSchedulingChangeImmediately(long candidateNewNextFireTime) { schedSignaler.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 acquire 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) { failedRecords = clusterCheckIn(conn); commitConnection(conn); } if (firstCheckIn || (!failedRecords.isEmpty())) { 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.isEmpty()) { 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(LOCK_TRIGGER_ACCESS, transOwner); } finally { try { releaseLock(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(SchedulerStateRecord rec: states) { // 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) && (!firstCheckIn)) { // FUTURE_TODO: revisit when handle self-failed-out impl'ed (see FUTURE_TODO in clusterCheckIn() below) getLog().warn("This scheduler instance ({}) is still active but was recovered by another instance in the cluster. This may cause inconsistent behavior.", getInstanceId()); } 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()) { for (SchedulerStateRecord rec: schedulerStateRecords) { allFiredTriggerInstanceNames.remove(rec.getSchedulerInstanceId()); } for (String inst: allFiredTriggerInstanceNames) { SchedulerStateRecord orphanedInstance = new SchedulerStateRecord(); orphanedInstance.setSchedulerInstanceId(inst); 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 { // FUTURE_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; } @SuppressWarnings("ConstantConditions") protected void clusterRecover(Connection conn, List failedInstances) throws JobPersistenceException { if (!failedInstances.isEmpty()) { long recoverIds = System.currentTimeMillis(); logWarnIfNonZero(failedInstances.size(), "ClusterManager: detected " + failedInstances.size() + " failed or restarted instances."); try { for (SchedulerStateRecord rec : failedInstances) { getLog().info("ClusterManager: Scanning for instance \"{}\"'s failed in-progress jobs.", rec.getSchedulerInstanceId()); List firedTriggerRecs = getDelegate() .selectInstancesFiredTriggerRecords(conn, rec.getSchedulerInstanceId()); int acquiredCount = 0; int recoveredCount = 0; int otherCount = 0; Set triggerKeys = new HashSet<>(); for (FiredTriggerRecord ftRec : firedTriggerRecs) { TriggerKey tKey = ftRec.getTriggerKey(); JobKey jKey = ftRec.getJobKey(); triggerKeys.add(tKey); // release blocked triggers.. if (ftRec.getFireInstanceState().equals(STATE_BLOCKED)) { getDelegate() .updateTriggerStatesForJobFromOtherState( conn, jKey, STATE_WAITING, STATE_BLOCKED); } else if (ftRec.getFireInstanceState().equals(STATE_PAUSED_BLOCKED)) { getDelegate() .updateTriggerStatesForJobFromOtherState( conn, jKey, STATE_PAUSED, STATE_PAUSED_BLOCKED); } // release acquired triggers.. if (ftRec.getFireInstanceState().equals(STATE_ACQUIRED)) { getDelegate().updateTriggerStateFromOtherState( conn, tKey, STATE_WAITING, STATE_ACQUIRED); acquiredCount++; } else if (ftRec.isJobRequestsRecovery()) { // handle jobs marked for recovery that were not fully // executed.. if (jobExists(conn, jKey)) { @SuppressWarnings("deprecation") SimpleTriggerImpl rcvryTrig = new SimpleTriggerImpl( "recover_" + rec.getSchedulerInstanceId() + "_" + recoverIds++, Scheduler.DEFAULT_RECOVERY_GROUP, new Date(ftRec.getScheduleTimestamp())); rcvryTrig.setJobName(jKey.getName()); rcvryTrig.setJobGroup(jKey.getGroup()); rcvryTrig.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); 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())); jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_SCHEDULED_FIRETIME_IN_MILLISECONDS, String.valueOf(ftRec.getScheduleTimestamp())); rcvryTrig.setJobDataMap(jd); rcvryTrig.computeFirstFireTime(null); storeTrigger(conn, rcvryTrig, null, false, STATE_WAITING, false, true); recoveredCount++; } else { getLog() .warn("ClusterManager: failed job '{}' no longer exists, cannot schedule recovery.", jKey); otherCount++; } } else { otherCount++; } // free up stateful job's triggers if (ftRec.isJobDisallowsConcurrentExecution()) { getDelegate() .updateTriggerStatesForJobFromOtherState( conn, jKey, STATE_WAITING, STATE_BLOCKED); getDelegate() .updateTriggerStatesForJobFromOtherState( conn, jKey, 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 (TriggerKey triggerKey : triggerKeys) { if (getDelegate().selectTriggerState(conn, triggerKey). equals(STATE_COMPLETE)) { List firedTriggers = getDelegate().selectFiredTriggerRecords(conn, triggerKey.getName(), triggerKey.getGroup()); if (firedTriggers.isEmpty()) { if (removeTrigger(conn, triggerKey)) { 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())) { getDelegate().deleteSchedulerState(conn, rec.getSchedulerInstanceId()); } } } catch (Throwable 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.restoreOriginalAttributes(); closeConnection(connHandler.getWrappedConnection()); return; } } // Wasn'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 propagate * 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); } } } /** * Returns true if enhanced statements for the database operations is enabled * @return true if using enhanced statements */ public boolean isUsingEnhancedStatements() { return this.useEnhancedStatements; } /** * Set to true to use enhanced bulk statements for the database operations * * @param useEnhancedStatements true to use enhanced statements */ public void setUseEnhancedStatements(boolean useEnhancedStatements) { this.useEnhancedStatements = useEnhancedStatements; if (delegate != null) { delegate.setUseEnhancedStatements(useEnhancedStatements); } } /** * 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, TransactionValidator) * @see JobStoreSupport#executeInLock(String, TransactionCallback) * @see JobStoreSupport#executeWithoutLock(TransactionCallback) */ protected interface TransactionCallback { T execute(Connection conn) throws JobPersistenceException; } protected interface TransactionValidator { Boolean validate(Connection conn, T result) 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, TransactionValidator) */ protected abstract class VoidTransactionCallback implements TransactionCallback { public final Void execute(Connection conn) throws JobPersistenceException { executeVoid(conn); return null; } abstract void executeVoid(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 T executeWithoutLock( TransactionCallback txCallback) throws JobPersistenceException { return executeInLock(null, txCallback); } /** * Execute the given callback having acquired 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 acquire, for example * "TRIGGER_ACCESS". If null, then no lock is acquired, but the * lockCallback is still executed in a transaction. */ protected abstract T executeInLock( String lockName, TransactionCallback txCallback) throws JobPersistenceException; protected T retryExecuteInNonManagedTXLock(String lockName, TransactionCallback txCallback) { for (int retry = 1; !shutdown; retry++) { try { return executeInNonManagedTXLock(lockName, txCallback, null); } catch (JobPersistenceException jpe) { if(retry % 4 == 0) { schedSignaler.notifySchedulerListenersError("An error occurred while " + txCallback, jpe); } } catch (RuntimeException e) { getLog().error("retryExecuteInNonManagedTXLock: RuntimeException {}", e.getMessage(), e); } try { Thread.sleep(getDbRetryInterval()); // retry every N seconds (the db connection must be failed) } catch (InterruptedException e) { throw new IllegalStateException("Received interrupted exception", e); } } throw new IllegalStateException("JobStore is shutdown - aborting retry"); } /** * Execute the given callback having optionally acquired the given lock. * This uses the non-managed transaction connection. * * @param lockName The name of the lock to acquire, for example * "TRIGGER_ACCESS". If null, then no lock is acquired, but the * lockCallback is still executed in a non-managed transaction. */ protected T executeInNonManagedTXLock( String lockName, TransactionCallback txCallback, final TransactionValidator txValidator) 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 acquiring the lock since it isn't needed. if (getLockHandler().requiresConnection()) { conn = getNonManagedTXConnection(); } transOwner = getLockHandler().obtainLock(conn, lockName); } if (conn == null) { conn = getNonManagedTXConnection(); } final T result = txCallback.execute(conn); try { commitConnection(conn); } catch (JobPersistenceException e) { rollbackConnection(conn); if (txValidator == null || !retryExecuteInNonManagedTXLock(lockName, conn1 -> txValidator.validate(conn1, result))) { throw e; } } Long sigTime = clearAndGetSignalSchedulingChangeOnTxCompletion(); if(sigTime != null && sigTime >= 0) { signalSchedulingChangeImmediately(sigTime); } 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(lockName, transOwner); } finally { cleanupConnection(conn); } } } ///////////////////////////////////////////////////////////////////////////// // // ClusterManager Thread // ///////////////////////////////////////////////////////////////////////////// class ClusterManager extends Thread { private volatile 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(); ThreadExecutor executor = getThreadExecutor(); executor.execute(ClusterManager.this); } 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; } @Override 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()) { signalSchedulingChangeImmediately(0L); } }//while !shutdown } } ///////////////////////////////////////////////////////////////////////////// // // MisfireHandler Thread // ///////////////////////////////////////////////////////////////////////////// class MisfireHandler extends Thread { private volatile boolean shutdown = false; private int numFails = 0; MisfireHandler() { this.setName("QuartzScheduler_" + instanceName + "-" + instanceId + "_MisfireHandler"); this.setDaemon(getMakeThreadsDaemons()); } public void initialize() { ThreadExecutor executor = getThreadExecutor(); executor.execute(MisfireHandler.this); } 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; } @Override public void run() { while (!shutdown) { long sTime = System.currentTimeMillis(); RecoverMisfiredJobsResult recoverMisfiredJobsResult = manage(); if (recoverMisfiredJobsResult.getProcessedMisfiredTriggerCount() > 0) { signalSchedulingChangeImmediately(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 ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/JobStoreTX.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.Connection; import org.quartz.JobPersistenceException; import org.quartz.SchedulerConfigException; import org.quartz.impl.jdbcjobstore.JobStoreSupport; import org.quartz.spi.ClassLoadHelper; import 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 org.quartz.spi.JobStore} class to use * within an application-server environment, use {@link * org.quartz.impl.jdbcjobstore.JobStoreCMT} * instead. *

* * @author Jeffrey Wescott * @author James House */ public class JobStoreTX extends JobStoreSupport { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @Override public void initialize(ClassLoadHelper classLoadHelper, SchedulerSignaler schedSignaler) throws SchedulerConfigException { super.initialize(classLoadHelper, schedSignaler); getLog().info("JobStoreTX initialized."); } /** * For JobStoreTX, the non-managed TX connection is just * the normal connection because it is not CMT. * * @see JobStoreSupport#getConnection() */ @Override protected Connection getNonManagedTXConnection() throws JobPersistenceException { return getConnection(); } /** * Execute the given callback having optionally acquired 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 acquire, for example * "TRIGGER_ACCESS". If null, then no lock is acquired, but the * lockCallback is still executed in a transaction. * * @see JobStoreSupport#executeInNonManagedTXLock(java.lang.String, org.quartz.impl.jdbcjobstore.JobStoreSupport.TransactionCallback, org.quartz.impl.jdbcjobstore.JobStoreSupport.TransactionValidator) * @see JobStoreCMT#executeInLock(String, TransactionCallback) * @see JobStoreSupport#getNonManagedTXConnection() * @see JobStoreSupport#getConnection() */ @Override protected Object executeInLock( String lockName, TransactionCallback txCallback) throws JobPersistenceException { return executeInNonManagedTXLock(lockName, txCallback, null); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/LockException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import 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 { private static final long serialVersionUID = 3993800462589137228L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public LockException(String msg) { super(msg); } public LockException(String msg, Throwable cause) { super(msg, cause); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/MSSQLDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import static org.quartz.TriggerKey.triggerKey; 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 java.util.LinkedList; import java.util.List; import org.quartz.TriggerKey; /** *

* This is a driver delegate for the MSSQL JDBC driver. *

* * @author Jeffrey Wescott */ public class MSSQLDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override 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; try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { return rs.getBinaryStream(colName); } return getObjectFromBlob(rs, colName); } @Override public List selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan, int maxCount) throws SQLException { // Set max rows to retrieve if (maxCount < 1) maxCount = 1; // we want at least one trigger back. String selectTriggerToAcquire = "SELECT TOP " + maxCount + " " + SELECT_NEXT_TRIGGER_TO_ACQUIRE.substring(6); PreparedStatement ps = null; ResultSet rs = null; List nextTriggers = new LinkedList<>(); try { ps = conn.prepareStatement(rtp(selectTriggerToAcquire)); ps.setMaxRows(maxCount); // Try to give jdbc driver a hint to hopefully not pull over more than the few rows we actually need. // Note: in some jdbc drivers, such as MySQL, you must set maxRows before fetchSize, or you get exception! ps.setFetchSize(maxCount); 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() < maxCount) { nextTriggers.add(triggerKey( rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP))); } return nextTriggers; } finally { closeResultSet(rs); closeStatement(ps); } } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/NoRecordFoundException.java ================================================ package org.quartz.impl.jdbcjobstore; import org.quartz.TriggerKey; public class NoRecordFoundException extends IllegalStateException { private static final long serialVersionUID = 1L; private final TriggerKey triggerKey; private final String scheduleName; private final String statement; private final Class delegateClass; public NoRecordFoundException(TriggerKey triggerKey, String scheduleName, Class delegateClass) { super("No record found for selection of Trigger with key: '" + triggerKey + "' and Scheduler: " + scheduleName + " and Delegate: " + delegateClass); this.triggerKey = triggerKey; this.scheduleName = scheduleName; this.delegateClass = delegateClass; this.statement = null; } public NoRecordFoundException(TriggerKey triggerKey, String scheduleName, String statement) { super("No record found for selection of Trigger with key: '" + triggerKey + "' and statement: " + statement); this.triggerKey = triggerKey; this.scheduleName = scheduleName; this.statement = statement; this.delegateClass = null; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/NoSuchDelegateException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import 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 { private static final long serialVersionUID = -4255865028975822979L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public NoSuchDelegateException(String msg) { super(msg); } public NoSuchDelegateException(String msg, Throwable cause) { super(msg, cause); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/PointbaseDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.quartz.Calendar; import org.quartz.JobDetail; import org.quartz.spi.OperableTrigger; /** *

* This is a driver delegate for the Pointbase JDBC driver. *

* * @author Gregg Freeman */ public class PointbaseDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override 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; try { ps = conn.prepareStatement(rtp(INSERT_JOB_DETAIL)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); ps.setString(3, job.getDescription()); ps.setString(4, job.getJobClass().getName()); setBoolean(ps, 5, job.isDurable()); setBoolean(ps, 6, job.isConcurrentExecutionDisallowed()); setBoolean(ps, 7, job.isPersistJobDataAfterExecution()); setBoolean(ps, 8, job.requestsRecovery()); ps.setBinaryStream(9, bais, len); insertResult = ps.executeUpdate(); } finally { closeStatement(ps); } 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 */ @Override 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; 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.isConcurrentExecutionDisallowed()); setBoolean(ps, 5, job.isPersistJobDataAfterExecution()); setBoolean(ps, 6, job.requestsRecovery()); ps.setBinaryStream(7, bais, len); ps.setString(8, job.getKey().getName()); ps.setString(9, job.getKey().getGroup()); insertResult = ps.executeUpdate(); } finally { closeStatement(ps); } return insertResult; } @Override public int insertTrigger(Connection conn, OperableTrigger 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; try { ps = conn.prepareStatement(rtp(INSERT_TRIGGER)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setString(3, trigger.getJobKey().getName()); ps.setString(4, trigger.getJobKey().getGroup()); ps.setString(5, trigger.getDescription()); ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger .getNextFireTime().getTime()))); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(7, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(8, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(9, type); ps.setBigDecimal(10, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(11, new BigDecimal(String.valueOf(endTime))); ps.setString(12, trigger.getCalendarName()); ps.setInt(13, trigger.getMisfireInstruction()); ps.setBinaryStream(14, bais, len); ps.setInt(15, trigger.getPriority()); insertResult = ps.executeUpdate(); if(tDel == null) insertBlobTrigger(conn, trigger); else tDel.insertExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeStatement(ps); } return insertResult; } @Override public int updateTrigger(Connection conn, OperableTrigger 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; try { ps = conn.prepareStatement(rtp(UPDATE_TRIGGER)); ps.setString(1, trigger.getJobKey().getName()); ps.setString(2, trigger.getJobKey().getGroup()); ps.setString(3, trigger.getDescription()); long nextFireTime = -1; if (trigger.getNextFireTime() != null) { nextFireTime = trigger.getNextFireTime().getTime(); } ps.setBigDecimal(4, new BigDecimal(String.valueOf(nextFireTime))); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(5, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(6, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(7, type); ps.setBigDecimal(8, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(9, new BigDecimal(String.valueOf(endTime))); ps.setString(10, trigger.getCalendarName()); ps.setInt(11, trigger.getMisfireInstruction()); ps.setInt(12, trigger.getPriority()); ps.setBinaryStream(13, bais, len); ps.setString(14, trigger.getKey().getName()); ps.setString(15, trigger.getKey().getGroup()); insertResult = ps.executeUpdate(); if(tDel == null) updateBlobTrigger(conn, trigger); else tDel.updateExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeStatement(ps); } 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 */ @Override 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.getKey().getName()); ps.setString(3, job.getKey().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 */ @Override 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 */ @Override 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 */ @Override 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 (binaryInput.available() != 0) { try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } 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 */ @Override protected Object getJobDataFromBlob(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; } return new ByteArrayInputStream(data); } return getObjectFromBlob(rs, colName); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/PostgreSQLDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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; /** *

* This is a driver delegate for the PostgreSQL JDBC driver. *

* * @author Jeffrey Wescott */ public class PostgreSQLDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override protected Object getObjectFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { InputStream binaryInput; byte[] bytes = rs.getBytes(colName); Object obj = null; if(bytes != null && bytes.length != 0) { binaryInput = new ByteArrayInputStream(bytes); try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { InputStream binaryInput; byte[] bytes = rs.getBytes(colName); if(bytes == null || bytes.length == 0) { return null; } binaryInput = new ByteArrayInputStream(bytes); return binaryInput; } return getObjectFromBlob(rs, colName); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/SchedulerStateRecord.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; /** *

* Conveys a scheduler-instance state record. *

* * @author James House */ public class SchedulerStateRecord implements java.io.Serializable { private static final long serialVersionUID = -715704959016191445L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/Semaphore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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. */ void releaseLock(String lockName) throws LockException; /** * Whether this Semaphore implementation requires a database connection for * its lock management operations. * * @see #obtainLock(Connection, String) * @see #releaseLock(String) */ boolean requiresConnection(); } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/SimplePropertiesTriggerPersistenceDelegateSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.quartz.JobDetail; import org.quartz.ScheduleBuilder; import org.quartz.TriggerKey; import org.quartz.spi.OperableTrigger; /** * A base implementation of {@link TriggerPersistenceDelegate} that persists * trigger fields in the "QRTZ_SIMPROP_TRIGGERS" table. This allows extending * concrete classes to simply implement a couple methods that do the work of * getting/setting the trigger's fields, and creating the {@link ScheduleBuilder} * for the particular type of trigger. * * @see CalendarIntervalTriggerPersistenceDelegate for an example extension * * @author jhouse */ public abstract class SimplePropertiesTriggerPersistenceDelegateSupport implements TriggerPersistenceDelegate, StdJDBCConstants { protected static final String TABLE_SIMPLE_PROPERTIES_TRIGGERS = "SIMPROP_TRIGGERS"; protected static final String COL_STR_PROP_1 = "STR_PROP_1"; protected static final String COL_STR_PROP_2 = "STR_PROP_2"; protected static final String COL_STR_PROP_3 = "STR_PROP_3"; protected static final String COL_INT_PROP_1 = "INT_PROP_1"; protected static final String COL_INT_PROP_2 = "INT_PROP_2"; protected static final String COL_LONG_PROP_1 = "LONG_PROP_1"; protected static final String COL_LONG_PROP_2 = "LONG_PROP_2"; protected static final String COL_DEC_PROP_1 = "DEC_PROP_1"; protected static final String COL_DEC_PROP_2 = "DEC_PROP_2"; protected static final String COL_BOOL_PROP_1 = "BOOL_PROP_1"; protected static final String COL_BOOL_PROP_2 = "BOOL_PROP_2"; protected static final String SELECT_SIMPLE_PROPS_TRIGGER = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_PROPERTIES_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; protected static final String DELETE_SIMPLE_PROPS_TRIGGER = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_PROPERTIES_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; protected static final String INSERT_SIMPLE_PROPS_TRIGGER = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_PROPERTIES_TRIGGERS + " (" + COL_SCHEDULER_NAME + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_STR_PROP_1 + ", " + COL_STR_PROP_2 + ", " + COL_STR_PROP_3 + ", " + COL_INT_PROP_1 + ", " + COL_INT_PROP_2 + ", " + COL_LONG_PROP_1 + ", " + COL_LONG_PROP_2 + ", " + COL_DEC_PROP_1 + ", " + COL_DEC_PROP_2 + ", " + COL_BOOL_PROP_1 + ", " + COL_BOOL_PROP_2 + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; protected static final String UPDATE_SIMPLE_PROPS_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_PROPERTIES_TRIGGERS + " SET " + COL_STR_PROP_1 + " = ?, " + COL_STR_PROP_2 + " = ?, " + COL_STR_PROP_3 + " = ?, " + COL_INT_PROP_1 + " = ?, " + COL_INT_PROP_2 + " = ?, " + COL_LONG_PROP_1 + " = ?, " + COL_LONG_PROP_2 + " = ?, " + COL_DEC_PROP_1 + " = ?, " + COL_DEC_PROP_2 + " = ?, " + COL_BOOL_PROP_1 + " = ?, " + COL_BOOL_PROP_2 + " = ? WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; protected String tablePrefix; protected String schedNameLiteral; public void initialize(String theTablePrefix, String schedName) { this.tablePrefix = theTablePrefix; this.schedNameLiteral = "'" + schedName + "'"; } protected abstract SimplePropertiesTriggerProperties getTriggerProperties(OperableTrigger trigger); protected abstract TriggerPropertyBundle getTriggerPropertyBundle(SimplePropertiesTriggerProperties properties); public int deleteExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(DELETE_SIMPLE_PROPS_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } public int insertExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { SimplePropertiesTriggerProperties properties = getTriggerProperties(trigger); PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(INSERT_SIMPLE_PROPS_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setString(3, properties.getString1()); ps.setString(4, properties.getString2()); ps.setString(5, properties.getString3()); ps.setInt(6, properties.getInt1()); ps.setInt(7, properties.getInt2()); ps.setLong(8, properties.getLong1()); ps.setLong(9, properties.getLong2()); ps.setBigDecimal(10, properties.getDecimal1()); ps.setBigDecimal(11, properties.getDecimal2()); ps.setBoolean(12, properties.isBoolean1()); ps.setBoolean(13, properties.isBoolean2()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } public TriggerPropertyBundle loadExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(Util.rtp(SELECT_SIMPLE_PROPS_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); rs = ps.executeQuery(); if (rs.next()) { return loadExtendedTriggerPropertiesFromResultSet(rs, triggerKey); } throw new NoRecordFoundException(triggerKey, schedNameLiteral, Util.rtp(SELECT_SIMPLE_TRIGGER, tablePrefix, schedNameLiteral)); } finally { Util.closeResultSet(rs); Util.closeStatement(ps); } } public TriggerPropertyBundle loadExtendedTriggerPropertiesFromResultSet(ResultSet rs, TriggerKey triggerKey) throws SQLException { SimplePropertiesTriggerProperties properties = new SimplePropertiesTriggerProperties(); if (Util.areNull(rs, COL_STR_PROP_1, COL_STR_PROP_2, COL_STR_PROP_3, COL_INT_PROP_1, COL_INT_PROP_2, COL_LONG_PROP_1, COL_LONG_PROP_2, COL_DEC_PROP_1, COL_DEC_PROP_2, COL_BOOL_PROP_1, COL_BOOL_PROP_2)) { throw new NoRecordFoundException(triggerKey, schedNameLiteral, this.getClass()); } properties.setString1(rs.getString(COL_STR_PROP_1)); properties.setString2(rs.getString(COL_STR_PROP_2)); properties.setString3(rs.getString(COL_STR_PROP_3)); properties.setInt1(rs.getInt(COL_INT_PROP_1)); properties.setInt2(rs.getInt(COL_INT_PROP_2)); properties.setLong1(rs.getLong(COL_LONG_PROP_1)); properties.setLong2(rs.getLong(COL_LONG_PROP_2)); properties.setDecimal1(rs.getBigDecimal(COL_DEC_PROP_1)); properties.setDecimal2(rs.getBigDecimal(COL_DEC_PROP_2)); properties.setBoolean1(rs.getBoolean(COL_BOOL_PROP_1)); properties.setBoolean2(rs.getBoolean(COL_BOOL_PROP_2)); return getTriggerPropertyBundle(properties); } public boolean hasInlinedResultSetProperties() { return true; } public int updateExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { SimplePropertiesTriggerProperties properties = getTriggerProperties(trigger); PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(UPDATE_SIMPLE_PROPS_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, properties.getString1()); ps.setString(2, properties.getString2()); ps.setString(3, properties.getString3()); ps.setInt(4, properties.getInt1()); ps.setInt(5, properties.getInt2()); ps.setLong(6, properties.getLong1()); ps.setLong(7, properties.getLong2()); ps.setBigDecimal(8, properties.getDecimal1()); ps.setBigDecimal(9, properties.getDecimal2()); ps.setBoolean(10, properties.isBoolean1()); ps.setBoolean(11, properties.isBoolean2()); ps.setString(12, trigger.getKey().getName()); ps.setString(13, trigger.getKey().getGroup()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/SimplePropertiesTriggerProperties.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.math.BigDecimal; public class SimplePropertiesTriggerProperties { private String string1; private String string2; private String string3; private int int1; private int int2; private long long1; private long long2; private BigDecimal decimal1; private BigDecimal decimal2; private boolean boolean1; private boolean boolean2; public String getString1() { return string1; } public void setString1(String string1) { this.string1 = string1; } public String getString2() { return string2; } public void setString2(String string2) { this.string2 = string2; } public String getString3() { return string3; } public void setString3(String string3) { this.string3 = string3; } public int getInt1() { return int1; } public void setInt1(int int1) { this.int1 = int1; } public int getInt2() { return int2; } public void setInt2(int int2) { this.int2 = int2; } public long getLong1() { return long1; } public void setLong1(long long1) { this.long1 = long1; } public long getLong2() { return long2; } public void setLong2(long long2) { this.long2 = long2; } public BigDecimal getDecimal1() { return decimal1; } public void setDecimal1(BigDecimal decimal1) { this.decimal1 = decimal1; } public BigDecimal getDecimal2() { return decimal2; } public void setDecimal2(BigDecimal decimal2) { this.decimal2 = decimal2; } public boolean isBoolean1() { return boolean1; } public void setBoolean1(boolean boolean1) { this.boolean1 = boolean1; } public boolean isBoolean2() { return boolean2; } public void setBoolean2(boolean boolean2) { this.boolean2 = boolean2; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/SimpleSemaphore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.Connection; import java.util.HashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ final ThreadLocal> lockOwners = new ThreadLocal<>(); final HashSet locks = new HashSet<>(); private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger getLog() { return log; } private HashSet getThreadLocks() { HashSet threadLocks = 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(); if(log.isDebugEnabled()) { log.debug("Lock '{}' is desired by: {}", lockName, Thread.currentThread().getName()); } if (!isLockOwner(lockName)) { if(log.isDebugEnabled()) { log.debug("Lock '{}' is being obtained: {}", lockName, Thread.currentThread().getName()); } while (locks.contains(lockName)) { try { this.wait(); } catch (InterruptedException ie) { if(log.isDebugEnabled()) { log.debug("Lock '{}' was not obtained by: {}", lockName, Thread.currentThread().getName()); } } } if(log.isDebugEnabled()) { log.debug("Lock '{}' given to: {}", lockName, Thread.currentThread().getName()); } getThreadLocks().add(lockName); locks.add(lockName); } else if(log.isDebugEnabled()) { log.debug("Lock '{}' already owned by: {} -- but not owner!", lockName, Thread.currentThread().getName(), 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(String lockName) { lockName = lockName.intern(); if (isLockOwner(lockName)) { if(getLog().isDebugEnabled()) { getLog().debug("Lock '{}' returned by: {}", lockName, Thread.currentThread().getName()); } getThreadLocks().remove(lockName); locks.remove(lockName); this.notifyAll(); } else if (getLog().isDebugEnabled()) { getLog().debug("Lock '{}' attempt to return by: {} -- but not owner!", lockName, Thread.currentThread().getName(), new Exception("stack-trace of wrongful returner")); } } /** * Determine whether the calling thread owns a lock on the identified * resource. */ public synchronized boolean isLockOwner(String lockName) { lockName = lockName.intern(); return getThreadLocks().contains(lockName); } /** * This Semaphore implementation does not use the database. */ public boolean requiresConnection() { return false; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/SimpleTriggerPersistenceDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; import java.math.BigDecimal; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.quartz.JobDetail; import org.quartz.SimpleScheduleBuilder; import org.quartz.SimpleTrigger; import org.quartz.TriggerKey; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.OperableTrigger; public class SimpleTriggerPersistenceDelegate implements TriggerPersistenceDelegate, StdJDBCConstants { protected String tablePrefix; protected String schedNameLiteral; public void initialize(String theTablePrefix, String schedName) { this.tablePrefix = theTablePrefix; this.schedNameLiteral = "'" + schedName + "'"; } public String getHandledTriggerTypeDiscriminator() { return TTYPE_SIMPLE; } public boolean canHandleTriggerType(OperableTrigger trigger) { return ((trigger instanceof SimpleTriggerImpl) && !((SimpleTriggerImpl)trigger).hasAdditionalProperties()); } public int deleteExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(DELETE_SIMPLE_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } public int insertExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { SimpleTrigger simpleTrigger = (SimpleTrigger)trigger; PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(INSERT_SIMPLE_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setInt(3, simpleTrigger.getRepeatCount()); ps.setBigDecimal(4, new BigDecimal(String.valueOf(simpleTrigger.getRepeatInterval()))); ps.setInt(5, simpleTrigger.getTimesTriggered()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } public TriggerPropertyBundle loadExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(Util.rtp(SELECT_SIMPLE_TRIGGER, tablePrefix, schedNameLiteral)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); rs = ps.executeQuery(); if (rs.next()) { return loadExtendedTriggerPropertiesFromResultSet(rs, triggerKey); } throw new NoRecordFoundException(triggerKey, schedNameLiteral, Util.rtp(SELECT_SIMPLE_TRIGGER, tablePrefix, schedNameLiteral)); } finally { Util.closeResultSet(rs); Util.closeStatement(ps); } } public TriggerPropertyBundle loadExtendedTriggerPropertiesFromResultSet(ResultSet rs, TriggerKey triggerKey) throws SQLException { if (Util.areNull(rs, COL_REPEAT_COUNT, COL_REPEAT_INTERVAL, COL_TIMES_TRIGGERED)) { throw new NoRecordFoundException(triggerKey, schedNameLiteral, this.getClass()); } int repeatCount = rs.getInt(COL_REPEAT_COUNT); long repeatInterval = rs.getLong(COL_REPEAT_INTERVAL); int timesTriggered = rs.getInt(COL_TIMES_TRIGGERED); SimpleScheduleBuilder sb = SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(repeatCount) .withIntervalInMilliseconds(repeatInterval); String[] statePropertyNames = { "timesTriggered" }; Object[] statePropertyValues = { timesTriggered }; return new TriggerPropertyBundle(sb, statePropertyNames, statePropertyValues); } public boolean hasInlinedResultSetProperties() { return true; } public int updateExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { SimpleTrigger simpleTrigger = (SimpleTrigger)trigger; PreparedStatement ps = null; try { ps = conn.prepareStatement(Util.rtp(UPDATE_SIMPLE_TRIGGER, tablePrefix, schedNameLiteral)); ps.setInt(1, simpleTrigger.getRepeatCount()); ps.setBigDecimal(2, new BigDecimal(String.valueOf(simpleTrigger.getRepeatInterval()))); ps.setInt(3, simpleTrigger.getTimesTriggered()); ps.setString(4, simpleTrigger.getKey().getName()); ps.setString(5, simpleTrigger.getKey().getGroup()); return ps.executeUpdate(); } finally { Util.closeStatement(ps); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/StdJDBCConstants.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_BOOL_PROP_1; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_BOOL_PROP_2; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_DEC_PROP_1; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_DEC_PROP_2; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_INT_PROP_1; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_INT_PROP_2; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_LONG_PROP_1; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_LONG_PROP_2; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_STR_PROP_1; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_STR_PROP_2; import static org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport.COL_STR_PROP_3; import org.quartz.Trigger; /** *

* This interface extends {@link * org.quartz.impl.jdbcjobstore.Constants} * to include the query string constants in use by the {@link * org.quartz.impl.jdbcjobstore.StdJDBCDelegate} * class. *

* * @author Jeffrey Wescott */ public interface StdJDBCConstants extends Constants { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // table prefix substitution string String TABLE_PREFIX_SUBST = "{0}"; // table prefix substitution string String SCHED_NAME_SUBST = "{1}"; // QUERIES String UPDATE_TRIGGER_STATES_FROM_OTHER_STATES = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + " = ?" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND (" + COL_TRIGGER_STATE + " = ? OR " + COL_TRIGGER_STATE + " = ?)"; String SELECT_MISFIRED_TRIGGERS = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND NOT (" + COL_MISFIRE_INSTRUCTION + " = " + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY + ") AND " + COL_NEXT_FIRE_TIME + " < ? " + "ORDER BY " + COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC"; String SELECT_TRIGGERS_IN_STATE = "SELECT " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_STATE + " = ?"; String SELECT_MISFIRED_TRIGGERS_IN_STATE = "SELECT " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND NOT (" + COL_MISFIRE_INSTRUCTION + " = " + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY + ") AND " + COL_NEXT_FIRE_TIME + " < ? AND " + COL_TRIGGER_STATE + " = ? " + "ORDER BY " + COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC"; String COUNT_MISFIRED_TRIGGERS_IN_STATE = "SELECT COUNT(" + COL_TRIGGER_NAME + ") FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND NOT (" + COL_MISFIRE_INSTRUCTION + " = " + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY + ") AND " + COL_NEXT_FIRE_TIME + " < ? " + "AND " + COL_TRIGGER_STATE + " = ?"; String SELECT_HAS_MISFIRED_TRIGGERS_IN_STATE = "SELECT " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND NOT (" + COL_MISFIRE_INSTRUCTION + " = " + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY + ") AND " + COL_NEXT_FIRE_TIME + " < ? " + "AND " + COL_TRIGGER_STATE + " = ? " + "ORDER BY " + COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC"; String SELECT_MISFIRED_TRIGGERS_IN_GROUP_IN_STATE = "SELECT " + COL_TRIGGER_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND NOT (" + COL_MISFIRE_INSTRUCTION + " = " + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY + ") AND " + COL_NEXT_FIRE_TIME + " < ? AND " + COL_TRIGGER_GROUP + " = ? AND " + COL_TRIGGER_STATE + " = ? " + "ORDER BY " + COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC"; String DELETE_FIRED_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String INSERT_JOB_DETAIL = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " (" + COL_SCHEDULER_NAME + ", " + COL_JOB_NAME + ", " + COL_JOB_GROUP + ", " + COL_DESCRIPTION + ", " + COL_JOB_CLASS + ", " + COL_IS_DURABLE + ", " + COL_IS_NONCONCURRENT + ", " + COL_IS_UPDATE_DATA + ", " + COL_REQUESTS_RECOVERY + ", " + COL_JOB_DATAMAP + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?, ?, ?, ?, ?, ?)"; String UPDATE_JOB_DETAIL = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " SET " + COL_DESCRIPTION + " = ?, " + COL_JOB_CLASS + " = ?, " + COL_IS_DURABLE + " = ?, " + COL_IS_NONCONCURRENT + " = ?, " + COL_IS_UPDATE_DATA + " = ?, " + COL_REQUESTS_RECOVERY + " = ?, " + COL_JOB_DATAMAP + " = ? " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String SELECT_BULK_TRIGGERS_BASE = "SELECT " + "T." + COL_SCHEDULER_NAME + ", T." + COL_TRIGGER_NAME + ", T." + COL_TRIGGER_GROUP + ", T." + COL_JOB_NAME + ", T." + COL_JOB_GROUP + ", T." + COL_DESCRIPTION + ", T." + COL_NEXT_FIRE_TIME + ", " + "T." + COL_PREV_FIRE_TIME + ", T." + COL_PRIORITY + ", T." + COL_TRIGGER_STATE + ", T." + COL_TRIGGER_TYPE + ", T." + COL_START_TIME + ", T." + COL_END_TIME + ", T." + COL_CALENDAR_NAME + ", T." + COL_MISFIRE_INSTRUCTION + ", T." + COL_JOB_DATAMAP + ", " + "QBT." + COL_BLOB + ", " + "QCT." + COL_CRON_EXPRESSION + ", QCT." + COL_TIME_ZONE_ID + ", " + "QST." + COL_REPEAT_COUNT + ", QST." + COL_REPEAT_INTERVAL + ", QST." + COL_TIMES_TRIGGERED + ", " + "QSP." + COL_STR_PROP_1 + ", QSP." + COL_STR_PROP_2 + ", QSP." + COL_STR_PROP_3 + ", QSP." + COL_INT_PROP_1 + ", " + "QSP." + COL_INT_PROP_2 + ", QSP." + COL_LONG_PROP_1 + ", QSP." + COL_LONG_PROP_2 + ", QSP." + COL_DEC_PROP_1 + ", " + "QSP." + COL_DEC_PROP_2 + ", QSP." + COL_BOOL_PROP_1 + ", QSP." + COL_BOOL_PROP_2 + " " + "FROM " + TABLE_PREFIX_SUBST + "TRIGGERS T " + "LEFT JOIN " + TABLE_PREFIX_SUBST + "BLOB_TRIGGERS QBT ON " + "T." + COL_SCHEDULER_NAME + " = QBT." + COL_SCHEDULER_NAME + " AND T." + COL_TRIGGER_NAME + " = QBT." + COL_TRIGGER_NAME + " AND T." + COL_TRIGGER_GROUP + " = QBT." + COL_TRIGGER_GROUP + " " + "LEFT JOIN " + TABLE_PREFIX_SUBST + "CRON_TRIGGERS QCT ON " + "T." + COL_SCHEDULER_NAME + " = QCT." + COL_SCHEDULER_NAME + " AND T." + COL_TRIGGER_NAME + " = QCT." + COL_TRIGGER_NAME + " AND T." + COL_TRIGGER_GROUP + " = QCT." + COL_TRIGGER_GROUP + " " + "LEFT JOIN " + TABLE_PREFIX_SUBST + "SIMPLE_TRIGGERS QST ON " + "T." + COL_SCHEDULER_NAME + " = QST." + COL_SCHEDULER_NAME + " AND T." + COL_TRIGGER_NAME + " = QST." + COL_TRIGGER_NAME + " AND T." + COL_TRIGGER_GROUP + " = QST." + COL_TRIGGER_GROUP + " " + "LEFT JOIN " + TABLE_PREFIX_SUBST + "SIMPROP_TRIGGERS QSP ON " + "T." + COL_SCHEDULER_NAME + " = QSP." + COL_SCHEDULER_NAME + " AND T." + COL_TRIGGER_NAME + " = QSP." + COL_TRIGGER_NAME + " AND T." + COL_TRIGGER_GROUP + " = QSP." + COL_TRIGGER_GROUP + " "; String SELECT_TRIGGERS_FOR_CALENDAR_V2 = SELECT_BULK_TRIGGERS_BASE + "WHERE T." + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String SELECT_TRIGGERS_FOR_JOB_V2 = SELECT_BULK_TRIGGERS_BASE + "WHERE T." + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_TRIGGERS_WITH_MATCHERS = SELECT_BULK_TRIGGERS_BASE + "WHERE T." + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND T." + COL_TRIGGER_GROUP + " {JOB_GROUP_LIKE} ?" + " AND T." + COL_TRIGGER_NAME + " {TRIGGER_NAME_LIKE} ?"; String DELETE_JOB_DETAIL = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_JOB_NONCONCURRENT = "SELECT " + COL_IS_NONCONCURRENT + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_JOB_EXISTENCE = "SELECT " + COL_JOB_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String UPDATE_JOB_DATA = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " SET " + COL_JOB_DATAMAP + " = ? " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_JOB_DETAIL = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_JOB_DETAILS_LIKE = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_GROUP + " LIKE ?"; String SELECT_JOB_DETAILS = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_GROUP + " LIKE ?"; String SELECT_NUM_JOBS = "SELECT COUNT(" + COL_JOB_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_JOB_GROUPS = "SELECT DISTINCT(" + COL_JOB_GROUP + ") FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_JOBS_IN_GROUP_LIKE = "SELECT " + COL_JOB_NAME + ", " + COL_JOB_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_GROUP + " LIKE ?"; String SELECT_JOBS_IN_GROUP = "SELECT " + COL_JOB_NAME + ", " + COL_JOB_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_GROUP + " = ?"; String INSERT_TRIGGER = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " (" + COL_SCHEDULER_NAME + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_JOB_NAME + ", " + COL_JOB_GROUP + ", " + 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(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; String INSERT_SIMPLE_TRIGGER = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " (" + COL_SCHEDULER_NAME + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_REPEAT_COUNT + ", " + COL_REPEAT_INTERVAL + ", " + COL_TIMES_TRIGGERED + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?, ?)"; String INSERT_CRON_TRIGGER = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " (" + COL_SCHEDULER_NAME + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_CRON_EXPRESSION + ", " + COL_TIME_ZONE_ID + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?)"; String INSERT_BLOB_TRIGGER = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " (" + COL_SCHEDULER_NAME + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_BLOB + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?)"; String UPDATE_TRIGGER_SKIP_DATA = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_JOB_NAME + " = ?, " + COL_JOB_GROUP + " = ?, " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String UPDATE_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_JOB_NAME + " = ?, " + COL_JOB_GROUP + " = ?, " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String UPDATE_CRON_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " SET " + COL_CRON_EXPRESSION + " = ?, " + COL_TIME_ZONE_ID + " = ? WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String UPDATE_BLOB_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " SET " + COL_BLOB + " = ? WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_TRIGGER_EXISTENCE = "SELECT " + COL_TRIGGER_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String UPDATE_TRIGGER_STATE = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + " = ?" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ? AND " + COL_TRIGGER_STATE + " = ?"; String UPDATE_TRIGGER_GROUP_STATE_FROM_STATE = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + " = ?" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " LIKE ? AND " + COL_TRIGGER_STATE + " = ?"; String UPDATE_TRIGGER_STATE_FROM_STATES = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + " = ?" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " LIKE ? 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ? AND " + COL_TRIGGER_STATE + " = ?"; String DELETE_SIMPLE_TRIGGER = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String DELETE_CRON_TRIGGER = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String DELETE_BLOB_TRIGGER = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String DELETE_TRIGGER = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND J." + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_TRIGGER_DATA = "SELECT " + COL_JOB_DATAMAP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_TRIGGER_STATE = "SELECT " + COL_TRIGGER_STATE + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_SIMPLE_TRIGGER = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_CRON_TRIGGER = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_BLOB_TRIGGER = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_NUM_TRIGGERS = "SELECT COUNT(" + COL_TRIGGER_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_NUM_TRIGGERS_IN_GROUP = "SELECT COUNT(" + COL_TRIGGER_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_TRIGGER_GROUPS = "SELECT DISTINCT(" + COL_TRIGGER_GROUP + ") FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_TRIGGER_GROUPS_FILTERED = "SELECT DISTINCT(" + COL_TRIGGER_GROUP + ") FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " LIKE ?"; String SELECT_TRIGGERS_IN_GROUP_LIKE = "SELECT " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " LIKE ?"; String SELECT_TRIGGERS_IN_GROUP = "SELECT " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " = ?"; String INSERT_CALENDAR = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " (" + COL_SCHEDULER_NAME + ", " + COL_CALENDAR_NAME + ", " + COL_CALENDAR + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?)"; String UPDATE_CALENDAR = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " SET " + COL_CALENDAR + " = ? " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String SELECT_CALENDAR_EXISTENCE = "SELECT " + COL_CALENDAR_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String SELECT_CALENDAR = "SELECT *" + " FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String SELECT_REFERENCED_CALENDAR = "SELECT " + COL_CALENDAR_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String DELETE_CALENDAR = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; String SELECT_NUM_CALENDARS = "SELECT COUNT(" + COL_CALENDAR_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_CALENDARS = "SELECT " + COL_CALENDAR_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_STATE + " = ? AND " + COL_NEXT_FIRE_TIME + " <= ? " + "AND (" + COL_MISFIRE_INSTRUCTION + " = -1 OR (" +COL_MISFIRE_INSTRUCTION+ " <> -1 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_SCHEDULER_NAME + ", " + COL_ENTRY_ID + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_INSTANCE_NAME + ", " + COL_FIRED_TIME + ", " + COL_SCHED_TIME + ", " + COL_ENTRY_STATE + ", " + COL_JOB_NAME + ", " + COL_JOB_GROUP + ", " + COL_IS_NONCONCURRENT + ", " + COL_REQUESTS_RECOVERY + ", " + COL_PRIORITY + ") VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; String UPDATE_FIRED_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " SET " + COL_INSTANCE_NAME + " = ?, " + COL_FIRED_TIME + " = ?, " + COL_SCHED_TIME + " = ?, " + COL_ENTRY_STATE + " = ?, " + COL_JOB_NAME + " = ?, " + COL_JOB_GROUP + " = ?, " + COL_IS_NONCONCURRENT + " = ?, " + COL_REQUESTS_RECOVERY + " = ? WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_ENTRY_ID + " = ?"; String SELECT_INSTANCES_FIRED_TRIGGERS = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_INSTANCE_NAME + " = ?"; String SELECT_INSTANCES_RECOVERABLE_FIRED_TRIGGERS = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_FIRED_TRIGGERS = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_FIRED_TRIGGER = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_FIRED_TRIGGER_GROUP = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_FIRED_TRIGGERS_OF_JOB = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; String SELECT_FIRED_TRIGGERS_OF_JOB_GROUP = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_GROUP + " = ?"; String DELETE_FIRED_TRIGGER = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_ENTRY_ID + " = ?"; String DELETE_INSTANCES_FIRED_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_INSTANCE_NAME + " = ?"; String DELETE_NO_RECOVERY_FIRED_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_INSTANCE_NAME + " = ?" + COL_REQUESTS_RECOVERY + " = ?"; String DELETE_ALL_SIMPLE_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + "SIMPLE_TRIGGERS " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_SIMPROP_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + "SIMPROP_TRIGGERS " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_CRON_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + "CRON_TRIGGERS" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_BLOB_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + "BLOB_TRIGGERS" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_TRIGGERS = "DELETE FROM " + TABLE_PREFIX_SUBST + "TRIGGERS" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_JOB_DETAILS = "DELETE FROM " + TABLE_PREFIX_SUBST + "JOB_DETAILS" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_CALENDARS = "DELETE FROM " + TABLE_PREFIX_SUBST + "CALENDARS" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_ALL_PAUSED_TRIGGER_GRPS = "DELETE FROM " + TABLE_PREFIX_SUBST + "PAUSED_TRIGGER_GRPS" + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String SELECT_FIRED_TRIGGER_INSTANCE_NAMES = "SELECT DISTINCT " + COL_INSTANCE_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String INSERT_SCHEDULER_STATE = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " (" + COL_SCHEDULER_NAME + ", " + COL_INSTANCE_NAME + ", " + COL_LAST_CHECKIN_TIME + ", " + COL_CHECKIN_INTERVAL + ") VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?)"; String SELECT_SCHEDULER_STATE = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_INSTANCE_NAME + " = ?"; String SELECT_SCHEDULER_STATES = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_SCHEDULER_STATE = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_INSTANCE_NAME + " = ?"; String UPDATE_SCHEDULER_STATE = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " SET " + COL_LAST_CHECKIN_TIME + " = ? WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_INSTANCE_NAME + " = ?"; String INSERT_PAUSED_TRIGGER_GROUP = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " (" + COL_SCHEDULER_NAME + ", " + COL_TRIGGER_GROUP + ") VALUES(" + SCHED_NAME_SUBST + ", ?)"; String SELECT_PAUSED_TRIGGER_GROUP = "SELECT " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " = ?"; String SELECT_PAUSED_TRIGGER_GROUPS = "SELECT " + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; String DELETE_PAUSED_TRIGGER_GROUP = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_GROUP + " LIKE ?"; String DELETE_PAUSED_TRIGGER_GROUPS = "DELETE FROM " + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST; // 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 ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/StdJDBCDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import static org.quartz.JobKey.jobKey; import static org.quartz.TriggerBuilder.newTrigger; import static org.quartz.TriggerKey.triggerKey; import static org.quartz.impl.jdbcjobstore.Util.containsColumnNames; 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.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.quartz.Calendar; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.JobPersistenceException; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.quartz.impl.JobDetailImpl; import org.quartz.impl.jdbcjobstore.TriggerPersistenceDelegate.TriggerPropertyBundle; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.matchers.StringMatcher; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.OperableTrigger; import org.slf4j.Logger; /** *

* This is meant to be an abstract base class for most, if not all, {@link 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 Logger logger = null; protected String tablePrefix = DEFAULT_TABLE_PREFIX; protected String instanceId; protected String schedName; protected boolean useProperties; protected ClassLoadHelper classLoadHelper; protected final List triggerPersistenceDelegates = new LinkedList<>(); protected boolean useEnhancedStatements = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create new StdJDBCDelegate instance. *

*/ public StdJDBCDelegate() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * @param initString of the format: settingName=settingValue|otherSettingName=otherSettingValue|... * @throws NoSuchDelegateException */ public void initialize(Logger logger, String tablePrefix, String schedName, String instanceId, ClassLoadHelper classLoadHelper, boolean useProperties, String initString) throws NoSuchDelegateException { this.logger = logger; this.tablePrefix = tablePrefix; this.schedName = schedName; this.instanceId = instanceId; this.useProperties = useProperties; this.classLoadHelper = classLoadHelper; addDefaultTriggerPersistenceDelegates(); if(initString == null) { return; } String[] settings = initString.split("\\|"); for(String setting: settings) { String[] parts = setting.split("="); String name = parts[0]; if(parts.length == 1 || parts[1] == null || parts[1].isEmpty()) continue; if(name.equals("triggerPersistenceDelegateClasses")) { String[] trigDelegates = parts[1].split(","); for(String trigDelClassName: trigDelegates) { try { Class trigDelClass = classLoadHelper.loadClass(trigDelClassName); addTriggerPersistenceDelegate((TriggerPersistenceDelegate) trigDelClass.getDeclaredConstructor().newInstance()); } catch (Exception e) { throw new NoSuchDelegateException("Error instantiating TriggerPersistenceDelegate of type: " + trigDelClassName, e); } } } else throw new NoSuchDelegateException("Unknown setting: '" + name + "'"); } } protected void addDefaultTriggerPersistenceDelegates() { addTriggerPersistenceDelegate(new SimpleTriggerPersistenceDelegate()); addTriggerPersistenceDelegate(new CronTriggerPersistenceDelegate()); addTriggerPersistenceDelegate(new CalendarIntervalTriggerPersistenceDelegate()); addTriggerPersistenceDelegate(new DailyTimeIntervalTriggerPersistenceDelegate()); } protected boolean canUseProperties() { return useProperties; } public void addTriggerPersistenceDelegate(TriggerPersistenceDelegate delegate) { logger.debug("Adding TriggerPersistenceDelegate of type: {}", delegate.getClass().getCanonicalName()); delegate.initialize(tablePrefix, schedName); this.triggerPersistenceDelegates.add(delegate); } public TriggerPersistenceDelegate findTriggerPersistenceDelegate(OperableTrigger trigger) { for(TriggerPersistenceDelegate delegate: triggerPersistenceDelegates) { if(delegate.canHandleTriggerType(trigger)) return delegate; } return null; } public TriggerPersistenceDelegate findTriggerPersistenceDelegate(String discriminator) { for(TriggerPersistenceDelegate delegate: triggerPersistenceDelegates) { if(delegate.getHandledTriggerTypeDiscriminator().equals(discriminator)) return delegate; } return null; } //--------------------------------------------------------------------------- // 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 * org.quartz.utils.Key} objects */ public List 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(); LinkedList list = new LinkedList<>(); while (rs.next()) { String triggerName = rs.getString(COL_TRIGGER_NAME); String groupName = rs.getString(COL_TRIGGER_GROUP); list.add(triggerKey(triggerName, groupName)); } return list; } 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 List 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(); LinkedList list = new LinkedList<>(); while (rs.next()) { list.add(triggerKey(rs.getString(1), rs.getString(2))); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } public List 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(); LinkedList list = new LinkedList<>(); while (rs.next()) { String triggerName = rs.getString(COL_TRIGGER_NAME); String groupName = rs.getString(COL_TRIGGER_GROUP); list.add(triggerKey(triggerName, groupName)); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Get the names of all of the triggers in the given state 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 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 hasMisfiredTriggersInState(Connection conn, String state1, long ts, int count, List resultList) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_HAS_MISFIRED_TRIGGERS_IN_STATE)); ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); ps.setString(2, state1); rs = ps.executeQuery(); boolean hasReachedLimit = false; while (rs.next() && (!hasReachedLimit)) { if (resultList.size() == count) { hasReachedLimit = true; } else { String triggerName = rs.getString(COL_TRIGGER_NAME); String groupName = rs.getString(COL_TRIGGER_GROUP); resultList.add(triggerKey(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 countMisfiredTriggersInState( Connection conn, String state1, long ts) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(COUNT_MISFIRED_TRIGGERS_IN_STATE)); ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); ps.setString(2, state1); 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 * org.quartz.utils.Key} objects */ public List 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(); LinkedList list = new LinkedList<>(); while (rs.next()) { String triggerName = rs.getString(COL_TRIGGER_NAME); list.add(triggerKey(triggerName, groupName)); } return list; } 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 * 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 org.quartz.Trigger} objects */ public List 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(); LinkedList list = new LinkedList<>(); 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); long scheduledTime = rs.getLong(COL_SCHED_TIME); int priority = rs.getInt(COL_PRIORITY); @SuppressWarnings("deprecation") SimpleTriggerImpl rcvryTrig = new SimpleTriggerImpl("recover_" + instanceId + "_" + dumId++, Scheduler.DEFAULT_RECOVERY_GROUP, new Date(scheduledTime)); rcvryTrig.setJobName(jobName); rcvryTrig.setJobGroup(jobGroup); rcvryTrig.setPriority(priority); rcvryTrig.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); 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)); jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_SCHEDULED_FIRETIME_IN_MILLISECONDS, String.valueOf(scheduledTime)); rcvryTrig.setJobDataMap(jd); list.add(rcvryTrig); } return list; } 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 theInstanceId) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(DELETE_INSTANCES_FIRED_TRIGGERS)); ps.setString(1, theInstanceId); return ps.executeUpdate(); } finally { closeStatement(ps); } } /** * Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws SQLException */ public void clearData(Connection conn) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(DELETE_ALL_SIMPLE_TRIGGERS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_SIMPROP_TRIGGERS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_CRON_TRIGGERS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_BLOB_TRIGGERS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_TRIGGERS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_JOB_DETAILS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_CALENDARS)); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(DELETE_ALL_PAUSED_TRIGGER_GRPS)); 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; try { ps = conn.prepareStatement(rtp(INSERT_JOB_DETAIL)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); ps.setString(3, job.getDescription()); ps.setString(4, job.getJobClass().getName()); setBoolean(ps, 5, job.isDurable()); setBoolean(ps, 6, job.isConcurrentExecutionDisallowed()); setBoolean(ps, 7, job.isPersistJobDataAfterExecution()); setBoolean(ps, 8, job.requestsRecovery()); setBytes(ps, 9, baos); insertResult = ps.executeUpdate(); } finally { closeStatement(ps); } 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; 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.isConcurrentExecutionDisallowed()); setBoolean(ps, 5, job.isPersistJobDataAfterExecution()); setBoolean(ps, 6, job.requestsRecovery()); setBytes(ps, 7, baos); ps.setString(8, job.getKey().getName()); ps.setString(9, job.getKey().getGroup()); insertResult = ps.executeUpdate(); } finally { closeStatement(ps); } return insertResult; } /** *

* Get the names of all of the triggers associated with the given job. *

* * @param conn * the DB Connection * @return an array of {@link org.quartz.utils.Key} objects */ public List selectTriggerKeysForJob(Connection conn, JobKey jobKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_JOB)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { String trigName = rs.getString(COL_TRIGGER_NAME); String trigGroup = rs.getString(COL_TRIGGER_GROUP); list.add(triggerKey(trigName, trigGroup)); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Delete the job detail record for the given job. *

* * @param conn * the DB Connection * @return the number of rows deleted */ public int deleteJobDetail(Connection conn, JobKey jobKey) throws SQLException { PreparedStatement ps = null; try { if (logger.isDebugEnabled()) { logger.debug("Deleting job: {}", jobKey); } ps = conn.prepareStatement(rtp(DELETE_JOB_DETAIL)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); return ps.executeUpdate(); } finally { closeStatement(ps); } } /** *

* Check whether or not the given job is stateful. *

* * @param conn * the DB Connection * @return true if the job exists and is stateful, false otherwise */ public boolean isJobNonConcurrent(Connection conn, JobKey jobKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_JOB_NONCONCURRENT)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); if (!rs.next()) { return false; } return getBoolean(rs, COL_IS_NONCONCURRENT); } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Check whether or not the given job exists. *

* * @param conn * the DB Connection * @return true if the job exists, false otherwise */ public boolean jobExists(Connection conn, JobKey jobKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_JOB_EXISTENCE)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); return rs.next(); } 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.getKey().getName()); ps.setString(3, job.getKey().getGroup()); return ps.executeUpdate(); } finally { closeStatement(ps); } } /** *

* Select the JobDetail object for a given job name / group name. *

* * @param conn * the DB Connection * @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, JobKey jobKey, ClassLoadHelper loadHelper) throws ClassNotFoundException, IOException, SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_JOB_DETAIL)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); JobDetailImpl job = null; if (rs.next()) { job = handleJobDetails(rs, loadHelper, true); } return job; } finally { closeResultSet(rs); closeStatement(ps); } } public List selectJobDetails(Connection conn, GroupMatcher matcher, ClassLoadHelper loadHelper) throws ClassNotFoundException, IOException, SQLException { PreparedStatement ps = null; ResultSet rs = null; try { if(isMatcherEquals(matcher)) { ps = conn.prepareStatement(rtp(SELECT_JOB_DETAILS)); ps.setString(1, toSqlEqualsClause(matcher)); } else { ps = conn.prepareStatement(rtp(SELECT_JOB_DETAILS_LIKE)); ps.setString(1, toSqlLikeClause(matcher)); } rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { JobDetailImpl job = handleJobDetails(rs, loadHelper, true); list.add(job); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } /** * build Map from java.util.Properties encoding. */ private Map getMapFromProperties(ResultSet rs, String columnPrefix) throws ClassNotFoundException, IOException, SQLException { Map map; String colName = COL_JOB_DATAMAP; if (columnPrefix != null && !columnPrefix.isEmpty()) { colName = columnPrefix + COL_JOB_DATAMAP; } try (InputStream is = (InputStream) getJobDataFromBlob(rs, colName)) { if (is == null) { return null; } Properties properties = new Properties(); properties.load(is); map = convertFromProperty(properties); } return map; } /** * build Map from java.util.Properties encoding. */ private Map getMapFromProperties(ResultSet rs) throws ClassNotFoundException, IOException, SQLException { return getMapFromProperties(rs, null); } /** *

* 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 List selectJobGroups(Connection conn) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_JOB_GROUPS)); rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { list.add(rs.getString(1)); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Select all of the jobs contained in a given group. *

* * @param conn * the DB Connection * @param matcher * the groupMatcher to evaluate the jobs against * @return an array of String job names */ public Set selectJobsInGroup(Connection conn, GroupMatcher matcher) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { if(isMatcherEquals(matcher)) { ps = conn.prepareStatement(rtp(SELECT_JOBS_IN_GROUP)); ps.setString(1, toSqlEqualsClause(matcher)); } else { ps = conn.prepareStatement(rtp(SELECT_JOBS_IN_GROUP_LIKE)); ps.setString(1, toSqlLikeClause(matcher)); } rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { list.add(jobKey(rs.getString(1), rs.getString(2))); } return new HashSet<>(list); } finally { closeResultSet(rs); closeStatement(ps); } } protected boolean isMatcherEquals(final GroupMatcher matcher) { return matcher.getCompareWithOperator().equals(StringMatcher.StringOperatorName.EQUALS); } protected String toSqlEqualsClause(final GroupMatcher matcher) { return matcher.getCompareToValue(); } protected String toSqlLikeClause(final GroupMatcher matcher) { String groupName; switch(matcher.getCompareWithOperator()) { case EQUALS: groupName = matcher.getCompareToValue(); break; case CONTAINS: groupName = "%" + matcher.getCompareToValue() + "%"; break; case ENDS_WITH: groupName = "%" + matcher.getCompareToValue(); break; case STARTS_WITH: groupName = matcher.getCompareToValue() + "%"; break; case ANYTHING: groupName = "%"; break; default: throw new UnsupportedOperationException("Don't know how to translate " + matcher.getCompareWithOperator() + " into SQL"); } return groupName; } //--------------------------------------------------------------------------- // 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, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { ByteArrayOutputStream baos = null; if(!trigger.getJobDataMap().isEmpty()) { baos = serializeJobData(trigger.getJobDataMap()); } PreparedStatement ps = null; int insertResult; try { ps = conn.prepareStatement(rtp(INSERT_TRIGGER)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setString(3, trigger.getJobKey().getName()); ps.setString(4, trigger.getJobKey().getGroup()); ps.setString(5, trigger.getDescription()); if(trigger.getNextFireTime() != null) ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger .getNextFireTime().getTime()))); else ps.setBigDecimal(6, null); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(7, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(8, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(9, type); ps.setBigDecimal(10, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(11, new BigDecimal(String.valueOf(endTime))); ps.setString(12, trigger.getCalendarName()); ps.setInt(13, trigger.getMisfireInstruction()); setBytes(ps, 14, baos); ps.setInt(15, trigger.getPriority()); insertResult = ps.executeUpdate(); if(tDel == null) insertBlobTrigger(conn, trigger); else tDel.insertExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeStatement(ps); } return insertResult; } /** *

* 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, OperableTrigger trigger) throws SQLException, IOException { PreparedStatement ps = null; ByteArrayOutputStream os; 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.getKey().getName()); ps.setString(2, trigger.getKey().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, OperableTrigger 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) { baos = serializeJobData(trigger.getJobDataMap()); } PreparedStatement ps = null; int insertResult; try { if(updateJobData) { ps = conn.prepareStatement(rtp(UPDATE_TRIGGER)); } else { ps = conn.prepareStatement(rtp(UPDATE_TRIGGER_SKIP_DATA)); } ps.setString(1, trigger.getJobKey().getName()); ps.setString(2, trigger.getJobKey().getGroup()); ps.setString(3, trigger.getDescription()); long nextFireTime = -1; if (trigger.getNextFireTime() != null) { nextFireTime = trigger.getNextFireTime().getTime(); } ps.setBigDecimal(4, new BigDecimal(String.valueOf(nextFireTime))); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(5, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(6, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(7, type); ps.setBigDecimal(8, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(9, new BigDecimal(String.valueOf(endTime))); ps.setString(10, trigger.getCalendarName()); ps.setInt(11, trigger.getMisfireInstruction()); ps.setInt(12, trigger.getPriority()); if(updateJobData) { setBytes(ps, 13, baos); ps.setString(14, trigger.getKey().getName()); ps.setString(15, trigger.getKey().getGroup()); } else { ps.setString(13, trigger.getKey().getName()); ps.setString(14, trigger.getKey().getGroup()); } insertResult = ps.executeUpdate(); if(tDel == null) updateBlobTrigger(conn, trigger); else tDel.updateExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeStatement(ps); } return insertResult; } /** *

* 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, OperableTrigger trigger) throws SQLException, IOException { PreparedStatement ps = null; try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { // update the blob 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.getKey().getName()); ps.setString(3, trigger.getKey().getGroup()); return ps.executeUpdate(); } finally { closeStatement(ps); } } /** *

* Check whether or not a trigger exists. *

* * @param conn * the DB Connection * @return true if the trigger exists, false otherwise */ public boolean triggerExists(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGER_EXISTENCE)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); rs = ps.executeQuery(); return rs.next(); } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Update the state for a given trigger. *

* * @param conn * the DB Connection * @param state * the new state for the trigger * @return the number of rows updated */ public int updateTriggerState(Connection conn, TriggerKey triggerKey, String state) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(UPDATE_TRIGGER_STATE)); ps.setString(1, state); ps.setString(2, triggerKey.getName()); ps.setString(3, triggerKey.getGroup()); 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 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, TriggerKey triggerKey, 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, triggerKey.getName()); ps.setString(3, triggerKey.getGroup()); ps.setString(4, oldState1); ps.setString(5, oldState2); ps.setString(6, oldState3); 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 matcher * the groupMatcher to evaluate the triggers against * @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, GroupMatcher matcher, 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, toSqlLikeClause(matcher)); 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 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, TriggerKey triggerKey, 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, triggerKey.getName()); ps.setString(3, triggerKey.getGroup()); 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 matcher * the groupMatcher to evaluate the triggers against * @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, GroupMatcher matcher, 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, toSqlLikeClause(matcher)); 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 state * the new state for the triggers * @return the number of rows updated */ public int updateTriggerStatesForJob(Connection conn, JobKey jobKey, String state) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(UPDATE_JOB_TRIGGER_STATES)); ps.setString(1, state); ps.setString(2, jobKey.getName()); ps.setString(3, jobKey.getGroup()); return ps.executeUpdate(); } finally { closeStatement(ps); } } public int updateTriggerStatesForJobFromOtherState(Connection conn, JobKey jobKey, 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, jobKey.getName()); ps.setString(3, jobKey.getGroup()); ps.setString(4, oldState); return ps.executeUpdate(); } finally { closeStatement(ps); } } /** *

* Delete the cron trigger data for a trigger. *

* * @param conn * the DB Connection * @return the number of rows deleted */ public int deleteBlobTrigger(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(DELETE_BLOB_TRIGGER)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); return ps.executeUpdate(); } finally { closeStatement(ps); } } /** *

* Delete the base trigger data for a trigger. *

* * @param conn * the DB Connection * @return the number of rows deleted */ public int deleteTrigger(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; deleteTriggerExtension(conn, triggerKey); try { ps = conn.prepareStatement(rtp(DELETE_TRIGGER)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); return ps.executeUpdate(); } finally { closeStatement(ps); } } protected void deleteTriggerExtension(Connection conn, TriggerKey triggerKey) throws SQLException { for(TriggerPersistenceDelegate tDel: triggerPersistenceDelegates) { if(tDel.deleteExtendedTriggerProperties(conn, triggerKey) > 0) { return; // as soon as one affects a row, we're done. } } deleteBlobTrigger(conn, triggerKey); } /** *

* Select the number of triggers associated with a given job. *

* * @param conn * the DB Connection * @return the number of triggers for the given job */ public int selectNumTriggersForJob(Connection conn, JobKey jobKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS_FOR_JOB)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); 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 * @return the {@link org.quartz.JobDetail} object * associated with the given trigger * @throws SQLException * @throws ClassNotFoundException */ public JobDetail selectJobForTrigger(Connection conn, ClassLoadHelper loadHelper, TriggerKey triggerKey) throws ClassNotFoundException, SQLException, IOException { return selectJobForTrigger(conn, loadHelper, triggerKey, true); } private JobDetailImpl handleJobDetails(ResultSet rs, ClassLoadHelper loadHelper, boolean loadJobClass) throws SQLException, ClassNotFoundException, IOException { JobDetailImpl job = new JobDetailImpl(); job.setName(rs.getString(COL_JOB_NAME)); job.setGroup(rs.getString(COL_JOB_GROUP)); if (containsColumnNames(rs, COL_DESCRIPTION)) { job.setDescription(rs.getString(COL_DESCRIPTION)); } job.setDurability(rs.getBoolean(COL_IS_DURABLE)); if (loadJobClass) { job.setJobClass(loadHelper.loadClass(rs.getString(COL_JOB_CLASS), Job.class)); } job.setRequestsRecovery(rs.getBoolean(COL_REQUESTS_RECOVERY)); if (containsColumnNames(rs, COL_JOB_DATAMAP) || containsColumnNames(rs, COL_JOB_DATAMAP)) { Map map = null; if (canUseProperties()) { if (containsColumnNames(rs, COL_JOB_DATAMAP)) { map = getMapFromProperties(rs); } } else { if (containsColumnNames(rs, COL_JOB_DATAMAP)) { map = (Map) getObjectFromBlob(rs, COL_JOB_DATAMAP); } } if (null != map) { job.setJobDataMap(new JobDataMap(map)); } } return job; } /** *

* Select the job to which the trigger is associated. Allow option to load actual job class or not. When case of * remove, we do not need to load the class, which in many cases, it's no longer exists. *

* * @param conn * the DB Connection * @return the {@link org.quartz.JobDetail} object * associated with the given trigger * @throws SQLException * @throws ClassNotFoundException */ public JobDetail selectJobForTrigger(Connection conn, ClassLoadHelper loadHelper, TriggerKey triggerKey, boolean loadJobClass) throws ClassNotFoundException, SQLException, IOException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_JOB_FOR_TRIGGER)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); rs = ps.executeQuery(); if (rs.next()) { return handleJobDetails(rs, loadHelper, loadJobClass); } else { if (logger.isDebugEnabled()) { logger.debug("No job for trigger '{}'.", triggerKey); } return null; } } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Select the triggers for a job *

* * @param conn * the DB Connection * @return an array of {@link org.quartz.Trigger} objects * associated with a given job. * @throws SQLException * @throws JobPersistenceException */ public List selectTriggersForJob(Connection conn, JobKey jobKey) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { if (useEnhancedStatements) { return selectBulkTriggersForJob(conn, jobKey); } LinkedList trigList = new LinkedList<>(); PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_JOB)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); while (rs.next()) { OperableTrigger t = selectTrigger(conn, triggerKey(rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP))); if(t != null) { trigList.add(t); } } } finally { closeResultSet(rs); closeStatement(ps); } return trigList; } protected List selectBulkTriggersForJob(Connection conn, JobKey jobKey) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { LinkedList trigList = new LinkedList<>(); PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_JOB_V2)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); while (rs.next()) { trigList.add(handleTriggerV2(rs, conn, triggerKey(rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP)))); } } finally { closeResultSet(rs); closeStatement(ps); } return trigList; } public List selectTriggersForCalendar(Connection conn, String calName) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { if(useEnhancedStatements) { return selectBulkTriggersForCalendar(conn, calName); } LinkedList trigList = new LinkedList<>(); 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, triggerKey(rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP)))); } } finally { closeResultSet(rs); closeStatement(ps); } return trigList; } protected List selectBulkTriggersForCalendar(Connection conn, String calName) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { LinkedList trigList = new LinkedList<>(); PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_CALENDAR_V2)); ps.setString(1, calName); rs = ps.executeQuery(); while (rs.next()) { trigList.add(handleTriggerV2(rs, conn, triggerKey(rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP)))); } } finally { closeResultSet(rs); closeStatement(ps); } return trigList; } public List getTriggersByJobAndTriggerGroup(Connection conn, GroupMatcher jobMatcher, GroupMatcher triggerMatcher) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { String baseStatement = SELECT_BULK_TRIGGERS_BASE; boolean hasMatcher = false; List columnSetters = new LinkedList<>(); if (jobMatcher == null) { throw new IllegalArgumentException("jobMatcher cannot be null"); } if (triggerMatcher == null) { throw new IllegalArgumentException("triggerMatcher cannot be null"); } if (jobMatcher.getCompareWithOperator() != StringMatcher.StringOperatorName.ANYTHING) { baseStatement += " WHERE " + "T." + COL_JOB_GROUP; hasMatcher = true; if (isMatcherEquals(jobMatcher)) { baseStatement += " = ?"; columnSetters.add(ps -> ps.setString(1, toSqlEqualsClause(jobMatcher))); } else { baseStatement += " LIKE ?"; columnSetters.add(ps -> ps.setString(1, toSqlLikeClause(jobMatcher))); } } if (triggerMatcher.getCompareWithOperator() != StringMatcher.StringOperatorName.ANYTHING) { final int columnIndex = hasMatcher ? 2 : 1; if (hasMatcher) { baseStatement += "\n AND "; } else { baseStatement += "\n WHERE "; } baseStatement += "T." + COL_TRIGGER_GROUP; if (isMatcherEquals(triggerMatcher)) { baseStatement += " = ?"; columnSetters.add(ps -> ps.setString(columnIndex, toSqlEqualsClause(triggerMatcher))); } else { baseStatement += " LIKE ?"; columnSetters.add(ps -> ps.setString(columnIndex, toSqlLikeClause(triggerMatcher))); } } LinkedList trigList = new LinkedList<>(); PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(baseStatement)); for (PreparedStatementConsumer setter : columnSetters) { setter.accept(ps); } rs = ps.executeQuery(); while (rs.next()) { trigList.add(handleTriggerV2(rs, conn, triggerKey(rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP)))); } } finally { closeResultSet(rs); closeStatement(ps); } return trigList; } private OperableTrigger handleTrigger(ResultSet rs, Connection conn, TriggerKey triggerKey, PreparedStatement ps) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { OperableTrigger trigger = null; String jobName = rs.getString(COL_JOB_NAME); String jobGroup = rs.getString(COL_JOB_GROUP); 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; 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); } if (triggerType.equals(TTYPE_BLOB)) { try (PreparedStatement pps = conn.prepareStatement(rtp(SELECT_BLOB_TRIGGER))) { pps.setString(1, triggerKey.getName()); pps.setString(2, triggerKey.getGroup()); ResultSet rss = pps.executeQuery(); if (rss.next()) { trigger = (OperableTrigger) getObjectFromBlob(rs, COL_BLOB); } } } else { TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(triggerType); if(tDel == null){ throw new JobPersistenceException("No TriggerPersistenceDelegate for trigger discriminator type: " + triggerType); } TriggerPropertyBundle triggerProps; try { triggerProps = tDel.loadExtendedTriggerProperties(conn, triggerKey); } catch (IllegalStateException isex) { if (isTriggerStillPresent(ps)) { throw isex; } else { // QTZ-386 Trigger has been deleted return null; } } TriggerBuilder tb = newTrigger() .withDescription(description) .withPriority(priority) .startAt(startTimeD) .endAt(endTimeD) .withIdentity(triggerKey) .modifiedByCalendar(calendarName) .withSchedule(triggerProps.getScheduleBuilder()) .forJob(jobKey(jobName, jobGroup)); if (null != map) { tb.usingJobData(new JobDataMap(map)); } trigger = (OperableTrigger) tb.build(); trigger.setMisfireInstruction(misFireInstr); trigger.setNextFireTime(nft); trigger.setPreviousFireTime(pft); setTriggerStateProperties(trigger, triggerProps); } return trigger; } private OperableTrigger handleTriggerV2(ResultSet rs, Connection conn, TriggerKey triggerKey) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { OperableTrigger trigger = null; String jobName = rs.getString(COL_JOB_NAME); String jobGroup = rs.getString(COL_JOB_GROUP); 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; 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); } if (triggerType.equals(TTYPE_BLOB)) { if (containsColumnNames(rs, COL_JOB_DATAMAP)) { trigger = (OperableTrigger) getObjectFromBlob(rs, COL_BLOB); } } else { TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(triggerType); if(tDel == null) { throw new JobPersistenceException("No TriggerPersistenceDelegate for trigger discriminator type: " + triggerType); } TriggerPropertyBundle triggerProps; if (tDel.hasInlinedResultSetProperties()) { triggerProps = tDel.loadExtendedTriggerPropertiesFromResultSet(rs, triggerKey); } else { //this is the old way, in case we have to load the trigger properties from the database triggerProps = tDel.loadExtendedTriggerProperties(conn, triggerKey); } TriggerBuilder tb = newTrigger() .withDescription(description) .withPriority(priority) .startAt(startTimeD) .endAt(endTimeD) .withIdentity(triggerKey) .modifiedByCalendar(calendarName) .withSchedule(triggerProps.getScheduleBuilder()) .forJob(jobKey(jobName, jobGroup)); if (null != map) { tb.usingJobData(new JobDataMap(map)); } trigger = (OperableTrigger) tb.build(); trigger.setMisfireInstruction(misFireInstr); trigger.setNextFireTime(nft); trigger.setPreviousFireTime(pft); setTriggerStateProperties(trigger, triggerProps); } return trigger; } /** *

* Select a trigger. *

* * @param conn * the DB Connection * @return the {@link org.quartz.Trigger} object * @throws JobPersistenceException */ public OperableTrigger selectTrigger(Connection conn, TriggerKey triggerKey) throws SQLException, ClassNotFoundException, IOException, JobPersistenceException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGER)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); rs = ps.executeQuery(); if (rs.next()) { return handleTrigger(rs, conn, triggerKey, ps); } return null; } catch (NoRecordFoundException nrfe) { if (isTriggerStillPresent(ps)) { throw nrfe; } else { // QTZ-386 Trigger has been deleted return null; } } catch (IOException ioex) { throw new JobPersistenceException("Error serializing trigger: " + triggerKey, ioex); } finally { closeResultSet(rs); closeStatement(ps); } } private boolean isTriggerStillPresent(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { rs = ps.executeQuery(); return rs.next(); } finally { closeResultSet(rs); } } private void setTriggerStateProperties(OperableTrigger trigger, TriggerPropertyBundle props) throws JobPersistenceException { if(props.getStatePropertyNames() == null) return; Util.setBeanProps(trigger, props.getStatePropertyNames(), props.getStatePropertyValues()); } /** *

* 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 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 { ps = conn.prepareStatement(rtp(SELECT_TRIGGER_DATA)); ps.setString(1, triggerName); ps.setString(2, groupName); rs = ps.executeQuery(); if (rs.next()) { Map map; 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 * @return the {@link org.quartz.Trigger} object */ public String selectTriggerState(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { String state; ps = conn.prepareStatement(rtp(SELECT_TRIGGER_STATE)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); 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 and next fire time). *

* * @param conn * the DB Connection * @return a TriggerStatus object, or null */ public TriggerStatus selectTriggerStatus(Connection conn, TriggerKey triggerKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { TriggerStatus status = null; ps = conn.prepareStatement(rtp(SELECT_TRIGGER_STATUS)); ps.setString(1, triggerKey.getName()); ps.setString(2, triggerKey.getGroup()); 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(triggerKey); status.setJobKey(jobKey(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 List selectTriggerGroups(Connection conn) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGER_GROUPS)); rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { list.add(rs.getString(1)); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } public List selectTriggerGroups(Connection conn, GroupMatcher matcher) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_TRIGGER_GROUPS_FILTERED)); ps.setString(1, toSqlLikeClause(matcher)); rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { list.add(rs.getString(1)); } return list; } finally { closeResultSet(rs); closeStatement(ps); } } /** *

* Select all of the triggers contained in a given group. *

* * @param conn * the DB Connection * @param matcher * to evaluate against known triggers * @return a Set of TriggerKeys */ public Set selectTriggersInGroup(Connection conn, GroupMatcher matcher) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { if(isMatcherEquals(matcher)) { ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_IN_GROUP)); ps.setString(1, toSqlEqualsClause(matcher)); } else { ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_IN_GROUP_LIKE)); ps.setString(1, toSqlLikeClause(matcher)); } rs = ps.executeQuery(); Set keys = new HashSet<>(); while (rs.next()) { keys.add(triggerKey(rs.getString(1), rs.getString(2))); } return keys; } 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); return ps.executeUpdate(); } 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); return ps.executeUpdate(); } finally { closeStatement(ps); } } public int deletePausedTriggerGroup(Connection conn, GroupMatcher matcher) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(DELETE_PAUSED_TRIGGER_GROUP)); ps.setString(1, toSqlLikeClause(matcher)); return ps.executeUpdate(); } finally { closeStatement(ps); } } public int deleteAllPausedTriggerGroups(Connection conn) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(DELETE_PAUSED_TRIGGER_GROUPS)); return ps.executeUpdate(); } 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(); return rs.next(); } 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(); return rs.next(); } 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 List selectCalendars(Connection conn) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_CALENDARS)); rs = ps.executeQuery(); LinkedList list = new LinkedList<>(); while (rs.next()) { list.add(rs.getString(1)); } return list; } 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. */ @Deprecated 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 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 TriggerKey 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 TriggerKey(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. * * @deprecated - This remained for compatibility reason. Use {@link #selectTriggerToAcquire(Connection, long, long, int)} instead. */ @Deprecated public List selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan) throws SQLException { // This old API used to always return 1 trigger. return selectTriggerToAcquire(conn, noLaterThan, noEarlierThan, 1); } /** *

* 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) * @param maxCount * maximum number of trigger keys allow to acquired in the returning list. * * @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, int maxCount) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; List nextTriggers = new LinkedList<>(); try { ps = conn.prepareStatement(rtp(SELECT_NEXT_TRIGGER_TO_ACQUIRE)); // Set max rows to retrieve if (maxCount < 1) maxCount = 1; // we want at least one trigger back. ps.setMaxRows(maxCount); // Try to give jdbc driver a hint to hopefully not pull over more than the few rows we actually need. // Note: in some jdbc drivers, such as MySQL, you must set maxRows before fetchSize, or you get exception! ps.setFetchSize(maxCount); 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() < maxCount) { nextTriggers.add(triggerKey( 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, OperableTrigger 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.getKey().getName()); ps.setString(3, trigger.getKey().getGroup()); ps.setString(4, instanceId); ps.setBigDecimal(5, new BigDecimal(String.valueOf(System.currentTimeMillis()))); ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger.getNextFireTime().getTime()))); ps.setString(7, state); if (job != null) { ps.setString(8, trigger.getJobKey().getName()); ps.setString(9, trigger.getJobKey().getGroup()); setBoolean(ps, 10, job.isConcurrentExecutionDisallowed()); 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); } } /** *

* Update 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 updateFiredTrigger(Connection conn, OperableTrigger trigger, String state, JobDetail job) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(UPDATE_FIRED_TRIGGER)); ps.setString(1, instanceId); ps.setBigDecimal(2, new BigDecimal(String.valueOf(System.currentTimeMillis()))); ps.setBigDecimal(3, new BigDecimal(String.valueOf(trigger.getNextFireTime().getTime()))); ps.setString(4, state); if (job != null) { ps.setString(5, trigger.getJobKey().getName()); ps.setString(6, trigger.getJobKey().getGroup()); setBoolean(ps, 7, job.isConcurrentExecutionDisallowed()); setBoolean(ps, 8, job.requestsRecovery()); } else { ps.setString(5, null); ps.setString(6, null); setBoolean(ps, 7, false); setBoolean(ps, 8, false); } ps.setString(9, trigger.getFireInstanceId()); 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.setScheduleTimestamp(rs.getLong(COL_SCHED_TIME)); rec.setPriority(rs.getInt(COL_PRIORITY)); rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); rec.setTriggerKey(triggerKey(rs.getString(COL_TRIGGER_NAME), rs .getString(COL_TRIGGER_GROUP))); if (!rec.getFireInstanceState().equals(STATE_ACQUIRED)) { rec.setJobDisallowsConcurrentExecution(getBoolean(rs, COL_IS_NONCONCURRENT)); rec.setJobRequestsRecovery(rs .getBoolean(COL_REQUESTS_RECOVERY)); rec.setJobKey(jobKey(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.setScheduleTimestamp(rs.getLong(COL_SCHED_TIME)); rec.setPriority(rs.getInt(COL_PRIORITY)); rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); rec.setTriggerKey(triggerKey(rs.getString(COL_TRIGGER_NAME), rs .getString(COL_TRIGGER_GROUP))); if (!rec.getFireInstanceState().equals(STATE_ACQUIRED)) { rec.setJobDisallowsConcurrentExecution(getBoolean(rs, COL_IS_NONCONCURRENT)); rec.setJobRequestsRecovery(rs .getBoolean(COL_REQUESTS_RECOVERY)); rec.setJobKey(jobKey(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.setScheduleTimestamp(rs.getLong(COL_SCHED_TIME)); rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); rec.setTriggerKey(triggerKey(rs.getString(COL_TRIGGER_NAME), rs .getString(COL_TRIGGER_GROUP))); if (!rec.getFireInstanceState().equals(STATE_ACQUIRED)) { rec.setJobDisallowsConcurrentExecution(getBoolean(rs, COL_IS_NONCONCURRENT)); rec.setJobRequestsRecovery(rs .getBoolean(COL_REQUESTS_RECOVERY)); rec.setJobKey(jobKey(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, JobKey jobKey) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_JOB_EXECUTION_COUNT)); ps.setString(1, jobKey.getName()); ps.setString(2, jobKey.getGroup()); rs = ps.executeQuery(); return (rs.next()) ? rs.getInt(1) : 0; } finally { closeResultSet(rs); closeStatement(ps); } } public int insertSchedulerState(Connection conn, String theInstanceId, long checkInTime, long interval) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(INSERT_SCHEDULER_STATE)); ps.setString(1, theInstanceId); ps.setLong(2, checkInTime); ps.setLong(3, interval); return ps.executeUpdate(); } finally { closeStatement(ps); } } public int deleteSchedulerState(Connection conn, String theInstanceId) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(DELETE_SCHEDULER_STATE)); ps.setString(1, theInstanceId); return ps.executeUpdate(); } finally { closeStatement(ps); } } public int updateSchedulerState(Connection conn, String theInstanceId, long checkInTime) throws SQLException { PreparedStatement ps = null; try { ps = conn.prepareStatement(rtp(UPDATE_SCHEDULER_STATE)); ps.setLong(1, checkInTime); ps.setString(2, theInstanceId); return ps.executeUpdate(); } finally { closeStatement(ps); } } public List selectSchedulerStateRecords(Connection conn, String theInstanceId) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { List lst = new LinkedList<>(); if (theInstanceId != null) { ps = conn.prepareStatement(rtp(SELECT_SCHEDULER_STATE)); ps.setString(1, theInstanceId); } 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 unsubstituted query * @return the query, with proper table prefix substituted */ protected final String rtp(String query) { return Util.rtp(query, tablePrefix, getSchedulerNameLiteral()); } private String schedNameLiteral = null; protected String getSchedulerNameLiteral() { if(schedNameLiteral == null) schedNameLiteral = "'" + schedName + "'"; return schedNameLiteral; } /** *

* 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 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 (Map.Entry value : data.entrySet()) { try { serializeObject(value.getValue()).close(); } catch (IOException e) { return value.getKey(); } } // 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) { return new HashMap<>(properties); } /** * convert the JobDataMap into a list of properties */ protected Properties convertToProperty(Map data) throws IOException { Properties properties = new Properties(); for (Map.Entry value : data.entrySet()) { Object key = value.getKey(); Object val = (value.getValue() == null) ? "" : value.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 { try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } } } 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 getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { Blob blobLocator = rs.getBlob(colName); if (blobLocator != null) { return blobLocator.getBinaryStream(); } else { return null; } } return getObjectFromBlob(rs, colName); } /** * @see 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 static 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 static 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()); } @Override public boolean isUsingEnhancedStatements() { return this.useEnhancedStatements; } @Override public void setUseEnhancedStatements(boolean useEnhancedStatements) { this.useEnhancedStatements = useEnhancedStatements; } @FunctionalInterface protected interface PreparedStatementConsumer { void accept(PreparedStatement ps) throws SQLException; } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/StdRowLockSemaphore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_LOCK_NAME + " = ? FOR UPDATE"; public static final String INSERT_LOCK = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_LOCKS + "(" + COL_SCHEDULER_NAME + ", " + COL_LOCK_NAME + ") VALUES (" + SCHED_NAME_SUBST + ", ?)"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public StdRowLockSemaphore() { super(DEFAULT_TABLE_PREFIX, null, SELECT_FOR_LOCK, INSERT_LOCK); } public StdRowLockSemaphore(String tablePrefix, String schedName, String selectWithLockSQL) { super(tablePrefix, schedName, selectWithLockSQL != null ? selectWithLockSQL : SELECT_FOR_LOCK, INSERT_LOCK); } // Data Members // Configurable lock retry parameters private int maxRetry = 3; private long retryPeriod = 1000L; public void setMaxRetry(int maxRetry) { this.maxRetry = maxRetry; } public void setRetryPeriod(long retryPeriod) { this.retryPeriod = retryPeriod; } public int getMaxRetry() { return maxRetry; } public long getRetryPeriod() { return retryPeriod; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Execute the SQL select for update that will lock the proper database row. */ @Override protected void executeSQL(Connection conn, final String lockName, final String expandedSQL, final String expandedInsertSQL) throws LockException { PreparedStatement ps = null; ResultSet rs = null; SQLException initCause = null; // attempt lock two times (to work-around possible race conditions in inserting the lock row the first time running) int count = 0; // Configurable lock retry attempts int maxRetryLocal = this.maxRetry; long retryPeriodLocal = this.retryPeriod; do { count++; try { ps = conn.prepareStatement(expandedSQL); ps.setString(1, lockName); if (getLog().isDebugEnabled()) { getLog().debug("Lock '{}' is being obtained: {}", lockName, Thread.currentThread().getName()); } rs = ps.executeQuery(); if (!rs.next()) { getLog().debug("Inserting new lock row for lock: '{}' being obtained by thread: {}", lockName, Thread.currentThread().getName()); rs.close(); rs = null; ps.close(); ps = null; ps = conn.prepareStatement(expandedInsertSQL); ps.setString(1, lockName); int res = ps.executeUpdate(); if(res != 1) { if(count < maxRetryLocal) { // pause a bit to give another thread some time to commit the insert of the new lock row try { Thread.sleep(retryPeriodLocal); } catch (InterruptedException ignore) { Thread.currentThread().interrupt(); } // try again ... continue; } throw new SQLException(Util.rtp( "No row exists, and one could not be inserted in table " + TABLE_PREFIX_SUBST + TABLE_LOCKS + " for lock named: " + lockName, getTablePrefix(), getSchedulerNameLiteral())); } } return; // obtained lock, go } catch (SQLException sqle) { //Exception src = // (Exception)getThreadLocksObtainer().get(lockName); //if(src != null) // src.printStackTrace(); //else // System.err.println("--- ***************** NO OBTAINER!"); if(initCause == null) initCause = sqle; if (getLog().isDebugEnabled()) { getLog().debug("Lock '{}' was not obtained by: {}{}", lockName, Thread.currentThread().getName(), count < maxRetryLocal ? " - will try again." : ""); } if(count < maxRetryLocal) { try { conn.rollback(); } catch (SQLException e) { getLog().error("Couldn't rollback jdbc connection. {}", e.getMessage(), e); } // pause a bit to give another thread some time to commit the insert of the new lock row try { Thread.sleep(retryPeriodLocal); } catch (InterruptedException ignore) { Thread.currentThread().interrupt(); } // try again ... continue; } 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) { } } } } while(count < (maxRetryLocal + 1)); throw new LockException("Failure obtaining db row lock, reached maximum number of attempts. Initial exception (if any) attached as root cause.", initCause); } protected String getSelectWithLockSQL() { return getSQL(); } public void setSelectWithLockSQL(String selectWithLockSQL) { setSQL(selectWithLockSQL); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/SybaseDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** *

* This is a driver delegate for the Sybase database. *

* * @author Jeffrey Wescott * @author jhouse * @author Ray Case */ public class SybaseDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override 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; try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { return rs.getBinaryStream(colName); } return getObjectFromBlob(rs, colName); } /** * 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. */ @Override protected void setBytes(PreparedStatement ps, int index, ByteArrayOutputStream baos) throws SQLException { ps.setBytes(index, (baos == null) ? null: baos.toByteArray()); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/TablePrefixAware.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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); void setSchedName(String schedName); } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/TriggerPersistenceDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import org.quartz.JobDetail; import org.quartz.ScheduleBuilder; import org.quartz.TriggerKey; import org.quartz.spi.OperableTrigger; /** * An interface which provides an implementation for storing a particular * type of Trigger's extended properties. * * @author jhouse */ public interface TriggerPersistenceDelegate { void initialize(String tablePrefix, String schedulerName); boolean canHandleTriggerType(OperableTrigger trigger); String getHandledTriggerTypeDiscriminator(); int insertExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException; int updateExtendedTriggerProperties(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException; int deleteExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException; TriggerPropertyBundle loadExtendedTriggerProperties(Connection conn, TriggerKey triggerKey) throws SQLException; default TriggerPropertyBundle loadExtendedTriggerPropertiesFromResultSet(ResultSet resultSet, TriggerKey triggerKey) throws SQLException { throw new UnsupportedOperationException("This delegate does not support loading from ResultSet"); } default boolean hasInlinedResultSetProperties() { return false; } class TriggerPropertyBundle { private final ScheduleBuilder sb; private final String[] statePropertyNames; private final Object[] statePropertyValues; public TriggerPropertyBundle(ScheduleBuilder sb, String[] statePropertyNames, Object[] statePropertyValues) { this.sb = sb; this.statePropertyNames = statePropertyNames; this.statePropertyValues = statePropertyValues; } public ScheduleBuilder getScheduleBuilder() { return sb; } public String[] getStatePropertyNames() { return statePropertyNames; } public Object[] getStatePropertyValues() { return statePropertyValues; } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/TriggerStatus.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.util.Date; import org.quartz.JobKey; import org.quartz.TriggerKey; /** *

* Object representing a job or trigger key. *

* * @author James House */ public class TriggerStatus { // FUTURE_TODO: Repackage under spi or root pkg ?, put status constants here. /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private TriggerKey key; private JobKey jobKey; private final String status; private final Date nextFireTime; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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) { this.status = status; this.nextFireTime = nextFireTime; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public JobKey getJobKey() { return jobKey; } public void setJobKey(JobKey jobKey) { this.jobKey = jobKey; } public TriggerKey getKey() { return key; } public void setKey(TriggerKey key) { this.key = key; } /** *

* Get the name portion of the key. *

* * @return the name */ public String getStatus() { return status; } /** *

* Get the group portion of the key. *

* * @return the group */ public Date getNextFireTime() { return nextFireTime; } /** *

* Return the string representation of the TriggerStatus. *

* */ @Override public String toString() { return "status: " + getStatus() + ", next Fire = " + getNextFireTime(); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/UpdateLockRowSemaphore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_LOCK_NAME + " = ? "; public static final String INSERT_LOCK = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_LOCKS + "(" + COL_SCHEDULER_NAME + ", " + COL_LOCK_NAME + ") VALUES (" + SCHED_NAME_SUBST + ", ?)"; private static final int RETRY_COUNT = 2; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public UpdateLockRowSemaphore() { super(DEFAULT_TABLE_PREFIX, null, UPDATE_FOR_LOCK, INSERT_LOCK); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Execute the SQL select for update that will lock the proper database row. */ @Override protected void executeSQL(Connection conn, final String lockName, final String expandedSQL, final String expandedInsertSQL) throws LockException { SQLException lastFailure = null; for (int i = 0; i < RETRY_COUNT; i++) { try { if (!lockViaUpdate(conn, lockName, expandedSQL)) { lockViaInsert(conn, lockName, expandedInsertSQL); } return; } catch (SQLException e) { lastFailure = e; if ((i + 1) == RETRY_COUNT) { getLog().debug("Lock '{}' was not obtained by: {}", lockName, Thread.currentThread().getName()); } else { getLog().debug("Lock '{}' was not obtained by: {} - will try again.", lockName, Thread.currentThread().getName()); } try { Thread.sleep(1000L); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } } throw new LockException("Failure obtaining db row lock: " + lastFailure.getMessage(), lastFailure); } protected String getUpdateLockRowSQL() { return getSQL(); } public void setUpdateLockRowSQL(String updateLockRowSQL) { setSQL(updateLockRowSQL); } private boolean lockViaUpdate(Connection conn, String lockName, String sql) throws SQLException { try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, lockName); getLog().debug("Lock '{}' is being obtained: {}", lockName, Thread.currentThread().getName()); return ps.executeUpdate() >= 1; } } private void lockViaInsert(Connection conn, String lockName, String sql) throws SQLException { getLog().debug("Inserting new lock row for lock: '{}' being obtained by thread: {}", lockName, Thread.currentThread().getName()); try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, lockName); if (ps.executeUpdate() != 1) { throw new SQLException(Util.rtp( "No row exists, and one could not be inserted in table " + TABLE_PREFIX_SUBST + TABLE_LOCKS + " for lock named: " + lockName, getTablePrefix(), getSchedulerNameLiteral())); } } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/Util.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.quartz.JobPersistenceException; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; import java.util.Arrays; import java.util.Locale; /** *

* 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 unsubstituted query * @param tablePrefix * the table prefix * @return the query, with proper table prefix substituted */ public static String rtp(String query, String tablePrefix, String schedNameLiteral) { return MessageFormat.format(query, new Object[]{tablePrefix, schedNameLiteral}); } /** *

* 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(); } /** * Cleanup helper method that closes the given ResultSet * while ignoring any errors. */ public static 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. */ public static void closeStatement(Statement statement) { if (null != statement) { try { statement.close(); } catch (SQLException ignore) { } } } public static void setBeanProps(Object obj, String[] propNames, Object[] propValues) throws JobPersistenceException { if(propNames == null || propNames.length == 0) return; if(propNames.length != propValues.length) throw new IllegalArgumentException("propNames[].length != propValues[].length"); String name = null; try { BeanInfo bi = Introspector.getBeanInfo(obj.getClass()); PropertyDescriptor[] propDescs = bi.getPropertyDescriptors(); for(int i=0; i < propNames.length; i++) { name = propNames[i]; String c = name.substring(0, 1).toUpperCase(Locale.US); String methName = "set" + c + name.substring(1); java.lang.reflect.Method setMeth = getSetMethod(methName, propDescs); 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 + "'"); } setMeth.invoke(obj, new Object[]{ propValues[i] }); } } catch(Exception e) { throw new JobPersistenceException( "Unable to set property named: " + name +" of object of type: " + obj.getClass().getCanonicalName(), e); } } private static java.lang.reflect.Method getSetMethod(String name, PropertyDescriptor[] props) { for (PropertyDescriptor prop : props) { java.lang.reflect.Method wMeth = prop.getWriteMethod(); if (wMeth != null && wMeth.getName().equals(name)) { return wMeth; } } return null; } static boolean containsColumnNames(ResultSet rs, String... colNames) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); for (int i = 1; i <= columnCount; i++ ) { String name = rsmd.getColumnName(i); for (String colName : colNames) { if (colName.equalsIgnoreCase(name)) { return true; } } // Do stuff with name } return false; } static String getString(ResultSet resultSet, String... columnNames) throws SQLException { for (String columnName : columnNames) { if (containsColumnNames(resultSet, columnName)) { return resultSet.getString(columnName); } } throw new SQLException("Missing columns in result set: " + Arrays.toString(columnNames)); } static boolean getBoolean(ResultSet resultSet, String... columnNames) throws SQLException { for (String columnName : columnNames) { if (containsColumnNames(resultSet, columnName)) { return resultSet.getBoolean(columnName); } } throw new SQLException("Missing columns in result set: " + Arrays.toString(columnNames)); } /** * Checks if all columns are null in the result set. * @param resultSet the result set * @param columnNames columns names to check * @return true if all columns are null, false otherwise * @throws SQLException */ static boolean areNull(ResultSet resultSet, String... columnNames) throws SQLException { //TODO: check if resultSet is closed? for (String columnName : columnNames) { if (containsColumnNames(resultSet, columnName)) { if (resultSet.getObject(columnName) != null) { return false; } } } return true; } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/WebLogicDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.sql.Blob; import java.sql.ResultSet; import java.sql.SQLException; /** *

* This is a driver delegate for the WebLogic JDBC driver. *

* * @see org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate * @author Jeffrey Wescott */ public class WebLogicDelegate extends StdJDBCDelegate { //--------------------------------------------------------------------------- // 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 */ @Override protected Object getObjectFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { Object obj = null; Blob blobLocator = rs.getBlob(colName); InputStream binaryInput = null; try { if (null != blobLocator && blobLocator.length() > 0) { binaryInput = blobLocator.getBinaryStream(); } } catch (Exception ignore) { } if (null != binaryInput) { try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } return obj; } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { Blob blobLocator = rs.getBlob(colName); InputStream binaryInput = null; try { if (null != blobLocator && blobLocator.length() > 0) { binaryInput = blobLocator.getBinaryStream(); } } catch (Exception ignore) { } return binaryInput; } return getObjectFromBlob(rs, colName); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/oracle/OracleDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore.oracle; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; 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 org.quartz.Calendar; import org.quartz.JobDetail; import org.quartz.impl.jdbcjobstore.StdJDBCDelegate; import org.quartz.impl.jdbcjobstore.TriggerPersistenceDelegate; import org.quartz.spi.OperableTrigger; /** *

* This is a driver delegate for the Oracle 10 and 11 database. *

* * @see org.quartz.impl.jdbcjobstore.WebLogicDelegate * @see org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate * @author James House * @author Patrick Lightbody * @author Eric Mueller */ public class OracleDelegate extends StdJDBCDelegate { public static final String INSERT_ORACLE_JOB_DETAIL = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " (" + COL_SCHEDULER_NAME + ", " + COL_JOB_NAME + ", " + COL_JOB_GROUP + ", " + COL_DESCRIPTION + ", " + COL_JOB_CLASS + ", " + COL_IS_DURABLE + ", " + COL_IS_NONCONCURRENT + ", " + COL_IS_UPDATE_DATA + ", " + COL_REQUESTS_RECOVERY + ", " + COL_JOB_DATAMAP + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, ?, ?, ?, ?, ?, ?, ?, EMPTY_BLOB())"; public static final String UPDATE_ORACLE_JOB_DETAIL = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " SET " + COL_DESCRIPTION + " = ?, " + COL_JOB_CLASS + " = ?, " + COL_IS_DURABLE + " = ?, " + COL_IS_NONCONCURRENT + " = ?, " + COL_IS_UPDATE_DATA + " = ?, " + COL_REQUESTS_RECOVERY + " = ?, " + COL_JOB_DATAMAP + " = EMPTY_BLOB() " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; public static final String UPDATE_ORACLE_JOB_DETAIL_BLOB = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " SET " + COL_JOB_DATAMAP + " = ? " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; public static final String SELECT_ORACLE_JOB_DETAIL_BLOB = "SELECT " + COL_JOB_DATAMAP + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ? FOR UPDATE"; public static final String UPDATE_ORACLE_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_JOB_NAME + " = ?, " + COL_JOB_GROUP + " = ?, " + 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_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; public static final String SELECT_ORACLE_TRIGGER_JOB_DETAIL_BLOB = "SELECT " + COL_JOB_DATAMAP + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ? FOR UPDATE"; public static final String UPDATE_ORACLE_TRIGGER_JOB_DETAIL_BLOB = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_JOB_DATAMAP + " = ? " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; public static final String UPDATE_ORACLE_TRIGGER_JOB_DETAIL_EMPTY_BLOB = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_JOB_DATAMAP + " = EMPTY_BLOB() " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; public static final String INSERT_ORACLE_CALENDAR = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " (" + COL_SCHEDULER_NAME + ", " + COL_CALENDAR_NAME + ", " + COL_CALENDAR + ") " + " VALUES(" + SCHED_NAME_SUBST + ", ?, EMPTY_BLOB())"; public static final String SELECT_ORACLE_CALENDAR_BLOB = "SELECT " + COL_CALENDAR + " FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ? FOR UPDATE"; public static final String UPDATE_ORACLE_CALENDAR_BLOB = "UPDATE " + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " SET " + COL_CALENDAR + " = ? " + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_CALENDAR_NAME + " = ?"; //--------------------------------------------------------------------------- // protected methods that can be overridden by subclasses //--------------------------------------------------------------------------- @Override protected Object getObjectFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { Object obj = null; InputStream binaryInput = rs.getBinaryStream(colName); if (binaryInput != null) { try (ObjectInputStream in = new ObjectInputStream(binaryInput)) { obj = in.readObject(); } } return obj; } @Override public int insertJobDetail(Connection conn, JobDetail job) throws IOException, SQLException { ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); byte[] data = baos.toByteArray(); PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(INSERT_ORACLE_JOB_DETAIL)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); ps.setString(3, job.getDescription()); ps.setString(4, job.getJobClass().getName()); setBoolean(ps, 5, job.isDurable()); setBoolean(ps, 6, job.isConcurrentExecutionDisallowed()); setBoolean(ps, 7, job.isPersistJobDataAfterExecution()); setBoolean(ps, 8, job.requestsRecovery()); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(SELECT_ORACLE_JOB_DETAIL_BLOB)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); rs = ps.executeQuery(); int res = 0; Blob dbBlob; if (rs.next()) { dbBlob = writeDataToBlob(rs, 1, data); } else { return res; } rs.close(); ps.close(); ps = conn.prepareStatement(rtp(UPDATE_ORACLE_JOB_DETAIL_BLOB)); ps.setBlob(1, dbBlob); ps.setString(2, job.getKey().getName()); ps.setString(3, job.getKey().getGroup()); res = ps.executeUpdate(); return res; } finally { closeResultSet(rs); closeStatement(ps); } } @Override protected Object getJobDataFromBlob(ResultSet rs, String colName) throws ClassNotFoundException, IOException, SQLException { if (canUseProperties()) { return rs.getBinaryStream(colName); } return getObjectFromBlob(rs, colName); } @Override public int updateJobDetail(Connection conn, JobDetail job) throws IOException, SQLException { ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); byte[] data = baos.toByteArray(); PreparedStatement ps = null; PreparedStatement ps2 = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(UPDATE_ORACLE_JOB_DETAIL)); ps.setString(1, job.getDescription()); ps.setString(2, job.getJobClass().getName()); setBoolean(ps, 3, job.isDurable()); setBoolean(ps, 4, job.isConcurrentExecutionDisallowed()); setBoolean(ps, 5, job.isPersistJobDataAfterExecution()); setBoolean(ps, 6, job.requestsRecovery()); ps.setString(7, job.getKey().getName()); ps.setString(8, job.getKey().getGroup()); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(SELECT_ORACLE_JOB_DETAIL_BLOB)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); rs = ps.executeQuery(); int res = 0; if (rs.next()) { Blob dbBlob = writeDataToBlob(rs, 1, data); ps2 = conn.prepareStatement(rtp(UPDATE_ORACLE_JOB_DETAIL_BLOB)); ps2.setBlob(1, dbBlob); ps2.setString(2, job.getKey().getName()); ps2.setString(3, job.getKey().getGroup()); res = ps2.executeUpdate(); } return res; } finally { closeResultSet(rs); closeStatement(ps); closeStatement(ps2); } } @Override public int insertTrigger(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { byte[] data = null; if (!trigger.getJobDataMap().isEmpty()) { data = serializeJobData(trigger.getJobDataMap()).toByteArray(); } PreparedStatement ps = null; ResultSet rs = null; int insertResult; try { ps = conn.prepareStatement(rtp(INSERT_TRIGGER)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setString(3, trigger.getJobKey().getName()); ps.setString(4, trigger.getJobKey().getGroup()); ps.setString(5, trigger.getDescription()); ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger .getNextFireTime().getTime()))); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(7, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(8, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(9, type); ps.setBigDecimal(10, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(11, new BigDecimal(String.valueOf(endTime))); ps.setString(12, trigger.getCalendarName()); ps.setInt(13, trigger.getMisfireInstruction()); ps.setBinaryStream(14, null, 0); ps.setInt(15, trigger.getPriority()); insertResult = ps.executeUpdate(); if(data != null) { ps.close(); ps = conn .prepareStatement(rtp(UPDATE_ORACLE_TRIGGER_JOB_DETAIL_EMPTY_BLOB)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(SELECT_ORACLE_TRIGGER_JOB_DETAIL_BLOB)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); rs = ps.executeQuery(); Blob dbBlob; if (rs.next()) { dbBlob = writeDataToBlob(rs, 1, data); } else { return 0; } rs.close(); ps.close(); ps = conn.prepareStatement(rtp(UPDATE_ORACLE_TRIGGER_JOB_DETAIL_BLOB)); ps.setBlob(1, dbBlob); ps.setString(2, trigger.getKey().getName()); ps.setString(3, trigger.getKey().getGroup()); ps.executeUpdate(); } if(tDel == null) insertBlobTrigger(conn, trigger); else tDel.insertExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeResultSet(rs); closeStatement(ps); } return insertResult; } @Override public int updateTrigger(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { // save some clock cycles by unnecessarily writing job data blob ... boolean updateJobData = trigger.getJobDataMap().isDirty(); byte[] data = null; if (updateJobData && !trigger.getJobDataMap().isEmpty()) { data = serializeJobData(trigger.getJobDataMap()).toByteArray(); } PreparedStatement ps = null; PreparedStatement ps2 = null; ResultSet rs = null; int insertResult; try { ps = conn.prepareStatement(rtp(UPDATE_ORACLE_TRIGGER)); ps.setString(1, trigger.getJobKey().getName()); ps.setString(2, trigger.getJobKey().getGroup()); ps.setString(3, trigger.getDescription()); long nextFireTime = -1; if (trigger.getNextFireTime() != null) { nextFireTime = trigger.getNextFireTime().getTime(); } ps.setBigDecimal(4, new BigDecimal(String.valueOf(nextFireTime))); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(5, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(6, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(7, type); ps.setBigDecimal(8, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(9, new BigDecimal(String.valueOf(endTime))); ps.setString(10, trigger.getCalendarName()); ps.setInt(11, trigger.getMisfireInstruction()); ps.setInt(12, trigger.getPriority()); ps.setString(13, trigger.getKey().getName()); ps.setString(14, trigger.getKey().getGroup()); insertResult = ps.executeUpdate(); if(updateJobData) { ps.close(); ps = conn .prepareStatement(rtp(UPDATE_ORACLE_TRIGGER_JOB_DETAIL_EMPTY_BLOB)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(SELECT_ORACLE_TRIGGER_JOB_DETAIL_BLOB)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); rs = ps.executeQuery(); if (rs.next()) { Blob dbBlob = writeDataToBlob(rs, 1, data); ps2 = conn.prepareStatement(rtp(UPDATE_ORACLE_TRIGGER_JOB_DETAIL_BLOB)); ps2.setBlob(1, dbBlob); ps2.setString(2, trigger.getKey().getName()); ps2.setString(3, trigger.getKey().getGroup()); ps2.executeUpdate(); } } if(tDel == null) updateBlobTrigger(conn, trigger); else tDel.updateExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeResultSet(rs); closeStatement(ps); closeStatement(ps2); } return insertResult; } @Override public int insertCalendar(Connection conn, String calendarName, Calendar calendar) throws IOException, SQLException { ByteArrayOutputStream baos = serializeObject(calendar); PreparedStatement ps = null; PreparedStatement ps2 = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(INSERT_ORACLE_CALENDAR)); ps.setString(1, calendarName); ps.executeUpdate(); ps.close(); ps = conn.prepareStatement(rtp(SELECT_ORACLE_CALENDAR_BLOB)); ps.setString(1, calendarName); rs = ps.executeQuery(); if (rs.next()) { Blob dbBlob = writeDataToBlob(rs, 1, baos.toByteArray()); ps2 = conn.prepareStatement(rtp(UPDATE_ORACLE_CALENDAR_BLOB)); ps2.setBlob(1, dbBlob); ps2.setString(2, calendarName); return ps2.executeUpdate(); } return 0; } finally { closeResultSet(rs); closeStatement(ps); closeStatement(ps2); } } @Override public int updateCalendar(Connection conn, String calendarName, Calendar calendar) throws IOException, SQLException { ByteArrayOutputStream baos = serializeObject(calendar); PreparedStatement ps = null; PreparedStatement ps2 = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_ORACLE_CALENDAR_BLOB)); ps.setString(1, calendarName); rs = ps.executeQuery(); if (rs.next()) { Blob dbBlob = writeDataToBlob(rs, 1, baos.toByteArray()); ps2 = conn.prepareStatement(rtp(UPDATE_ORACLE_CALENDAR_BLOB)); ps2.setBlob(1, dbBlob); ps2.setString(2, calendarName); return ps2.executeUpdate(); } return 0; } finally { closeResultSet(rs); closeStatement(ps); closeStatement(ps2); } } @Override public int updateJobData(Connection conn, JobDetail job) throws IOException, SQLException { ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); byte[] data = baos.toByteArray(); PreparedStatement ps = null; PreparedStatement ps2 = null; ResultSet rs = null; try { ps = conn.prepareStatement(rtp(SELECT_ORACLE_JOB_DETAIL_BLOB)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); rs = ps.executeQuery(); int res = 0; if (rs.next()) { Blob dbBlob = writeDataToBlob(rs, 1, data); ps2 = conn.prepareStatement(rtp(UPDATE_ORACLE_JOB_DETAIL_BLOB)); ps2.setBlob(1, dbBlob); ps2.setString(2, job.getKey().getName()); ps2.setString(3, job.getKey().getGroup()); res = ps2.executeUpdate(); } return res; } finally { closeResultSet(rs); closeStatement(ps); closeStatement(ps2); } } @SuppressWarnings("deprecation") protected Blob writeDataToBlob(ResultSet rs, int column, byte[] data) throws SQLException { Blob blob = rs.getBlob(column); // get blob if (blob == null) { throw new SQLException("Driver's Blob representation is null!"); } if (blob instanceof oracle.sql.BLOB) { // is it an oracle blob? ((oracle.sql.BLOB) blob).putBytes(1, data); ((oracle.sql.BLOB) blob).trim(data.length); return blob; } else { throw new SQLException( "Driver's Blob representation is of an unsupported type: " + blob.getClass().getName()); } } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/impl/jdbcjobstore/oracle/weblogic/WebLogicOracleDelegate.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore.oracle.weblogic; import org.quartz.impl.jdbcjobstore.oracle.OracleDelegate; import java.lang.reflect.Method; import java.sql.Blob; import java.sql.ResultSet; import java.sql.SQLException; /** * Handle Blobs correctly when Oracle is being used inside of Weblogic 8.1, * as discussed at: http://edocs.bea.com/wls/docs81/jdbc/thirdparty.html#1043705 * * @see org.quartz.impl.jdbcjobstore.WebLogicDelegate * @author James House * @author Igor Fedulov igor@fedulov.com */ public class WebLogicOracleDelegate extends OracleDelegate { /** * Check for the Weblogic Blob wrapper, and handle accordingly... */ @Override protected Blob writeDataToBlob(ResultSet rs, int column, byte[] data) throws SQLException { Blob blob = rs.getBlob(column); if (blob == null) { throw new SQLException("Driver's Blob representation is null!"); } // handle thin driver's blob if (blob instanceof weblogic.jdbc.vendor.oracle.OracleThinBlob) { ((weblogic.jdbc.vendor.oracle.OracleThinBlob) blob).putBytes(1, data); return blob; } else if(blob.getClass().getPackage().getName().startsWith("weblogic.")) { // (more slowly) handle blob for wrappers of other variations of drivers... try { // try to find putBytes method... Method m = blob.getClass().getMethod("putBytes", new Class[] {long.class, byte[].class}); m.invoke(blob, new Object[] {1L, data}); } catch (Exception e) { try { // Added this logic to the original code from OpenSymphony // putBytes method does not exist. Try setBytes Method m = blob.getClass().getMethod("setBytes", new Class[] { long.class, byte[].class }); m.invoke(blob, new Object[] {1L, data }); } catch (Exception e2) { throw new SQLException("Unable to find putBytes(long,byte[]) or setBytes(long,byte[]) methods on blob: " + e2); } } return blob; } else { return super.writeDataToBlob(rs, column, data); } } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/AndMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.Matcher; import org.quartz.utils.Key; /** * Matches using an AND operator on two Matcher operands. * * @author jhouse */ public class AndMatcher> implements Matcher { private static final long serialVersionUID = 4697276220890670941L; protected Matcher leftOperand; protected Matcher rightOperand; protected AndMatcher(Matcher leftOperand, Matcher rightOperand) { if(leftOperand == null || rightOperand == null) throw new IllegalArgumentException("Two non-null operands required!"); this.leftOperand = leftOperand; this.rightOperand = rightOperand; } /** * Create an AndMatcher that depends upon the result of both of the given matchers. */ public static > AndMatcher and(Matcher leftOperand, Matcher rightOperand) { return new AndMatcher<>(leftOperand, rightOperand); } public boolean isMatch(T key) { return leftOperand.isMatch(key) && rightOperand.isMatch(key); } public Matcher getLeftOperand() { return leftOperand; } public Matcher getRightOperand() { return rightOperand; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((leftOperand == null) ? 0 : leftOperand.hashCode()); result = prime * result + ((rightOperand == null) ? 0 : rightOperand.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AndMatcher other = (AndMatcher) obj; if (leftOperand == null) { if (other.leftOperand != null) return false; } else if (!leftOperand.equals(other.leftOperand)) return false; if (rightOperand == null) { return other.rightOperand == null; } else return rightOperand.equals(other.rightOperand); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/EverythingMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.JobKey; import org.quartz.Matcher; import org.quartz.TriggerKey; import org.quartz.utils.Key; /** * Matches on the complete key being equal (both name and group). * * @author jhouse */ public class EverythingMatcher> implements Matcher { private static final long serialVersionUID = 202300056681974058L; protected EverythingMatcher() { } /** * Create an EverythingMatcher that matches all jobs. */ public static EverythingMatcher allJobs() { return new EverythingMatcher<>(); } /** * Create an EverythingMatcher that matches all triggers. */ public static EverythingMatcher allTriggers() { return new EverythingMatcher<>(); } public boolean isMatch(T key) { return true; } @Override public boolean equals(Object obj) { if(obj == null) return false; return obj.getClass().equals(getClass()); } @Override public int hashCode() { return getClass().getName().hashCode(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/GroupMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.JobKey; import org.quartz.TriggerKey; import org.quartz.utils.Key; /** * Matches on group (ignores name) property of Keys. * * @author jhouse */ public class GroupMatcher> extends StringMatcher { private static final long serialVersionUID = -3275767650469343849L; protected GroupMatcher(String compareTo, StringOperatorName compareWith) { super(compareTo, compareWith); } /** * Create a GroupMatcher that matches groups equaling the given string. */ public static > GroupMatcher groupEquals(String compareTo) { return new GroupMatcher<>(compareTo, StringOperatorName.EQUALS); } /** * Create a GroupMatcher that matches job groups equaling the given string. */ public static GroupMatcher jobGroupEquals(String compareTo) { return GroupMatcher.groupEquals(compareTo); } /** * Create a GroupMatcher that matches trigger groups equaling the given string. */ public static GroupMatcher triggerGroupEquals(String compareTo) { return GroupMatcher.groupEquals(compareTo); } /** * Create a GroupMatcher that matches groups starting with the given string. */ public static > GroupMatcher groupStartsWith(String compareTo) { return new GroupMatcher<>(compareTo, StringOperatorName.STARTS_WITH); } /** * Create a GroupMatcher that matches job groups starting with the given string. */ public static GroupMatcher jobGroupStartsWith(String compareTo) { return GroupMatcher.groupStartsWith(compareTo); } /** * Create a GroupMatcher that matches trigger groups starting with the given string. */ public static GroupMatcher triggerGroupStartsWith(String compareTo) { return GroupMatcher.groupStartsWith(compareTo); } /** * Create a GroupMatcher that matches groups ending with the given string. */ public static > GroupMatcher groupEndsWith(String compareTo) { return new GroupMatcher<>(compareTo, StringOperatorName.ENDS_WITH); } /** * Create a GroupMatcher that matches job groups ending with the given string. */ public static GroupMatcher jobGroupEndsWith(String compareTo) { return GroupMatcher.groupEndsWith(compareTo); } /** * Create a GroupMatcher that matches trigger groups ending with the given string. */ public static GroupMatcher triggerGroupEndsWith(String compareTo) { return GroupMatcher.groupEndsWith(compareTo); } /** * Create a GroupMatcher that matches groups containing the given string. */ public static > GroupMatcher groupContains(String compareTo) { return new GroupMatcher<>(compareTo, StringOperatorName.CONTAINS); } /** * Create a GroupMatcher that matches job groups containing the given string. */ public static GroupMatcher jobGroupContains(String compareTo) { return GroupMatcher.groupContains(compareTo); } /** * Create a GroupMatcher that matches trigger groups containing the given string. */ public static GroupMatcher triggerGroupContains(String compareTo) { return GroupMatcher.groupContains(compareTo); } /** * Create a GroupMatcher that matches groups starting with the given string. */ public static > GroupMatcher anyGroup() { return new GroupMatcher<>("", StringOperatorName.ANYTHING); } /** * Create a GroupMatcher that matches job groups starting with the given string. */ public static GroupMatcher anyJobGroup() { return GroupMatcher.anyGroup(); } /** * Create a GroupMatcher that matches trigger groups starting with the given string. */ public static GroupMatcher anyTriggerGroup() { return GroupMatcher.anyGroup(); } @Override protected String getValue(T key) { return key.getGroup(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/KeyMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.Matcher; import org.quartz.utils.Key; /** * Matches on the complete key being equal (both name and group). * * @author jhouse */ public class KeyMatcher> implements Matcher { private static final long serialVersionUID = 1230009869074992437L; protected final T compareTo; protected KeyMatcher(T compareTo) { this.compareTo = compareTo; } /** * Create a KeyMatcher that matches Keys that equal the given key. */ public static > KeyMatcher keyEquals(U compareTo) { return new KeyMatcher<>(compareTo); } public boolean isMatch(T key) { return compareTo.equals(key); } public T getCompareToValue() { return compareTo; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((compareTo == null) ? 0 : compareTo.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; KeyMatcher other = (KeyMatcher) obj; if (compareTo == null) { return other.compareTo == null; } else return compareTo.equals(other.compareTo); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/NameMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.JobKey; import org.quartz.TriggerKey; import org.quartz.utils.Key; /** * Matches on name (ignores group) property of Keys. * * @author jhouse */ public class NameMatcher> extends StringMatcher { private static final long serialVersionUID = -33104959459613480L; protected NameMatcher(String compareTo, StringOperatorName compareWith) { super(compareTo, compareWith); } /** * Create a NameMatcher that matches names equaling the given string. */ public static > NameMatcher nameEquals(String compareTo) { return new NameMatcher<>(compareTo, StringOperatorName.EQUALS); } /** * Create a NameMatcher that matches job names equaling the given string. */ public static NameMatcher jobNameEquals(String compareTo) { return NameMatcher.nameEquals(compareTo); } /** * Create a NameMatcher that matches trigger names equaling the given string. */ public static NameMatcher triggerNameEquals(String compareTo) { return NameMatcher.nameEquals(compareTo); } /** * Create a NameMatcher that matches names starting with the given string. */ public static > NameMatcher nameStartsWith(String compareTo) { return new NameMatcher<>(compareTo, StringOperatorName.STARTS_WITH); } /** * Create a NameMatcher that matches job names starting with the given string. */ public static NameMatcher jobNameStartsWith(String compareTo) { return NameMatcher.nameStartsWith(compareTo); } /** * Create a NameMatcher that matches trigger names starting with the given string. */ public static NameMatcher triggerNameStartsWith(String compareTo) { return NameMatcher.nameStartsWith(compareTo); } /** * Create a NameMatcher that matches names ending with the given string. */ public static > NameMatcher nameEndsWith(String compareTo) { return new NameMatcher<>(compareTo, StringOperatorName.ENDS_WITH); } /** * Create a NameMatcher that matches job names ending with the given string. */ public static NameMatcher jobNameEndsWith(String compareTo) { return NameMatcher.nameEndsWith(compareTo); } /** * Create a NameMatcher that matches trigger names ending with the given string. */ public static NameMatcher triggerNameEndsWith(String compareTo) { return NameMatcher.nameEndsWith(compareTo); } /** * Create a NameMatcher that matches names containing the given string. */ public static > NameMatcher nameContains(String compareTo) { return new NameMatcher<>(compareTo, StringOperatorName.CONTAINS); } /** * Create a NameMatcher that matches job names containing the given string. */ public static NameMatcher jobNameContains(String compareTo) { return NameMatcher.nameContains(compareTo); } /** * Create a NameMatcher that matches trigger names containing the given string. */ public static NameMatcher triggerNameContains(String compareTo) { return NameMatcher.nameContains(compareTo); } @Override protected String getValue(T key) { return key.getName(); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/NotMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.Matcher; import org.quartz.utils.Key; /** * Matches using an NOT operator on another Matcher. * * @author jhouse */ public class NotMatcher> implements Matcher { private static final long serialVersionUID = -2856769076151741391L; protected Matcher operand; protected NotMatcher(Matcher operand) { if(operand == null) throw new IllegalArgumentException("Non-null operand required!"); this.operand = operand; } /** * Create a NotMatcher that reverses the result of the given matcher. */ public static > NotMatcher not(Matcher operand) { return new NotMatcher<>(operand); } public boolean isMatch(T key) { return !operand.isMatch(key); } public Matcher getOperand() { return operand; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((operand == null) ? 0 : operand.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; NotMatcher other = (NotMatcher) obj; if (operand == null) { return other.operand == null; } else return operand.equals(other.operand); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/OrMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.Matcher; import org.quartz.utils.Key; /** * Matches using an OR operator on two Matcher operands. * * @author jhouse */ public class OrMatcher> implements Matcher { private static final long serialVersionUID = -2867392824539403712L; protected Matcher leftOperand; protected Matcher rightOperand; protected OrMatcher(Matcher leftOperand, Matcher rightOperand) { if(leftOperand == null || rightOperand == null) throw new IllegalArgumentException("Two non-null operands required!"); this.leftOperand = leftOperand; this.rightOperand = rightOperand; } /** * Create an OrMatcher that depends upon the result of at least one of the given matchers. */ public static > OrMatcher or(Matcher leftOperand, Matcher rightOperand) { return new OrMatcher<>(leftOperand, rightOperand); } public boolean isMatch(T key) { return leftOperand.isMatch(key) || rightOperand.isMatch(key); } public Matcher getLeftOperand() { return leftOperand; } public Matcher getRightOperand() { return rightOperand; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((leftOperand == null) ? 0 : leftOperand.hashCode()); result = prime * result + ((rightOperand == null) ? 0 : rightOperand.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; OrMatcher other = (OrMatcher) obj; if (leftOperand == null) { if (other.leftOperand != null) return false; } else if (!leftOperand.equals(other.leftOperand)) return false; if (rightOperand == null) { return other.rightOperand == null; } else return rightOperand.equals(other.rightOperand); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/matchers/StringMatcher.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.quartz.Matcher; import org.quartz.utils.Key; /** * An abstract base class for some types of matchers. * * @author jhouse */ public abstract class StringMatcher> implements Matcher { private static final long serialVersionUID = -2757924162611145836L; public enum StringOperatorName { EQUALS { @Override public boolean evaluate(final String value, final String compareTo) { return value.equals(compareTo); } }, STARTS_WITH { @Override public boolean evaluate(final String value, final String compareTo) { return value.startsWith(compareTo); } }, ENDS_WITH { @Override public boolean evaluate(final String value, final String compareTo) { return value.endsWith(compareTo); } }, CONTAINS { @Override public boolean evaluate(final String value, final String compareTo) { return value.contains(compareTo); } }, ANYTHING { @Override public boolean evaluate(final String value, final String compareTo) { return true; } }; public abstract boolean evaluate(String value, String compareTo); } protected String compareTo; protected StringOperatorName compareWith; protected StringMatcher(String compareTo, StringOperatorName compareWith) { if(compareTo == null) throw new IllegalArgumentException("CompareTo value cannot be null!"); if(compareWith == null) throw new IllegalArgumentException("CompareWith operator cannot be null!"); this.compareTo = compareTo; this.compareWith = compareWith; } protected abstract String getValue(T key); public boolean isMatch(T key) { return compareWith.evaluate(getValue(key), compareTo); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((compareTo == null) ? 0 : compareTo.hashCode()); result = prime * result + ((compareWith == null) ? 0 : compareWith.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; StringMatcher other = (StringMatcher) obj; if (compareTo == null) { if (other.compareTo != null) return false; } else if (!compareTo.equals(other.compareTo)) return false; if (compareWith == null) { return other.compareWith == null; } else return compareWith.equals(other.compareWith); } public String getCompareToValue() { return compareTo; } public StringOperatorName getCompareWithOperator() { return compareWith; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/package.html ================================================ Package org.quartz.impl

Contains implementations of the SchedulerFactory, JobStore, ThreadPool, and other interfaces required by the org.quartz.core.QuartzScheduler.

Classes in this package may have dependencies on third-party packages.




See the Quartz project for more information. ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/AbstractTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import org.quartz.Calendar; import org.quartz.CronTrigger; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.ScheduleBuilder; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.quartz.spi.OperableTrigger; /** *

* 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. *

* * @author James House * @author Sharada Jambula */ public abstract class AbstractTrigger implements OperableTrigger { private static final long serialVersionUID = -3904243490805975570L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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; @SuppressWarnings("unused") private static final boolean VOLATILITY = false; // still here for serialization backward compatibility private String calendarName = null; private String fireInstanceId = null; private int misfireInstruction = MISFIRE_INSTRUCTION_SMART_POLICY; private int priority = DEFAULT_PRIORITY; private transient TriggerKey key = null; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a Trigger with no specified name, group, or {@link 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}. *

*/ protected AbstractTrigger() { // do nothing... } /** *

* Create a Trigger with the given name, and default 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}. *

* * @exception IllegalArgumentException * if name is null or empty, or the group is an empty string. */ protected AbstractTrigger(String name) { setName(name); setGroup(null); } /** *

* 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. */ protected AbstractTrigger(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. */ protected AbstractTrigger(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().isEmpty()) { throw new IllegalArgumentException( "Trigger name cannot be null or empty."); } this.name = name; this.key = null; } /** *

* 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().isEmpty()) { throw new IllegalArgumentException( "Group name cannot be an empty string."); } if(group == null) { group = Scheduler.DEFAULT_GROUP; } this.group = group; this.key = null; } public void setKey(TriggerKey key) { setName(key.getName()); setGroup(key.getGroup()); this.key = key; } /** *

* Get the name of the associated {@link org.quartz.JobDetail}. *

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

* Set the name of the associated {@link org.quartz.JobDetail}. *

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

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

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

* Set the name of the associated {@link 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().isEmpty()) { throw new IllegalArgumentException( "Group name cannot be null or empty."); } if(jobGroup == null) { jobGroup = Scheduler.DEFAULT_GROUP; } this.jobGroup = jobGroup; } public void setJobKey(JobKey key) { setJobName(key.getName()); setJobGroup(key.getGroup()); } /** *

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

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

* 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; } /** *

* 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; } /** * 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; } /** *

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

* *

* Called after the {@link Scheduler} has executed the * {@link 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 CompletedExecutionInstruction constants. * * @see org.quartz.Trigger.CompletedExecutionInstruction * @see #triggered(Calendar) */ public CompletedExecutionInstruction executionComplete(JobExecutionContext context, JobExecutionException result) { if (result != null && result.refireImmediately()) { return CompletedExecutionInstruction.RE_EXECUTE_JOB; } if (result != null && result.unscheduleFiringTrigger()) { return CompletedExecutionInstruction.SET_TRIGGER_COMPLETE; } if (result != null && result.unscheduleAllTriggers()) { return CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE; } if (!mayFireAgain()) { return CompletedExecutionInstruction.DELETE_TRIGGER; } return CompletedExecutionInstruction.NOOP; } /** *

* 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 candidateMisfireInstruction); /** *

* 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; } /** *

* 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"); } if (group == null) { throw new SchedulerException("Trigger's group cannot be null"); } if (jobName == null) { throw new SchedulerException( "Trigger's related Job's name cannot be null"); } if (jobGroup == null) { throw new SchedulerException( "Trigger's related Job's group cannot be null"); } } /** *

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

* *

* Usable by {@link 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. *

*/ @Override public String toString() { return "Trigger '" + getFullName() + "': triggerClass: '" + getClass().getName() + " calendar: '" + getCalendarName() + "' misfireInstruction: " + getMisfireInstruction() + " nextFireTime: " + getNextFireTime(); } /** *

* Compare the next fire time of this Trigger to that of * another by comparing their keys, or in other words, sorts them * according to the natural (i.e. alphabetical) order of their keys. *

*/ public int compareTo(Trigger other) { if(other.getKey() == null && getKey() == null) return 0; if(other.getKey() == null) return -1; if(getKey() == null) return 1; return getKey().compareTo(other.getKey()); } /** * Trigger equality is based upon the equality of the TriggerKey. * * @return true if the key of this Trigger equals that of the given Trigger. */ @Override public boolean equals(Object o) { if(!(o instanceof Trigger)) return false; Trigger other = (Trigger)o; return !(other.getKey() == null || getKey() == null) && getKey().equals(other.getKey()); } @Override public int hashCode() { if(getKey() == null) return super.hashCode(); return getKey().hashCode(); } @Override public Object clone() { AbstractTrigger copy; try { copy = (AbstractTrigger) super.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; } public TriggerBuilder getTriggerBuilder() { return TriggerBuilder.newTrigger() .forJob(getJobKey()) .modifiedByCalendar(getCalendarName()) .usingJobData(getJobDataMap()) .withDescription(getDescription()) .endAt(getEndTime()) .withIdentity(getKey()) .withPriority(getPriority()) .startAt(getStartTime()) .withSchedule(getScheduleBuilder()); } public abstract ScheduleBuilder getScheduleBuilder(); } ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/CalendarIntervalTriggerImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import org.quartz.CalendarIntervalScheduleBuilder; import org.quartz.CalendarIntervalTrigger; import org.quartz.CronTrigger; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.ScheduleBuilder; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerUtils; /** *

A concrete {@link Trigger} that is used to fire a {@link org.quartz.JobDetail} * based upon repeating calendar time intervals.

* *

The trigger will fire every N (see {@link #setRepeatInterval(int)} ) units of calendar time * (see {@link #setRepeatIntervalUnit(org.quartz.DateBuilder.IntervalUnit)}) as specified in the trigger's definition. * This trigger can achieve schedules that are not possible with {@link SimpleTrigger} (e.g * because months are not a fixed number of seconds) or {@link CronTrigger} (e.g. because * "every 5 months" is not an even divisor of 12).

* *

If you use an interval unit of MONTH then care should be taken when setting * a startTime value that is on a day near the end of the month. For example, * if you choose a start time that occurs on January 31st, and have a trigger with unit * MONTH and interval 1, then the next fire time will be February 28th, * and the next time after that will be March 28th - and essentially each subsequent firing will * occur on the 28th of the month, even if a 31st day exists. If you want a trigger that always * fires on the last day of the month - regardless of the number of days in the month, * you should use CronTrigger.

* * @see Trigger * @see CronTrigger * @see SimpleTrigger * @see TriggerUtils * * @since 1.7 * * @author James House */ public class CalendarIntervalTriggerImpl extends AbstractTrigger implements CalendarIntervalTrigger, CoreTrigger { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private static final long serialVersionUID = -2635982274232850343L; private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR) + 100; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Date startTime = null; private Date endTime = null; private Date nextFireTime = null; private Date previousFireTime = null; private int repeatInterval = 0; private IntervalUnit repeatIntervalUnit = IntervalUnit.DAY; private TimeZone timeZone; private boolean preserveHourOfDayAcrossDaylightSavings = false; // false is backward-compatible with behavior private boolean skipDayIfHourDoesNotExist = false; private int timesTriggered = 0; private final boolean complete = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a DateIntervalTrigger with no settings. *

*/ public CalendarIntervalTriggerImpl() { super(); } /** *

* Create a DateIntervalTrigger that will occur immediately, and * repeat at the given interval. *

*/ public CalendarIntervalTriggerImpl(String name, IntervalUnit intervalUnit, int repeatInterval) { this(name, null, intervalUnit, repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur immediately, and * repeat at the given interval. *

*/ public CalendarIntervalTriggerImpl(String name, String group, IntervalUnit intervalUnit, int repeatInterval) { this(name, group, new Date(), null, intervalUnit, repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur at the given time, * and repeat at the given interval 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 intervalUnit * The repeat interval unit (minutes, days, months, etc). * @param repeatInterval * The number of milliseconds to pause between the repeat firing. */ public CalendarIntervalTriggerImpl(String name, Date startTime, Date endTime, IntervalUnit intervalUnit, int repeatInterval) { this(name, null, startTime, endTime, intervalUnit, repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur at the given time, * and repeat at the given interval 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 intervalUnit * The repeat interval unit (minutes, days, months, etc). * @param repeatInterval * The number of milliseconds to pause between the repeat firing. */ public CalendarIntervalTriggerImpl(String name, String group, Date startTime, Date endTime, IntervalUnit intervalUnit, int repeatInterval) { super(name, group); setStartTime(startTime); setEndTime(endTime); setRepeatIntervalUnit(intervalUnit); setRepeatInterval(repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur at the given time, * fire the identified Job and repeat at the given * interval 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 intervalUnit * The repeat interval unit (minutes, days, months, etc). * @param repeatInterval * The number of milliseconds to pause between the repeat firing. */ public CalendarIntervalTriggerImpl(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, IntervalUnit intervalUnit, int repeatInterval) { super(name, group, jobName, jobGroup); setStartTime(startTime); setEndTime(endTime); setRepeatIntervalUnit(intervalUnit); setRepeatInterval(repeatInterval); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Get the time at which the DateIntervalTrigger should occur. *

*/ @Override public Date getStartTime() { if(startTime == null) startTime = new Date(); return startTime; } /** *

* Set the time at which the DateIntervalTrigger should occur. *

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

* Get the time at which the DateIntervalTrigger should quit * repeating. *

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

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

* * @exception IllegalArgumentException * if endTime is before start time. */ @Override 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; } /* (non-Javadoc) * @see org.quartz.DateIntervalTriggerI#getRepeatIntervalUnit() */ public IntervalUnit getRepeatIntervalUnit() { return repeatIntervalUnit; } /** *

Set the interval unit - the time unit on with the interval applies.

*/ public void setRepeatIntervalUnit(IntervalUnit intervalUnit) { this.repeatIntervalUnit = intervalUnit; } /* (non-Javadoc) * @see org.quartz.DateIntervalTriggerI#getRepeatInterval() */ public int getRepeatInterval() { return repeatInterval; } /** *

* set the time interval that will be added to the DateIntervalTrigger's * fire time (in the set repeat interval unit) in order to calculate the time of the * next trigger repeat. *

* * @exception IllegalArgumentException * if repeatInterval is < 1 */ public void setRepeatInterval( int repeatInterval) { if (repeatInterval < 0) { throw new IllegalArgumentException( "Repeat interval must be >= 1"); } this.repeatInterval = repeatInterval; } /* (non-Javadoc) * @see org.quartz.CalendarIntervalTriggerI#getTimeZone() */ public TimeZone getTimeZone() { if (timeZone == null) { timeZone = TimeZone.getDefault(); } return timeZone; } /** *

* Sets the time zone within which time calculations related to this * trigger will be performed. *

* * @param timeZone the desired TimeZone, or null for the system default. */ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } /** * If intervals are a day or greater, this property (set to true) will * cause the firing of the trigger to always occur at the same time of day, * (the time of day of the startTime) regardless of daylight saving time * transitions. Default value is false. * *

* For example, without the property set, your trigger may have a start * time of 9:00 am on March 1st, and a repeat interval of 2 days. But * after the daylight saving transition occurs, the trigger may start * firing at 8:00 am every other day. *

* *

* If however, the time of day does not exist on a given day to fire * (e.g. 2:00 am in the United States on the days of daylight saving * transition), the trigger will go ahead and fire one hour off on * that day, and then resume the normal hour on other days. If * you wish for the trigger to never fire at the "wrong" hour, then * you should set the property skipDayIfHourDoesNotExist. *

* * @see #isSkipDayIfHourDoesNotExist() * @see #getStartTime() * @see #getTimeZone() */ public boolean isPreserveHourOfDayAcrossDaylightSavings() { return preserveHourOfDayAcrossDaylightSavings; } public void setPreserveHourOfDayAcrossDaylightSavings(boolean preserveHourOfDayAcrossDaylightSavings) { this.preserveHourOfDayAcrossDaylightSavings = preserveHourOfDayAcrossDaylightSavings; } /** * If intervals are a day or greater, and * preserveHourOfDayAcrossDaylightSavings property is set to true, and the * hour of the day does not exist on a given day for which the trigger * would fire, the day will be skipped and the trigger advanced a second * interval if this property is set to true. Defaults to false. * *

* CAUTION! If you enable this property, and your hour of day happens * to be that of daylight savings transition (e.g. 2:00 am in the United * States) and the trigger's interval would have had the trigger fire on * that day, then you may actually completely miss a firing on the day of * transition if that hour of day does not exist on that day! In such a * case the next fire time of the trigger will be computed as double (if * the interval is 2 days, then a span of 4 days between firings will * occur). *

* * @see #isPreserveHourOfDayAcrossDaylightSavings() */ public boolean isSkipDayIfHourDoesNotExist() { return skipDayIfHourDoesNotExist; } public void setSkipDayIfHourDoesNotExist(boolean skipDayIfHourDoesNotExist) { this.skipDayIfHourDoesNotExist = skipDayIfHourDoesNotExist; } /* (non-Javadoc) * @see org.quartz.DateIntervalTriggerI#getTimesTriggered() */ public int getTimesTriggered() { return timesTriggered; } /** *

* Set the number of times the DateIntervalTrigger has already * fired. *

*/ public void setTimesTriggered(int timesTriggered) { this.timesTriggered = timesTriggered; } @Override protected boolean validateMisfireInstruction(int misfireInstruction) { if (misfireInstruction < MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { return false; } return misfireInstruction <= MISFIRE_INSTRUCTION_DO_NOTHING; } /** *

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

* *

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

*
    *
  • The instruction will be interpreted as MISFIRE_INSTRUCTION_FIRE_ONCE_NOW *
*/ @Override public void updateAfterMisfire(org.quartz.Calendar cal) { int instr = getMisfireInstruction(); if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return; 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) { // fire once now... setNextFireTime(new Date()); // the new fire time afterward will magically preserve the original // time of day for firing for day/week/month interval triggers, // because of the way getFireTimeAfter() works - in its always restarting // computation from the start time. } } /** *

* 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) */ @Override public void triggered(org.quartz.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 org.quartz.spi.OperableTrigger#updateWithNewCalendar(org.quartz.Calendar, long) */ @Override public void updateWithNewCalendar(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); } } } } /** *

* 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). */ @Override public Date computeFirstFireTime(org.quartz.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; } /** *

* 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. *

*/ @Override public Date getNextFireTime() { return nextFireTime; } /** *

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

* Set the next time at which the DateIntervalTrigger 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 DateIntervalTrigger 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 DateIntervalTrigger will * fire, after the given time. If the trigger will not fire after the given * time, null will be returned. *

*/ @Override public Date getFireTimeAfter(Date afterTime) { return getFireTimeAfter(afterTime, false); } protected Date getFireTimeAfter(Date afterTime, boolean ignoreEndTime) { if (complete) { return null; } // increment afterTime by a second, so that we are // comparing against a time after it! if (afterTime == null) { afterTime = new Date(); } long startMillis = getStartTime().getTime(); long afterMillis = afterTime.getTime(); long endMillis = (getEndTime() == null) ? Long.MAX_VALUE : getEndTime() .getTime(); if (!ignoreEndTime && (endMillis <= afterMillis)) { return null; } if (afterMillis < startMillis) { return new Date(startMillis); } long secondsAfterStart = 1 + (afterMillis - startMillis) / 1000L; Date time = null; long repeatLong = getRepeatInterval(); Calendar aTime = Calendar.getInstance(); aTime.setTime(afterTime); Calendar sTime = Calendar.getInstance(); if(timeZone != null) sTime.setTimeZone(timeZone); sTime.setTime(getStartTime()); sTime.setLenient(true); if(getRepeatIntervalUnit().equals(IntervalUnit.SECOND)) { long jumpCount = secondsAfterStart / repeatLong; if(secondsAfterStart % repeatLong != 0) jumpCount++; sTime.add(Calendar.SECOND, getRepeatInterval() * (int)jumpCount); time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MINUTE)) { long jumpCount = secondsAfterStart / (repeatLong * 60L); if(secondsAfterStart % (repeatLong * 60L) != 0) jumpCount++; sTime.add(Calendar.MINUTE, getRepeatInterval() * (int)jumpCount); time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.HOUR)) { long jumpCount = secondsAfterStart / (repeatLong * 60L * 60L); if(secondsAfterStart % (repeatLong * 60L * 60L) != 0) jumpCount++; sTime.add(Calendar.HOUR_OF_DAY, getRepeatInterval() * (int)jumpCount); time = sTime.getTime(); } else { // intervals a day or greater ... int initialHourOfDay = sTime.get(Calendar.HOUR_OF_DAY); if(getRepeatIntervalUnit().equals(IntervalUnit.DAY)) { sTime.setLenient(true); // Because intervals greater than an hour have an non-fixed number // of seconds in them (due to daylight savings, variation number of // days in each month, leap year, etc. ) we can't jump forward an // exact number of seconds to calculate the fire time as we can // with the second, minute and hour intervals. But, rather // than slowly crawling our way there by iteratively adding the // increment to the start time until we reach the "after time", // we can first make a big leap most of the way there... long jumpCount = secondsAfterStart / (repeatLong * 24L * 60L * 60L); // if we need to make a big jump, jump most of the way there, // but not all the way because in some cases we may over-shoot or under-shoot if(jumpCount > 20) { if(jumpCount < 50) jumpCount = (long) (jumpCount * 0.80); else if(jumpCount < 500) jumpCount = (long) (jumpCount * 0.90); else jumpCount = (long) (jumpCount * 0.95); sTime.add(java.util.Calendar.DAY_OF_YEAR, (int) (getRepeatInterval() * jumpCount)); } // now baby-step the rest of the way there... while(!sTime.getTime().after(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.DAY_OF_YEAR, getRepeatInterval()); } while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.DAY_OF_YEAR, getRepeatInterval()); } time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.WEEK)) { sTime.setLenient(true); // Because intervals greater than an hour have an non-fixed number // of seconds in them (due to daylight savings, variation number of // days in each month, leap year, etc. ) we can't jump forward an // exact number of seconds to calculate the fire time as we can // with the second, minute and hour intervals. But, rather // than slowly crawling our way there by iteratively adding the // increment to the start time until we reach the "after time", // we can first make a big leap most of the way there... long jumpCount = secondsAfterStart / (repeatLong * 7L * 24L * 60L * 60L); // if we need to make a big jump, jump most of the way there, // but not all the way because in some cases we may over-shoot or under-shoot if(jumpCount > 20) { if(jumpCount < 50) jumpCount = (long) (jumpCount * 0.80); else if(jumpCount < 500) jumpCount = (long) (jumpCount * 0.90); else jumpCount = (long) (jumpCount * 0.95); sTime.add(java.util.Calendar.WEEK_OF_YEAR, (int) (getRepeatInterval() * jumpCount)); } while(!sTime.getTime().after(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.WEEK_OF_YEAR, getRepeatInterval()); } while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.WEEK_OF_YEAR, getRepeatInterval()); } time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MONTH)) { sTime.setLenient(true); // because of the large variation in size of months, and // because months are already large blocks of time, we will // just advance via brute-force iteration. while(!sTime.getTime().after(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.MONTH, getRepeatInterval()); } while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.MONTH, getRepeatInterval()); } time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.YEAR)) { while(!sTime.getTime().after(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.YEAR, getRepeatInterval()); } while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.YEAR, getRepeatInterval()); } time = sTime.getTime(); } } // case of interval of a day or greater if (!ignoreEndTime && (endMillis <= time.getTime())) { return null; } return time; } private boolean daylightSavingHourShiftOccurredAndAdvanceNeeded(Calendar newTime, int initialHourOfDay, Date afterTime) { if(isPreserveHourOfDayAcrossDaylightSavings() && newTime.get(Calendar.HOUR_OF_DAY) != initialHourOfDay) { newTime.set(Calendar.HOUR_OF_DAY, initialHourOfDay); if (newTime.get(Calendar.HOUR_OF_DAY) != initialHourOfDay) { return isSkipDayIfHourDoesNotExist(); } else { return !newTime.getTime().after(afterTime); } } return false; } /** *

* Returns the final time at which the DateIntervalTrigger will * fire, if there is no end time set, null will be returned. *

* *

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

*/ @Override public Date getFinalFireTime() { if (complete || getEndTime() == null) { return null; } // back up a second from end time Date fTime = new Date(getEndTime().getTime() - 1000L); // find the next fire time after that fTime = getFireTimeAfter(fTime, true); // the trigger fires at the end time, that's it! if(fTime.equals(getEndTime())) return fTime; // otherwise we have to back up one interval from the fire time after the end time Calendar lTime = Calendar.getInstance(); if(timeZone != null) lTime.setTimeZone(timeZone); lTime.setTime(fTime); lTime.setLenient(true); if(getRepeatIntervalUnit().equals(IntervalUnit.SECOND)) { lTime.add(java.util.Calendar.SECOND, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MINUTE)) { lTime.add(java.util.Calendar.MINUTE, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.HOUR)) { lTime.add(java.util.Calendar.HOUR_OF_DAY, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.DAY)) { lTime.add(java.util.Calendar.DAY_OF_YEAR, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.WEEK)) { lTime.add(java.util.Calendar.WEEK_OF_YEAR, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MONTH)) { lTime.add(java.util.Calendar.MONTH, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.YEAR)) { lTime.add(java.util.Calendar.YEAR, -1 * getRepeatInterval()); } return lTime.getTime(); } /** *

* Determines whether or not the DateIntervalTrigger will occur * again. *

*/ @Override 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. */ @Override public void validate() throws SchedulerException { super.validate(); if (repeatInterval < 1) { throw new SchedulerException("Repeat Interval cannot be zero."); } } /** * Get a {@link ScheduleBuilder} that is configured to produce a * schedule identical to this trigger's schedule. * * @see #getTriggerBuilder() */ @Override public ScheduleBuilder getScheduleBuilder() { CalendarIntervalScheduleBuilder cb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule() .withInterval(getRepeatInterval(), getRepeatIntervalUnit()); switch(getMisfireInstruction()) { case MISFIRE_INSTRUCTION_DO_NOTHING : cb.withMisfireHandlingInstructionDoNothing(); break; case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW : cb.withMisfireHandlingInstructionFireAndProceed(); break; } return cb; } public boolean hasAdditionalProperties() { return false; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/CoreTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import org.quartz.Trigger; /** * internal interface preserved for backward compatibility */ public interface CoreTrigger extends Trigger { boolean hasAdditionalProperties(); } ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/CronTriggerImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import org.quartz.CronExpression; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.ScheduleBuilder; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.TriggerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

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

* * * @author Sharada Jambula, James House * @author Contributions from Mads Henderson */ public class CronTriggerImpl extends AbstractTrigger implements CronTrigger, CoreTrigger { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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; private static final Logger LOGGER = LoggerFactory.getLogger(CronTriggerImpl.class); protected static final int YEAR_TO_GIVEUP_SCHEDULING_AT = CronExpression.MAX_YEAR; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 system's default time zone. *

*/ public CronTriggerImpl() { super(); setStartTime(new Date()); setTimeZone(TimeZone.getDefault()); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @Override public Object clone() { CronTriggerImpl copy = (CronTriggerImpl) super.clone(); if (cronEx != null) { copy.setCronExpression(new CronExpression(cronEx)); } return copy; } public void setCronExpression(String cronExpression) throws ParseException { TimeZone origTz = getTimeZone(); this.cronEx = new CronExpression(cronExpression); this.cronEx.setTimeZone(origTz); } /* (non-Javadoc) * @see org.quartz.CronTriggerI#getCronExpression() */ 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. */ public void setCronExpression(CronExpression cronExpression) { this.cronEx = cronExpression; this.timeZone = cronExpression.getTimeZone(); } /** *

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

*/ @Override public Date getStartTime() { return this.startTime; } @Override public void setStartTime(Date startTime) { if (startTime == null) { throw new IllegalArgumentException("Start time cannot be null"); } Date eTime = getEndTime(); if (eTime != 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 repeatCount isn't yet satisfied. *

* * @see #getFinalFireTime() */ @Override public Date getEndTime() { return this.endTime; } @Override 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(org.quartz.spi.OperableTrigger, org.quartz.Calendar, java.util.Date, java.util.Date) */ @Override 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. */ @Override 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; } /* (non-Javadoc) * @see org.quartz.CronTriggerI#getTimeZone() */ 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 * org.quartz.Calendar (if any) *

*/ @Override 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 org.quartz.calendar *

*/ @Override 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. *

*/ @Override public boolean mayFireAgain() { return (getNextFireTime() != null); } @Override protected boolean validateMisfireInstruction(int misfireInstruction) { return misfireInstruction >= MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY && misfireInstruction <= MISFIRE_INSTRUCTION_DO_NOTHING; } /** *

* 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:

*
    *
  • The instruction will be interpreted as MISFIRE_INSTRUCTION_FIRE_ONCE_NOW *
*/ @Override public void updateAfterMisfire(org.quartz.Calendar cal) { int instr = getMisfireInstruction(); if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return; 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 * 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_OF_DAY, 0); test.set(Calendar.MINUTE, 0); test.set(Calendar.SECOND, 0); } Date testTime = test.getTime(); Date fta = getFireTimeAfter(new Date(test.getTime().getTime() - 1000)); if(fta == null) return false; 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); } return fta.equals(testTime); } /** *

* 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) */ @Override public void triggered(org.quartz.Calendar calendar) { previousFireTime = nextFireTime; nextFireTime = getFireTimeAfter(nextFireTime); while (nextFireTime != null && calendar != null && !calendar.isTimeIncluded(nextFireTime.getTime())) { nextFireTime = getFireTimeAfter(nextFireTime); } } /** * * @see AbstractTrigger#updateWithNewCalendar(org.quartz.Calendar, long) */ @Override public void updateWithNewCalendar(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 // Use gregorian only because the constant is based on Gregorian java.util.Calendar c = new java.util.GregorianCalendar(); 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). */ @Override public Date computeFirstFireTime(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; } /* (non-Javadoc) * @see org.quartz.CronTriggerI#getExpressionSummary() */ 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; } /** * Get a {@link ScheduleBuilder} that is configured to produce a * schedule identical to this trigger's schedule. * * @see #getTriggerBuilder() */ @Override public ScheduleBuilder getScheduleBuilder() { CronScheduleBuilder cb = CronScheduleBuilder.cronSchedule(getCronExpression()) .inTimeZone(getTimeZone()); int misfireInstruction = getMisfireInstruction(); switch(misfireInstruction) { case MISFIRE_INSTRUCTION_SMART_POLICY: break; case MISFIRE_INSTRUCTION_DO_NOTHING: cb.withMisfireHandlingInstructionDoNothing(); break; case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: cb.withMisfireHandlingInstructionFireAndProceed(); break; case MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: cb.withMisfireHandlingInstructionIgnoreMisfires(); break; default: LOGGER.warn("Unrecognized misfire policy {}. Derived builder will use the default cron trigger behavior (MISFIRE_INSTRUCTION_FIRE_ONCE_NOW)", misfireInstruction); } return cb; } //////////////////////////////////////////////////////////////////////////// // // 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 eTime) { return (cronEx == null) ? null : cronEx.getTimeBefore(eTime); } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/DailyTimeIntervalTriggerImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import java.util.Calendar; import java.util.Date; import java.util.Set; import org.quartz.DailyTimeIntervalScheduleBuilder; import org.quartz.DailyTimeIntervalTrigger; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.ScheduleBuilder; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.TimeOfDay; import org.quartz.Trigger; import org.quartz.DateBuilder.IntervalUnit; /** * A concrete implementation of DailyTimeIntervalTrigger that is used to fire a {@link org.quartz.JobDetail} * based upon daily repeating time intervals. * *

The trigger will fire every N (see {@link #setRepeatInterval(int)} ) seconds, minutes or hours * (see {@link #setRepeatIntervalUnit(org.quartz.DateBuilder.IntervalUnit)}) during a given time window on specified days of the week.

* *

For example#1, a trigger can be set to fire every 72 minutes between 8:00 and 11:00 everyday. It's fire times would * be 8:00, 9:12, 10:24, then next day would repeat: 8:00, 9:12, 10:24 again.

* *

For example#2, a trigger can be set to fire every 23 minutes between 9:20 and 16:47 Monday through Friday.

* *

On each day, the starting fire time is reset to startTimeOfDay value, and then it will add repeatInterval value to it until * the endTimeOfDay is reached. If you set daysOfWeek values, then fire time will only occur during those week days period. Again, * remember this trigger will reset fire time each day with startTimeOfDay, regardless of your interval or endTimeOfDay!

* *

The default values for fields if not set are: startTimeOfDay defaults to 00:00:00, the endTimeOfDay default to 23:59:59, * and daysOfWeek is default to every day. The startTime default to current time-stamp now, while endTime has not value.

* *

If startTime is before startTimeOfDay, then startTimeOfDay will be used and startTime has no affect other than to specify * the first day of firing. Else if startTime is * after startTimeOfDay, then the first fire time for that day will be the next interval after the startTime. For example, if * you set startingTimeOfDay=9am, endingTimeOfDay=11am, interval=15 mins, and startTime=9:33am, then the next fire time will * be 9:45pm. Note also that if you do not set startTime value, the trigger builder will default to current time, and current time * maybe before or after the startTimeOfDay! So be aware how you set your startTime.

* *

This trigger also supports "repeatCount" feature to end the trigger fire time after * a certain number of count is reached. Just as the SimpleTrigger, setting repeatCount=0 * means trigger will fire once only! Setting any positive count then the trigger will repeat * count + 1 times. Unlike SimpleTrigger, the default value of repeatCount of this trigger * is set to REPEAT_INDEFINITELY instead of 0 though. * * @see DailyTimeIntervalTrigger * @see DailyTimeIntervalScheduleBuilder * * @since 2.1.0 * * @author James House * @author Zemian Deng <saltnlight5@gmail.com> */ public class DailyTimeIntervalTriggerImpl extends AbstractTrigger implements DailyTimeIntervalTrigger, CoreTrigger { private static final long serialVersionUID = -632667786771388749L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR) + 100; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Date startTime = null; private Date endTime = null; private Date nextFireTime = null; private Date previousFireTime = null; private int repeatCount = REPEAT_INDEFINITELY; private int repeatInterval = 1; private IntervalUnit repeatIntervalUnit = IntervalUnit.MINUTE; private Set daysOfWeek; private TimeOfDay startTimeOfDay; private TimeOfDay endTimeOfDay; private int timesTriggered = 0; private boolean complete = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a DailyTimeIntervalTrigger with no settings. *

*/ public DailyTimeIntervalTriggerImpl() { super(); } /** *

* Create a DailyTimeIntervalTrigger that will occur immediately, and * repeat at the given interval. *

* * @param startTimeOfDay * The TimeOfDay that the repeating should begin occurring. * @param endTimeOfDay * The TimeOfDay that the repeating should stop occurring. * @param intervalUnit The repeat interval unit. The only intervals that are valid for this type of trigger are * {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. * @throws IllegalArgumentException if an invalid IntervalUnit is given, or the repeat interval is zero or less. */ public DailyTimeIntervalTriggerImpl(String name, TimeOfDay startTimeOfDay, TimeOfDay endTimeOfDay, IntervalUnit intervalUnit, int repeatInterval) { this(name, null, startTimeOfDay, endTimeOfDay, intervalUnit, repeatInterval); } /** *

* Create a DailyTimeIntervalTrigger that will occur immediately, and * repeat at the given interval. *

* * @param startTimeOfDay * The TimeOfDay that the repeating should begin occurring. * @param endTimeOfDay * The TimeOfDay that the repeating should stop occurring. * @param intervalUnit The repeat interval unit. The only intervals that are valid for this type of trigger are * {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. * @throws IllegalArgumentException if an invalid IntervalUnit is given, or the repeat interval is zero or less. */ public DailyTimeIntervalTriggerImpl(String name, String group, TimeOfDay startTimeOfDay, TimeOfDay endTimeOfDay, IntervalUnit intervalUnit, int repeatInterval) { this(name, group, new Date(), null, startTimeOfDay, endTimeOfDay, intervalUnit, repeatInterval); } /** *

* Create a DailyTimeIntervalTrigger that will occur at the given time, * and repeat at the given interval 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 startTimeOfDay * The TimeOfDay that the repeating should begin occurring. * @param endTimeOfDay * The TimeOfDay that the repeating should stop occurring. * @param intervalUnit The repeat interval unit. The only intervals that are valid for this type of trigger are * {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. * @param repeatInterval * The number of milliseconds to pause between the repeat firing. * @throws IllegalArgumentException if an invalid IntervalUnit is given, or the repeat interval is zero or less. */ public DailyTimeIntervalTriggerImpl(String name, Date startTime, Date endTime, TimeOfDay startTimeOfDay, TimeOfDay endTimeOfDay, IntervalUnit intervalUnit, int repeatInterval) { this(name, null, startTime, endTime, startTimeOfDay, endTimeOfDay, intervalUnit, repeatInterval); } /** *

* Create a DailyTimeIntervalTrigger that will occur at the given time, * and repeat at the given interval 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 startTimeOfDay * The TimeOfDay that the repeating should begin occurring. * @param endTimeOfDay * The TimeOfDay that the repeating should stop occurring. * @param intervalUnit The repeat interval unit. The only intervals that are valid for this type of trigger are * {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. * @param repeatInterval * The number of milliseconds to pause between the repeat firing. * @throws IllegalArgumentException if an invalid IntervalUnit is given, or the repeat interval is zero or less. */ public DailyTimeIntervalTriggerImpl(String name, String group, Date startTime, Date endTime, TimeOfDay startTimeOfDay, TimeOfDay endTimeOfDay, IntervalUnit intervalUnit, int repeatInterval) { super(name, group); setStartTime(startTime); setEndTime(endTime); setRepeatIntervalUnit(intervalUnit); setRepeatInterval(repeatInterval); setStartTimeOfDay(startTimeOfDay); setEndTimeOfDay(endTimeOfDay); } /** *

* Create a DailyTimeIntervalTrigger that will occur at the given time, * fire the identified Job and repeat at the given * interval 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 startTimeOfDay * The TimeOfDay that the repeating should begin occurring. * @param endTimeOfDay * The TimeOfDay that the repeating should stop occurring. * @param intervalUnit The repeat interval unit. The only intervals that are valid for this type of trigger are * {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. * @param repeatInterval * The number of milliseconds to pause between the repeat firing. * @throws IllegalArgumentException if an invalid IntervalUnit is given, or the repeat interval is zero or less. */ public DailyTimeIntervalTriggerImpl(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, TimeOfDay startTimeOfDay, TimeOfDay endTimeOfDay, IntervalUnit intervalUnit, int repeatInterval) { super(name, group, jobName, jobGroup); setStartTime(startTime); setEndTime(endTime); setRepeatIntervalUnit(intervalUnit); setRepeatInterval(repeatInterval); setStartTimeOfDay(startTimeOfDay); setEndTimeOfDay(endTimeOfDay); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Get the time at which the DailyTimeIntervalTrigger should occur. It defaults to * the getStartTimeOfDay of current day. *

*/ @Override public Date getStartTime() { if(startTime == null) { startTime = new Date(); } return startTime; } /** *

* Set the time at which the DailyTimeIntervalTrigger should occur. *

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

* Get the time at which the DailyTimeIntervalTrigger should quit * repeating. *

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

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

* * @exception IllegalArgumentException * if endTime is before start time. */ @Override 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; } /* (non-Javadoc) * @see org.quartz.DailyTimeIntervalTriggerI#getRepeatIntervalUnit() */ public IntervalUnit getRepeatIntervalUnit() { return repeatIntervalUnit; } /** *

Set the interval unit - the time unit on with the interval applies.

* * @param intervalUnit The repeat interval unit. The only intervals that are valid for this type of trigger are * {@link IntervalUnit#SECOND}, {@link IntervalUnit#MINUTE}, and {@link IntervalUnit#HOUR}. */ public void setRepeatIntervalUnit(IntervalUnit intervalUnit) { if (repeatIntervalUnit == null || !((repeatIntervalUnit.equals(IntervalUnit.SECOND) || repeatIntervalUnit.equals(IntervalUnit.MINUTE) || repeatIntervalUnit.equals(IntervalUnit.HOUR)))) throw new IllegalArgumentException("Invalid repeat IntervalUnit (must be SECOND, MINUTE or HOUR)."); this.repeatIntervalUnit = intervalUnit; } /* (non-Javadoc) * @see org.quartz.DailyTimeIntervalTriggerI#getRepeatInterval() */ public int getRepeatInterval() { return repeatInterval; } /** *

* set the time interval that will be added to the DailyTimeIntervalTrigger's * fire time (in the set repeat interval unit) in order to calculate the time of the * next trigger repeat. *

* * @exception IllegalArgumentException * if repeatInterval is < 1 */ public void setRepeatInterval( int repeatInterval) { if (repeatInterval < 1) { throw new IllegalArgumentException( "Repeat interval must be >= 1"); } this.repeatInterval = repeatInterval; } /* (non-Javadoc) * @see org.quartz.DailyTimeIntervalTriggerI#getTimesTriggered() */ public int getTimesTriggered() { return timesTriggered; } /** *

* Set the number of times the DailyTimeIntervalTrigger has already * fired. *

*/ public void setTimesTriggered(int timesTriggered) { this.timesTriggered = timesTriggered; } @Override protected boolean validateMisfireInstruction(int misfireInstruction) { return misfireInstruction >= MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY && misfireInstruction <= MISFIRE_INSTRUCTION_DO_NOTHING; } /** *

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

* *

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

*
    *
  • The instruction will be interpreted as MISFIRE_INSTRUCTION_FIRE_ONCE_NOW *
*/ @Override public void updateAfterMisfire(org.quartz.Calendar cal) { int instr = getMisfireInstruction(); if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return; 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) { // fire once now... setNextFireTime(new Date()); // the new fire time afterward will magically preserve the original // time of day for firing for day/week/month interval triggers, // because of the way getFireTimeAfter() works - in its always restarting // computation from the start time. } } /** *

* 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) */ @Override public void triggered(org.quartz.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; } } if (nextFireTime == null) { complete = true; } } /** * @see org.quartz.impl.triggers.AbstractTrigger#updateWithNewCalendar(org.quartz.Calendar, long) */ @Override public void updateWithNewCalendar(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); } } } } /** *

* 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). */ @Override public Date computeFirstFireTime(org.quartz.Calendar calendar) { nextFireTime = getFireTimeAfter(new Date(getStartTime().getTime() - 1000L)); // Check calendar for date-time exclusion 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; } private Calendar createCalendarTime(Date dateTime) { Calendar cal = Calendar.getInstance(); cal.setTime(dateTime); return cal; } /** *

* 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. *

*/ @Override public Date getNextFireTime() { return nextFireTime; } /** *

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

* Set the next time at which the DailyTimeIntervalTrigger 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 DailyTimeIntervalTrigger 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 DailyTimeIntervalTrigger will * fire, after the given time. If the trigger will not fire after the given * time, null will be returned. *

*/ @Override public Date getFireTimeAfter(Date afterTime) { // Check if trigger has completed or not. if (complete) { return null; } // Check repeatCount limit if (repeatCount != REPEAT_INDEFINITELY && timesTriggered > repeatCount) { return null; } // a. Increment afterTime by a second, so that we are comparing against a time after it! if (afterTime == null) { afterTime = new Date(System.currentTimeMillis() + 1000L); } else { afterTime = new Date(afterTime.getTime() + 1000L); } // make sure afterTime is at least startTime if(afterTime.before(startTime)) afterTime = startTime; // b.Check to see if afterTime is after endTimeOfDay or not. If yes, then we need to advance to next day as well. boolean afterTimePastEndTimeOfDay = false; if (endTimeOfDay != null) { afterTimePastEndTimeOfDay = afterTime.getTime() > endTimeOfDay.getTimeOfDayForDate(afterTime).getTime(); } // c. now we need to move to the next valid day of week if either: // the given time is past the end time of day, or given time is not on a valid day of week Date fireTime = advanceToNextDayOfWeekIfNecessary(afterTime, afterTimePastEndTimeOfDay); if (fireTime == null) return null; // d. Calculate and save fireTimeEndDate variable for later use Date fireTimeEndDate; if (endTimeOfDay == null) fireTimeEndDate = new TimeOfDay(23, 59, 59).getTimeOfDayForDate(fireTime); else fireTimeEndDate = endTimeOfDay.getTimeOfDayForDate(fireTime); // e. Check fireTime against startTime or startTimeOfDay to see which go first. Date fireTimeStartDate = startTimeOfDay.getTimeOfDayForDate(fireTime); if (fireTime.before(fireTimeStartDate)) { return fireTimeStartDate; } // f. Continue to calculate the fireTime by incremental unit of intervals. // recall that if fireTime was less that fireTimeStartDate, we didn't get this far long fireMillis = fireTime.getTime(); long startMillis = fireTimeStartDate.getTime(); long secondsAfterStart = (fireMillis - startMillis) / 1000L; long repeatLong = getRepeatInterval(); Calendar sTime = createCalendarTime(fireTimeStartDate); IntervalUnit repeatUnit = getRepeatIntervalUnit(); if(repeatUnit.equals(IntervalUnit.SECOND)) { long jumpCount = secondsAfterStart / repeatLong; if(secondsAfterStart % repeatLong != 0) jumpCount++; sTime.add(Calendar.SECOND, getRepeatInterval() * (int)jumpCount); fireTime = sTime.getTime(); } else if(repeatUnit.equals(IntervalUnit.MINUTE)) { long jumpCount = secondsAfterStart / (repeatLong * 60L); if(secondsAfterStart % (repeatLong * 60L) != 0) jumpCount++; sTime.add(Calendar.MINUTE, getRepeatInterval() * (int)jumpCount); fireTime = sTime.getTime(); } else if(repeatUnit.equals(IntervalUnit.HOUR)) { long jumpCount = secondsAfterStart / (repeatLong * 60L * 60L); if(secondsAfterStart % (repeatLong * 60L * 60L) != 0) jumpCount++; sTime.add(Calendar.HOUR_OF_DAY, getRepeatInterval() * (int)jumpCount); fireTime = sTime.getTime(); } // g. Ensure this new fireTime is within the day, or else we need to advance to next day. if (fireTime.after(fireTimeEndDate)) { fireTime = advanceToNextDayOfWeekIfNecessary(fireTime, isSameDay(fireTime, fireTimeEndDate)); // make sure we hit the startTimeOfDay on the new day fireTime = startTimeOfDay.getTimeOfDayForDate(fireTime); } // i. Return calculated fireTime. return fireTime; } private boolean isSameDay(Date d1, Date d2) { Calendar c1 = createCalendarTime(d1); Calendar c2 = createCalendarTime(d2); return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR); } /** * Given fireTime time determine if it is on a valid day of week. If so, simply return it unaltered, * if not, advance to the next valid week day, and set the time of day to the start time of day * * @param fireTime - given next fireTime. * @param forceToAdvanceNextDay - flag to whether to advance day without check existing week day. This scenario * can happen when a caller determine fireTime has passed the endTimeOfDay that fireTime should move to next day anyway. * @return a next day fireTime. */ private Date advanceToNextDayOfWeekIfNecessary(Date fireTime, boolean forceToAdvanceNextDay) { // a. Advance or adjust to next dayOfWeek if need to first, starting next day with startTimeOfDay. TimeOfDay sTimeOfDay = getStartTimeOfDay(); Date fireTimeStartDate = sTimeOfDay.getTimeOfDayForDate(fireTime); Calendar fireTimeStartDateCal = createCalendarTime(fireTimeStartDate); int dayOfWeekOfFireTime = fireTimeStartDateCal.get(Calendar.DAY_OF_WEEK); // b2. We need to advance to another day if isAfterTimePassEndTimeOfDay is true, or dayOfWeek is not set. Set daysOfWeekToFire = getDaysOfWeek(); if (forceToAdvanceNextDay || !daysOfWeekToFire.contains(dayOfWeekOfFireTime)) { // Advance one day at a time until next available date. for(int i=1; i <= 7; i++) { fireTimeStartDateCal.add(Calendar.DATE, 1); dayOfWeekOfFireTime = fireTimeStartDateCal.get(Calendar.DAY_OF_WEEK); if (daysOfWeekToFire.contains(dayOfWeekOfFireTime)) { fireTime = fireTimeStartDateCal.getTime(); break; } } } // Check fireTime not pass the endTime Date eTime = getEndTime(); if (eTime != null && fireTime.getTime() > eTime.getTime()) { return null; } return fireTime; } /** *

* Returns the final time at which the DailyTimeIntervalTrigger will * fire, if there is no end time set, null will be returned. *

* *

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

*/ @Override public Date getFinalFireTime() { if (complete || getEndTime() == null) { return null; } // We have an endTime, we still need to check to see if there is a endTimeOfDay if that's applicable. Date eTime = getEndTime(); if (endTimeOfDay != null) { Date endTimeOfDayDate = endTimeOfDay.getTimeOfDayForDate(eTime); if (eTime.getTime() < endTimeOfDayDate.getTime()) { eTime = endTimeOfDayDate; } } return eTime; } /** *

* Determines whether or not the DailyTimeIntervalTrigger will occur * again. *

*/ @Override 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. */ @Override public void validate() throws SchedulerException { super.validate(); if (repeatIntervalUnit == null || !(repeatIntervalUnit.equals(IntervalUnit.SECOND) || repeatIntervalUnit.equals(IntervalUnit.MINUTE) ||repeatIntervalUnit.equals(IntervalUnit.HOUR))) throw new SchedulerException("Invalid repeat IntervalUnit (must be SECOND, MINUTE or HOUR)."); if (repeatInterval < 1) { throw new SchedulerException("Repeat Interval cannot be zero."); } // Ensure interval does not exceed 24 hours long secondsInHour = 24 * 60 * 60L; if (repeatIntervalUnit == IntervalUnit.SECOND && repeatInterval > secondsInHour) { throw new SchedulerException("repeatInterval can not exceed 24 hours (" + secondsInHour + " seconds). Given " + repeatInterval); } if (repeatIntervalUnit == IntervalUnit.MINUTE && repeatInterval > secondsInHour / 60L) { throw new SchedulerException("repeatInterval can not exceed 24 hours (" + secondsInHour / 60L + " minutes). Given " + repeatInterval); } if (repeatIntervalUnit == IntervalUnit.HOUR && repeatInterval > 24 ) { throw new SchedulerException("repeatInterval can not exceed 24 hours. Given " + repeatInterval + " hours."); } // Ensure timeOfDay is in order. // NOTE: We allow startTimeOfDay to be set equal to endTimeOfDay so the repeatCount can be // set to 1. if (getEndTimeOfDay() != null && !getStartTimeOfDay().equals(getEndTimeOfDay()) && !getStartTimeOfDay().before(getEndTimeOfDay())) { throw new SchedulerException("StartTimeOfDay " + startTimeOfDay + " should not come after endTimeOfDay " + endTimeOfDay); } } /** * {@inheritDoc} */ public Set getDaysOfWeek() { if (daysOfWeek == null) { daysOfWeek = DailyTimeIntervalScheduleBuilder.ALL_DAYS_OF_THE_WEEK; } return daysOfWeek; } public void setDaysOfWeek(Set daysOfWeek) { if(daysOfWeek == null || daysOfWeek.isEmpty()) throw new IllegalArgumentException("DaysOfWeek set must be a set that contains at least one day."); this.daysOfWeek = daysOfWeek; } /** * {@inheritDoc} */ public TimeOfDay getStartTimeOfDay() { if (startTimeOfDay == null) { startTimeOfDay = new TimeOfDay(0, 0, 0); } return startTimeOfDay; } public void setStartTimeOfDay(TimeOfDay startTimeOfDay) { if (startTimeOfDay == null) { throw new IllegalArgumentException("Start time of day cannot be null"); } TimeOfDay eTime = getEndTimeOfDay(); if (eTime != null && eTime.before(startTimeOfDay)) { throw new IllegalArgumentException( "End time of day cannot be before start time of day"); } this.startTimeOfDay = startTimeOfDay; } /** * {@inheritDoc} */ public TimeOfDay getEndTimeOfDay() { return endTimeOfDay; } public void setEndTimeOfDay(TimeOfDay endTimeOfDay) { if (endTimeOfDay == null) throw new IllegalArgumentException("End time of day cannot be null"); TimeOfDay sTime = getStartTimeOfDay(); if (sTime != null && endTimeOfDay.before(endTimeOfDay)) { throw new IllegalArgumentException( "End time of day cannot be before start time of day"); } this.endTimeOfDay = endTimeOfDay; } /** * Get a {@link ScheduleBuilder} that is configured to produce a * schedule identical to this trigger's schedule. * * @see #getTriggerBuilder() */ @Override public ScheduleBuilder getScheduleBuilder() { DailyTimeIntervalScheduleBuilder cb = DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule() .withInterval(getRepeatInterval(), getRepeatIntervalUnit()) .onDaysOfTheWeek(getDaysOfWeek()).startingDailyAt(getStartTimeOfDay()).endingDailyAt(getEndTimeOfDay()); switch(getMisfireInstruction()) { case MISFIRE_INSTRUCTION_DO_NOTHING : cb.withMisfireHandlingInstructionDoNothing(); break; case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW : cb.withMisfireHandlingInstructionFireAndProceed(); break; } return cb; } /** This trigger has no additional properties besides what's defined in this class. */ public boolean hasAdditionalProperties() { return false; } public int getRepeatCount() { return repeatCount; } 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; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/SimpleTriggerImpl.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import java.util.Date; import org.quartz.Calendar; import org.quartz.CronTrigger; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.ScheduleBuilder; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerUtils; /** *

* A concrete {@link Trigger} that is used to fire a {@link 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 SimpleTriggerImpl extends AbstractTrigger implements SimpleTrigger, CoreTrigger { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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; private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR) + 100; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 final boolean complete = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a SimpleTrigger with no settings. *

*/ public SimpleTriggerImpl() { super(); } /** *

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

* * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(String name) { this(name, (String)null); } /** *

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

* * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(String name, String group) { this(name, group, new Date(), null, 0, 0); } /** *

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

* * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(String name, int repeatCount, long repeatInterval) { this(name, null, repeatCount, repeatInterval); } /** *

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

* * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(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. *

* * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(String name, Date startTime) { this(name, null, startTime); } /** *

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

* * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(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 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. * * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(String name, Date startTime, Date endTime, int repeatCount, long repeatInterval) { this(name, null, startTime, endTime, repeatCount, repeatInterval); } /** *

* Create a SimpleTrigger that will occur at the given time, * and repeat at 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. * * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(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 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. * * @deprecated use a TriggerBuilder instead */ @Deprecated public SimpleTriggerImpl(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. *

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

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

* * @exception IllegalArgumentException * if startTime is null. */ @Override public void setStartTime(Date startTime) { if (startTime == null) { throw new IllegalArgumentException("Start time cannot be null"); } Date eTime = getEndTime(); if (eTime != 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 repeatCount isn't yet satisfied. *

* * @see #getFinalFireTime() */ @Override 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. */ @Override 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; } /* (non-Javadoc) * @see org.quartz.SimpleTriggerI#getRepeatCount() */ public int getRepeatCount() { return repeatCount; } /** *

* Set 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; } /* (non-Javadoc) * @see org.quartz.SimpleTriggerI#getRepeatInterval() */ public long getRepeatInterval() { return repeatInterval; } /** *

* Set 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; } @Override protected boolean validateMisfireInstruction(int misfireInstruction) { if (misfireInstruction < MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { return false; } return misfireInstruction <= MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT; } /** *

* 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:

*
    *
  • If the Repeat Count is 0, then the instruction will * be interpreted as MISFIRE_INSTRUCTION_FIRE_NOW.
  • *
  • If the Repeat Count is REPEAT_INDEFINITELY, then * the instruction will be interpreted as MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT. * WARNING: using MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT * with a trigger that has a non-null end-time may cause the trigger to * never fire again if the end-time arrived during the misfire time span. *
  • *
  • If the Repeat Count is > 0, then the instruction * will be interpreted as MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT. *
  • *
*/ @Override public void updateAfterMisfire(Calendar cal) { int instr = getMisfireInstruction(); if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return; 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) */ @Override 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 org.quartz.impl.triggers.AbstractTrigger#updateWithNewCalendar(org.quartz.Calendar, long) */ @Override 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). */ @Override 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; } /** *

* 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(org.quartz.spi.OperableTrigger, org.quartz.Calendar, java.util.Date, java.util.Date) */ @Override 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. */ @Override 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. *

*/ @Override 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. *

*/ @Override 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. *

*/ @Override 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. */ @Override public void validate() throws SchedulerException { super.validate(); if (repeatCount != 0 && repeatInterval < 1) { throw new SchedulerException("Repeat Interval cannot be zero."); } } /** * 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; } /** * Get a {@link ScheduleBuilder} that is configured to produce a * schedule identical to this trigger's schedule. * * @see #getTriggerBuilder() */ @Override public ScheduleBuilder getScheduleBuilder() { SimpleScheduleBuilder sb = SimpleScheduleBuilder.simpleSchedule() .withIntervalInMilliseconds(getRepeatInterval()) .withRepeatCount(getRepeatCount()); switch(getMisfireInstruction()) { case MISFIRE_INSTRUCTION_FIRE_NOW : sb.withMisfireHandlingInstructionFireNow(); break; case MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT : sb.withMisfireHandlingInstructionNextWithExistingCount(); break; case MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT : sb.withMisfireHandlingInstructionNextWithRemainingCount(); break; case MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT : sb.withMisfireHandlingInstructionNowWithExistingCount(); break; case MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT : sb.withMisfireHandlingInstructionNowWithRemainingCount(); break; } return sb; } } ================================================ FILE: quartz/src/main/java/org/quartz/impl/triggers/package.html ================================================ Package org.quartz.triggers

This package contains Trigger implementations that ship with Quartz. End-users should not directly use these classes, but rather use the builders and interfaces found in the main org.quartz package.




See the Quartz project for more information. ================================================ FILE: quartz/src/main/java/org/quartz/listeners/BroadcastJobListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.listeners; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; /** * Holds a List of references to JobListener instances and broadcasts all * events to them (in order). * *

The broadcasting behavior of this listener to delegate listeners 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(org.quartz.JobListener) * @see #removeListener(org.quartz.JobListener) * @see #removeListener(String) * * @author James House (jhouse AT revolition DOT net) */ public class BroadcastJobListener implements JobListener { private final String name; private final List listeners; /** * Construct an instance with the given name. * * (Remember to add some delegate listeners!) * * @param name the name of this instance */ public BroadcastJobListener(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 BroadcastJobListener(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 = itr.next(); if(jl.getName().equals(listenerName)) { itr.remove(); return true; } } return false; } public List getListeners() { return java.util.Collections.unmodifiableList(listeners); } public void jobToBeExecuted(JobExecutionContext context) { for (JobListener jl : listeners) { jl.jobToBeExecuted(context); } } public void jobExecutionVetoed(JobExecutionContext context) { for (JobListener jl : listeners) { jl.jobExecutionVetoed(context); } } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { for (JobListener jl : listeners) { jl.jobWasExecuted(context, jobException); } } } ================================================ FILE: quartz/src/main/java/org/quartz/listeners/BroadcastSchedulerListener.java ================================================ package org.quartz.listeners; import java.util.LinkedList; import java.util.List; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.SchedulerException; import org.quartz.SchedulerListener; import org.quartz.Trigger; import org.quartz.TriggerKey; /** * 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(org.quartz.SchedulerListener) * @see #removeListener(org.quartz.SchedulerListener) * * @author James House (jhouse AT revolition DOT net) */ public class BroadcastSchedulerListener implements SchedulerListener { private final 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 jobAdded(JobDetail jobDetail) { for (SchedulerListener l : listeners) { l.jobAdded(jobDetail); } } public void jobDeleted(JobKey jobKey) { for (SchedulerListener l : listeners) { l.jobDeleted(jobKey); } } public void jobScheduled(Trigger trigger) { for (SchedulerListener l : listeners) { l.jobScheduled(trigger); } } public void jobUnscheduled(TriggerKey triggerKey) { for (SchedulerListener l : listeners) { l.jobUnscheduled(triggerKey); } } public void triggerFinalized(Trigger trigger) { for (SchedulerListener l : listeners) { l.triggerFinalized(trigger); } } public void triggerPaused(TriggerKey key) { for (SchedulerListener l : listeners) { l.triggerPaused(key); } } public void triggersPaused(String triggerGroup) { for (SchedulerListener l : listeners) { l.triggersPaused(triggerGroup); } } public void triggerResumed(TriggerKey key) { for (SchedulerListener l : listeners) { l.triggerResumed(key); } } public void triggersResumed(String triggerGroup) { for (SchedulerListener l : listeners) { l.triggersResumed(triggerGroup); } } public void schedulingDataCleared() { for (SchedulerListener l : listeners) { l.schedulingDataCleared(); } } public void jobPaused(JobKey key) { for (SchedulerListener l : listeners) { l.jobPaused(key); } } public void jobsPaused(String jobGroup) { for (SchedulerListener l : listeners) { l.jobsPaused(jobGroup); } } public void jobResumed(JobKey key) { for (SchedulerListener l : listeners) { l.jobResumed(key); } } public void jobsResumed(String jobGroup) { for (SchedulerListener l : listeners) { l.jobsResumed(jobGroup); } } public void schedulerError(String msg, SchedulerException cause) { for (SchedulerListener l : listeners) { l.schedulerError(msg, cause); } } public void schedulerStarted() { for (SchedulerListener l : listeners) { l.schedulerStarted(); } } public void schedulerStarting() { for (SchedulerListener l : listeners) { l.schedulerStarting(); } } public void schedulerInStandbyMode() { for (SchedulerListener l : listeners) { l.schedulerInStandbyMode(); } } public void schedulerShutdown() { for (SchedulerListener l : listeners) { l.schedulerShutdown(); } } public void schedulerShuttingdown() { for (SchedulerListener l : listeners) { l.schedulerShuttingdown(); } } } ================================================ FILE: quartz/src/main/java/org/quartz/listeners/BroadcastTriggerListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.listeners; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.quartz.JobExecutionContext; import org.quartz.Trigger; import org.quartz.TriggerListener; import org.quartz.Trigger.CompletedExecutionInstruction; /** * Holds a List of references to TriggerListener instances and broadcasts all * events to them (in order). * *

The broadcasting behavior of this listener to delegate listeners 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(org.quartz.TriggerListener) * @see #removeListener(org.quartz.TriggerListener) * @see #removeListener(String) * * @author James House (jhouse AT revolition DOT net) */ public class BroadcastTriggerListener implements TriggerListener { private final String name; private final List listeners; /** * Construct an instance with the given name. * * (Remember to add some delegate listeners!) * * @param name the name of this instance */ public BroadcastTriggerListener(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 BroadcastTriggerListener(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 = itr.next(); if(l.getName().equals(listenerName)) { itr.remove(); return true; } } return false; } public List getListeners() { return java.util.Collections.unmodifiableList(listeners); } public void triggerFired(Trigger trigger, JobExecutionContext context) { for (TriggerListener l : listeners) { l.triggerFired(trigger, context); } } public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { for (TriggerListener l : listeners) { if (l.vetoJobExecution(trigger, context)) { return true; } } return false; } public void triggerMisfired(Trigger trigger) { for (TriggerListener l : listeners) { l.triggerMisfired(trigger); } } public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) { for (TriggerListener l : listeners) { l.triggerComplete(trigger, context, triggerInstructionCode); } } } ================================================ FILE: quartz/src/main/java/org/quartz/listeners/JobChainingJobListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.listeners; import java.util.HashMap; import java.util.Map; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import 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 * message 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 final String name; private final 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 JobKey with the name and group of the first job * @param secondJob a JobKey with the name and group of the follow-up job */ public void addJobChainLink(JobKey firstJob, JobKey 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); } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { JobKey sj = chainLinks.get(context.getJobDetail().getKey()); if(sj == null) { return; } getLog().info("Job '{}' will now chain to Job '{}'", context.getJobDetail().getKey(), sj); try { context.getScheduler().triggerJob(sj); } catch(SchedulerException se) { getLog().error("Error encountered during chaining to Job '{}'", sj, se); } } } ================================================ FILE: quartz/src/main/java/org/quartz/listeners/JobListenerSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.listeners; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.JobListener; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * A helpful abstract base class for implementors of * {@link org.quartz.JobListener}. * *

* The methods in this class are empty so you only need to override the * subset for the {@link org.quartz.JobListener} events * you care about. *

* *

* You are required to implement {@link org.quartz.JobListener#getName()} * to return the unique name of your JobListener. *

* * @see org.quartz.JobListener */ public abstract class JobListenerSupport implements JobListener { private final Logger log = LoggerFactory.getLogger(getClass()); /** * Get the {@link org.slf4j.Logger} for this * class's category. This should be used by subclasses for logging. */ protected Logger getLog() { return log; } public void jobToBeExecuted(JobExecutionContext context) { } public void jobExecutionVetoed(JobExecutionContext context) { } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { } } ================================================ FILE: quartz/src/main/java/org/quartz/listeners/SchedulerListenerSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.listeners; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.SchedulerException; import org.quartz.SchedulerListener; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A helpful abstract base class for implementors of * {@link org.quartz.SchedulerListener}. * *

* The methods in this class are empty so you only need to override the * subset for the {@link org.quartz.SchedulerListener} events * you care about. *

* * @see org.quartz.SchedulerListener */ public abstract class SchedulerListenerSupport implements SchedulerListener { private final Logger log = LoggerFactory.getLogger(getClass()); /** * Get the {@link org.slf4j.Logger} for this * class's category. This should be used by subclasses for logging. */ protected Logger getLog() { return log; } public void jobAdded(JobDetail jobDetail) { } public void jobDeleted(JobKey jobKey) { } public void jobPaused(JobKey jobKey) { } public void jobResumed(JobKey jobKey) { } public void jobScheduled(Trigger trigger) { } public void jobsPaused(String jobGroup) { } public void jobsResumed(String jobGroup) { } public void jobUnscheduled(TriggerKey triggerKey) { } public void schedulerError(String msg, SchedulerException cause) { } public void schedulerInStandbyMode() { } public void schedulerShutdown() { } public void schedulerShuttingdown() { } public void schedulerStarted() { } public void schedulerStarting() { } public void triggerFinalized(Trigger trigger) { } public void triggerPaused(TriggerKey triggerKey) { } public void triggerResumed(TriggerKey triggerKey) { } public void triggersPaused(String triggerGroup) { } public void triggersResumed(String triggerGroup) { } public void schedulingDataCleared() { } } ================================================ FILE: quartz/src/main/java/org/quartz/listeners/TriggerListenerSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.listeners; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.TriggerListener; import org.quartz.Trigger; import org.quartz.JobExecutionContext; import org.quartz.Trigger.CompletedExecutionInstruction; /** * A helpful abstract base class for implementors of * {@link org.quartz.TriggerListener}. * *

* The methods in this class are empty so you only need to override the * subset for the {@link org.quartz.TriggerListener} events * you care about. *

* *

* You are required to implement {@link org.quartz.TriggerListener#getName()} * to return the unique name of your TriggerListener. *

* * @see org.quartz.TriggerListener */ public abstract class TriggerListenerSupport implements TriggerListener { private final Logger log = LoggerFactory.getLogger(getClass()); /** * Get the {@link org.slf4j.Logger} for this * class's category. This should be used by subclasses for logging. */ protected Logger 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, CompletedExecutionInstruction triggerInstructionCode) { } } ================================================ FILE: quartz/src/main/java/org/quartz/management/ManagementRESTServiceConfiguration.java ================================================ /** * Copyright Terracotta, Inc. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.management; /** * Configuration class of management REST services. * * @author Ludovic Orban * * TODO : could be merged with ehcache * ManagementRESTServiceConfiguration in a common module */ public class ManagementRESTServiceConfiguration { /** * Default bind value. */ public static final String DEFAULT_BIND = "0.0.0.0:9888"; /** * Default timeout for the connection to the configured security service */ public static final int DEFAULT_SECURITY_SVC_TIMEOUT = 5 * 1000; private volatile boolean enabled = false; private volatile String securityServiceLocation; private volatile int securityServiceTimeout = DEFAULT_SECURITY_SVC_TIMEOUT; private volatile String bind = DEFAULT_BIND; // private volatile int sampleHistorySize = // CacheStatisticsSampler.DEFAULT_HISTORY_SIZE; // private volatile int sampleIntervalSeconds = // CacheStatisticsSampler.DEFAULT_INTERVAL_SECS; // private volatile int sampleSearchIntervalSeconds = // CacheStatisticsSampler.DEFAULT_SEARCH_INTERVAL_SEC; /** * Check if the REST services should be enabled or not. * @return true if REST services should be enabled. */ public boolean isEnabled() { return enabled; } /** * Set that the REST services should be enabled or disabled. * @param enabled true if the REST services should be enabled. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Returns the security service location required for trusted identity assertion to the embedded REST management * service. This feature is only available with an enterprise license. *

* If this value is set, then this service will require secure dialog with the TMS or other 3rd party REST client * implementations. The service furnished by the enterprise version of the TMC is located is provided at /api/assertIdentity. *

* * @return a string representing the URL of the security service. */ public String getSecurityServiceLocation() { return securityServiceLocation; } /** * Sets the security service location required for trusted identity assertion to the embedded REST management * service. This feature is only available with an enterprise license. *

* If this value is set, then this service will require secure dialog with the TMS or other 3rd party REST client * implementations. The service furnished by the enterprise version of the TMC is located is provided at /api/assertIdentity. *

* * @param securityServiceURL a string representing the URL of the security service. */ public void setSecurityServiceLocation(String securityServiceURL) { this.securityServiceLocation = securityServiceURL; } /** * Returns the connection/read timeout value for the security service in milliseconds. * * @return security service timeout */ public int getSecurityServiceTimeout() { return securityServiceTimeout; } /** * Sets the connection/read timeout value for the security service in milliseconds. * * @param securityServiceTimeout milliseconds to timeout */ public void setSecurityServiceTimeout(int securityServiceTimeout) { this.securityServiceTimeout = securityServiceTimeout; } /** * Get the host:port pair to which the REST server should be bound. * Format is: [IP address|host name]:[port number] * @return the host:port pair to which the REST server should be bound. */ public String getBind() { return bind; } /** * Get the host part of the host:port pair to which the REST server should be bound. * @return the host part of the host:port pair to which the REST server should be bound. */ public String getHost() { if (bind == null) { return null; } return bind.split("\\:")[0]; } /** * Get the port part of the host:port pair to which the REST server should be bound. * @return the port part of the host:port pair to which the REST server should be bound. */ public int getPort() { if (bind == null) { return -1; } String[] split = bind.split("\\:"); if (split.length != 2) { throw new IllegalArgumentException("invalid bind format (should be IP:port)"); } return Integer.parseInt(split[1]); } /** * Set the host:port pair to which the REST server should be bound. * @param bind host:port pair to which the REST server should be bound. */ public void setBind(String bind) { this.bind = bind; } /** * Returns the sample history size to be applied to the {@link SampledCounterConfig} for sampled statistics * * @return the sample history size */ // public int getSampleHistorySize() { // return sampleHistorySize; // } /** * Sets the sample history size to be applied to the {@link SampledCounterConfig} for sampled statistics * * @param sampleHistorySize to set */ // public void setSampleHistorySize(final int sampleHistorySize) { // this.sampleHistorySize = sampleHistorySize; // } /** * Returns the sample interval in seconds to be applied to the {@link SampledCounterConfig} for sampled statistics * * @return the sample interval in seconds */ // public int getSampleIntervalSeconds() { // return sampleIntervalSeconds; // } /** * Sets the sample interval in seconds to be applied to the {@link SampledCounterConfig} for sampled statistics * * @param sampleIntervalSeconds to set */ // public void setSampleIntervalSeconds(final int sampleIntervalSeconds) { // this.sampleIntervalSeconds = sampleIntervalSeconds; // } /** * Returns the sample search interval in seconds to be applied to the {@link SampledRateCounterConfig} for sampled statistics * * @return the sample search interval in seconds */ // public int getSampleSearchIntervalSeconds() { // return sampleSearchIntervalSeconds; // } /** * Sets the sample search interval in seconds to be applied to the {@link SampledCounterConfig} for sampled statistics * * @param sampleSearchInterval to set */ // public void setSampleSearchIntervalSeconds(final int // sampleSearchInterval) { // this.sampleSearchIntervalSeconds = sampleSearchInterval; // } /** * A factory method for {@link SampledCounterConfig} based on the global settings defined on this object * * @see #getSampleIntervalSeconds() * @see #getSampleHistorySize() * * @return a {@code SampledCounterConfig} */ // public SampledCounterConfig makeSampledCounterConfig() { // return new SampledCounterConfig(getSampleIntervalSeconds(), // getSampleHistorySize(), true, 0L); // } /** * A factory method for {@link SampledCounterConfig} based on the global settings defined on this object * * @see #getSampleIntervalSeconds() * @see #getSampleHistorySize() * * @return a {@code SampledCounterConfig} */ // public SampledRateCounterConfig makeSampledGetRateCounterConfig() { // return new SampledRateCounterConfig(getSampleIntervalSeconds(), // getSampleHistorySize(), true); // } /** * A factory method for {@link SampledCounterConfig} based on the global settings defined on this object * * @see #getSampleSearchIntervalSeconds() * @see #getSampleHistorySize() * * @return a {@code SampledCounterConfig} */ // public SampledRateCounterConfig makeSampledSearchRateCounterConfig() { // return new SampledRateCounterConfig(getSampleSearchIntervalSeconds(), // getSampleHistorySize(), true); // } } ================================================ FILE: quartz/src/main/java/org/quartz/management/ManagementServer.java ================================================ /** * Copyright Terracotta, Inc. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.management; import org.quartz.core.QuartzScheduler; /** * Interface implemented by management servers. * * @author Ludovic Orban * @author brandony */ public interface ManagementServer { /** * Start the management server */ void start(); /** * Stop the management server */ void stop(); /** * Puts the submitted resource under the purview of this {@code ManagementServer}. * * @param managedResource the resource to be managed */ void register(QuartzScheduler managedResource); /** * Removes the submitted resource under the purview of this {@code ManagementServer}. * * @param managedResource the resource to be managed */ void unregister(QuartzScheduler managedResource); /** * Returns true if this {@code ManagementServer} has any resources registered. * * @return true if actively managing resources, false if not. */ boolean hasRegistered(); } ================================================ FILE: quartz/src/main/java/org/quartz/package.html ================================================ Package org.quartz

The main package of Quartz, containing the client-side interfaces.




See the Quartz project for more information.

Quartz provides a builder-style API for constructing scheduling-related entities via a Domain-Specific Language (DSL). The DSL can best be utilized through the usage of static imports of the methods on the classes TriggerBuilder, JobBuilder, DateBuilder, JobKey, TriggerKey and the various ScheduleBuilder implementations.

Client code can then use the DSL to write code such as this:

        JobDetail job = newJob(MyJob.class)
            .withIdentity("myJob")
            .build();
            
        Trigger trigger = newTrigger() 
            .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
            .withSchedule(simpleSchedule()
                .withIntervalInHours(1)
                .repeatForever())
            .startAt(futureDate(10, MINUTES))
            .build();
        
        scheduler.scheduleJob(job, trigger);
================================================ FILE: quartz/src/main/java/org/quartz/plugins/SchedulerPluginWithUserTransactionSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins; import jakarta.transaction.Status; import jakarta.transaction.UserTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.ee.jta.UserTransactionHelper; import 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 Logger log = LoggerFactory.getLogger(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 UserTransaction 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 UserTransaction 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 Logger for this class. */ protected Logger 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 pname, Scheduler sched) throws SchedulerException { this.name = pname; this.scheduler = sched; } /** * 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) { 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/rolled back, * 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); } } } } ================================================ FILE: quartz/src/main/java/org/quartz/plugins/history/LoggingJobHistoryPlugin.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.history; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.JobListener; import org.quartz.impl.matchers.EverythingMatcher; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.SchedulerPlugin; import java.text.MessageFormat; /** * Logs a history of all job executions (and execution vetoes) via the slf4j. * *

* 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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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 Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public LoggingJobHistoryPlugin() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger 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 pname, Scheduler scheduler,ClassLoadHelper classLoadHelper) throws SchedulerException { this.name = pname; scheduler.getListenerManager().addJobListener(this, EverythingMatcher.allJobs()); } 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 org.quartz.JobListener#jobToBeExecuted(JobExecutionContext) */ public void jobToBeExecuted(JobExecutionContext context) { if (!getLog().isInfoEnabled()) { return; } Trigger trigger = context.getTrigger(); Object[] args = { context.getJobDetail().getKey().getName(), context.getJobDetail().getKey().getGroup(), new java.util.Date(), trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), context.getRefireCount() }; getLog().info(MessageFormat.format(getJobToBeFiredMessage(), args)); } /** * @see org.quartz.JobListener#jobWasExecuted(JobExecutionContext, JobExecutionException) */ public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { Trigger trigger = context.getTrigger(); Object[] args; if (jobException != null) { if (!getLog().isWarnEnabled()) { return; } String errMsg = jobException.getMessage(); args = new Object[] { context.getJobDetail().getKey().getName(), context.getJobDetail().getKey().getGroup(), new java.util.Date(), trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), 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().getKey().getName(), context.getJobDetail().getKey().getGroup(), new java.util.Date(), trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), context.getRefireCount(), result }; getLog().info(MessageFormat.format(getJobSuccessMessage(), args)); } } /** * @see org.quartz.JobListener#jobExecutionVetoed(org.quartz.JobExecutionContext) */ public void jobExecutionVetoed(JobExecutionContext context) { if (!getLog().isInfoEnabled()) { return; } Trigger trigger = context.getTrigger(); Object[] args = { context.getJobDetail().getKey().getName(), context.getJobDetail().getKey().getGroup(), new java.util.Date(), trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), context.getRefireCount() }; getLog().info(MessageFormat.format(getJobWasVetoedMessage(), args)); } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/plugins/history/LoggingTriggerHistoryPlugin.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.history; import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.JobExecutionContext; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerListener; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.impl.matchers.EverythingMatcher; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.SchedulerPlugin; /** * Logs a history of all trigger firings via slf4j. * *

* 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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
List of available data for messages.
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 Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public LoggingTriggerHistoryPlugin() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger 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 pname, Scheduler scheduler, ClassLoadHelper classLoadHelper) throws SchedulerException { this.name = pname; scheduler.getListenerManager().addTriggerListener(this, EverythingMatcher.allTriggers()); } 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.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), new java.util.Date(), context.getJobDetail().getKey().getName(), context.getJobDetail().getKey().getGroup(), context.getRefireCount() }; getLog().info(MessageFormat.format(getTriggerFiredMessage(), args)); } public void triggerMisfired(Trigger trigger) { if (!getLog().isInfoEnabled()) { return; } Object[] args = { trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), new java.util.Date(), trigger.getJobKey().getName(), trigger.getJobKey().getGroup() }; getLog().info(MessageFormat.format(getTriggerMisfiredMessage(), args)); } public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) { if (!getLog().isInfoEnabled()) { return; } String instrCode = "UNKNOWN"; if (triggerInstructionCode == CompletedExecutionInstruction.DELETE_TRIGGER) { instrCode = "DELETE TRIGGER"; } else if (triggerInstructionCode == CompletedExecutionInstruction.NOOP) { instrCode = "DO NOTHING"; } else if (triggerInstructionCode == CompletedExecutionInstruction.RE_EXECUTE_JOB) { instrCode = "RE-EXECUTE JOB"; } else if (triggerInstructionCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) { instrCode = "SET ALL OF JOB'S TRIGGERS COMPLETE"; } else if (triggerInstructionCode == CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) { instrCode = "SET THIS TRIGGER COMPLETE"; } Object[] args = { trigger.getKey().getName(), trigger.getKey().getGroup(), trigger.getPreviousFireTime(), trigger.getNextFireTime(), new java.util.Date(), context.getJobDetail().getKey().getName(), context.getJobDetail().getKey().getGroup(), context.getRefireCount(), triggerInstructionCode.toString(), instrCode }; getLog().info(MessageFormat.format(getTriggerCompleteMessage(), args)); } public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { return false; } } ================================================ FILE: quartz/src/main/java/org/quartz/plugins/interrupt/JobInterruptMonitorPlugin.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.interrupt; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.listeners.TriggerListenerSupport; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.SchedulerPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This plugin catches the event of job running for a long time (more than the * configured max time) and tells the scheduler to "try" interrupting it if * enabled. * * @see org.quartz.Scheduler#interrupt(JobKey) * * @author Rama Chavali */ public class JobInterruptMonitorPlugin extends TriggerListenerSupport implements SchedulerPlugin { private static final String JOB_INTERRUPT_MONITOR_KEY = "JOB_INTERRUPT_MONITOR_KEY"; private long DEFAULT_MAX_RUNTIME = 300000; private String name; private ScheduledExecutorService executor; @SuppressWarnings("rawtypes") private ScheduledFuture future; private Scheduler scheduler; private final Logger log = LoggerFactory.getLogger(getClass()); //Constants public static final String AUTO_INTERRUPTIBLE = "AutoInterruptable"; public static final String MAX_RUN_TIME = "MaxRunTime"; public JobInterruptMonitorPlugin() { } @Override public void start() { } @Override public void shutdown() { this.executor.shutdown(); } protected Logger getLog() { return log; } @SuppressWarnings("rawtypes") public ScheduledFuture scheduleJobInterruptMonitor(JobKey jobkey, long delay) { return this.executor.schedule(new InterruptMonitor(jobkey, scheduler), delay, TimeUnit.MILLISECONDS); } // Bean Property Methods public long getDefaultMaxRunTime() { return this.DEFAULT_MAX_RUNTIME; } public void setDefaultMaxRunTime(long defaultMaxRunTime) { this.DEFAULT_MAX_RUNTIME = defaultMaxRunTime; } // Trigger Listener Methods public String getName() { return name; } public void triggerFired(Trigger trigger, JobExecutionContext context) { // Call the scheduleJobInterruptMonitor and capture the ScheduledFuture in context try { // Schedule Monitor only if the job wants AutoInterruptable functionality if (context.getJobDetail().getJobDataMap().getBoolean(AUTO_INTERRUPTIBLE)) { JobInterruptMonitorPlugin monitorPlugin = (JobInterruptMonitorPlugin) context.getScheduler() .getContext().get(JOB_INTERRUPT_MONITOR_KEY); // Get the MaxRuntime from Job Data if NOT available use DEFAULT_MAX_RUNTIME from Plugin Configuration long jobDataDelay = DEFAULT_MAX_RUNTIME; if (context.getJobDetail().getJobDataMap().get(MAX_RUN_TIME) != null){ jobDataDelay = context.getJobDetail().getJobDataMap().getLong(MAX_RUN_TIME); } future = monitorPlugin.scheduleJobInterruptMonitor(context.getJobDetail().getKey(), jobDataDelay); getLog().debug("Job's Interrupt Monitor has been scheduled to interrupt with the delay :{}", DEFAULT_MAX_RUNTIME); } } catch (SchedulerException e) { getLog().info("Error scheduling interrupt monitor {}", e.getMessage(), e); } } public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) { // cancel the Future if job is complete if (future != null) { future.cancel(true); } } @Override public void initialize(String name, Scheduler scheduler, ClassLoadHelper helper) throws SchedulerException { getLog().info("Registering Job Interrupt Monitor Plugin"); this.name = name; this.executor = Executors.newScheduledThreadPool(1); scheduler.getContext().put(JOB_INTERRUPT_MONITOR_KEY, this); this.scheduler = scheduler; // Set the trigger Listener as this class to the ListenerManager here this.scheduler.getListenerManager().addTriggerListener(this); } static class InterruptMonitor implements Runnable { private final JobKey jobKey; private final Scheduler scheduler; private final Logger log = LoggerFactory.getLogger(getClass()); InterruptMonitor(JobKey jobKey, Scheduler scheduler) { this.jobKey = jobKey; this.scheduler = scheduler; } protected Logger getLog() { return log; } @Override public void run() { try { // Interrupt the job here - using Scheduler API that gets propagated to Job's interrupt getLog().info("Interrupting Job as it ran more than the configured max time. Job Details [{}:{}]", jobKey.getName(), jobKey.getGroup()); scheduler.interrupt(jobKey); } catch (SchedulerException x) { getLog().info("Error interrupting Job: {}", x.getMessage(), x); } } } } ================================================ FILE: quartz/src/main/java/org/quartz/plugins/management/ShutdownHookPlugin.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.management; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Scheduler; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.SchedulerPlugin; /** * This plugin catches the event of the JVM terminating (such as upon a CTRL-C) * and tells the scheduler to shutdown. * * @see org.quartz.Scheduler#shutdown(boolean) * * @author James House */ public class ShutdownHookPlugin implements SchedulerPlugin { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private boolean cleanShutdown = true; private final Logger log = LoggerFactory.getLogger(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 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 org.quartz.Scheduler#shutdown(boolean) */ public void setCleanShutdown(boolean b) { cleanShutdown = b; } protected Logger 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, ClassLoadHelper classLoadHelper) throws SchedulerException { getLog().info("Registering Quartz shutdown hook."); Thread t = new Thread("Quartz Shutdown-Hook " + scheduler.getSchedulerName()) { @Override 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 ================================================ FILE: quartz/src/main/java/org/quartz/plugins/xml/FileScanJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.xml; import org.quartz.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.net.URL; import java.net.URLDecoder; /** * 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 org.quartz.jobs.FileScanListener */ @DisallowConcurrentExecution @PersistJobDataAfterExecution public class FileScanJob implements Job { /** * JobDataMap key with which to specify * the name of the file to monitor. */ public static final String FILE_NAME = "FILE_NAME"; /** * JobDataMap key with which to specify the * {@link org.quartz.jobs.FileScanListener} to be * notified when the file contents change. */ public static final String FILE_SCAN_LISTENER_NAME = "FILE_SCAN_LISTENER_NAME"; /** * JobDataMap key with which to specify a long * value that represents the minimum number of milliseconds that must have * past since the file's last modified time in order to consider the file * new/altered. This is necessary because another process may still be * in the middle of writing to the file when the scan occurs, and the * file may therefore not yet be ready for processing. * *

If this parameter is not specified, a default value of * 5000 (five seconds) will be used.

*/ public static final String MINIMUM_UPDATE_AGE = "MINIMUM_UPDATE_AGE"; private static final String LAST_MODIFIED_TIME = "LAST_MODIFIED_TIME"; private final Logger log = LoggerFactory.getLogger(getClass()); public FileScanJob() { } /** * @see Job#execute(JobExecutionContext) */ public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); SchedulerContext schedCtx; try { schedCtx = 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)schedCtx.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 minAge = 5000; if(mergedJobDataMap.containsKey(MINIMUM_UPDATE_AGE)) { minAge = mergedJobDataMap.getLong(MINIMUM_UPDATE_AGE); } long maxAgeDate = System.currentTimeMillis() + minAge; long newDate = getLastModifiedDate(fileName); if(newDate < 0) { log.warn("File '{}' does not exist.", fileName); return; } if(lastDate > 0 && (newDate > lastDate && newDate < maxAgeDate)) { // notify call back... log.info("File '{}' updated, notifying listener.", fileName); listener.fileUpdated(fileName); } else if (log.isDebugEnabled()) { log.debug("File '{}' unchanged.", fileName); } // 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(); } } } ================================================ FILE: quartz/src/main/java/org/quartz/plugins/xml/FileScanListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.xml; /** * Interface for objects wishing to receive a 'call-back' from a * FileScanJob. * * @author jhouse * @see FileScanJob */ public interface FileScanListener { void fileUpdated(String fileName); } ================================================ FILE: quartz/src/main/java/org/quartz/plugins/xml/XMLSchedulingDataProcessorPlugin.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.plugins.xml; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import jakarta.transaction.UserTransaction; import org.quartz.plugins.xml.FileScanJob; import org.quartz.plugins.xml.FileScanListener; import org.quartz.plugins.SchedulerPluginWithUserTransactionSupport; import org.quartz.spi.ClassLoadHelper; import org.quartz.xml.XMLSchedulingDataProcessor; import org.quartz.*; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; /** * This plugin loads XML file(s) to add jobs and schedule them with triggers * as the scheduler is initialized, and can optionally periodically scan the * file for changes. * *

The XML schema definition can be found here: * http://www.quartz-scheduler.org/xml/job_scheduling_data_2_0.xsd

* *

* The periodically scanning of files for changes is not currently supported in a * clustered environment. *

* *

* If using this plugin with JobStoreCMT, be sure to set the * plugin property wrapInUserTransaction to true. Also, if you have a * positive scanInterval be sure to set * org.quartz.scheduler.wrapJobExecutionInUserTransaction to true. *

* * @see org.quartz.xml.XMLSchedulingDataProcessor * * @author James House * @author Pierre Awaragi * @author pl47ypus */ public class XMLSchedulingDataProcessorPlugin extends SchedulerPluginWithUserTransactionSupport implements FileScanListener { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private static final int MAX_JOB_TRIGGER_NAME_LEN = 80; private static final String JOB_INITIALIZATION_PLUGIN_NAME = "JobSchedulingDataLoaderPlugin"; private static final String FILE_NAME_DELIMITERS = ","; private boolean failOnFileNotFound = true; private String fileNames = XMLSchedulingDataProcessor.QUARTZ_XML_DEFAULT_FILE_NAME; // Populated by initialization private final Map jobFiles = new LinkedHashMap<>(); private long scanInterval = 0; boolean started = false; protected ClassLoadHelper classLoadHelper = null; private final Set jobTriggerNameSet = new HashSet<>(); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public XMLSchedulingDataProcessorPlugin() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Comma separated list of file names (with paths) to the XML files that should be read. */ public String getFileNames() { return fileNames; } /** * The file name (and path) to the XML file that should be read. */ public void setFileNames(String fileNames) { this.fileNames = fileNames; } /** * The interval (in seconds) at which to scan for changes to the file. * If the file has been changed, it is re-loaded and parsed. The default * value for the interval is 0, which disables scanning. * * @return Returns the scanInterval. */ public long getScanInterval() { return scanInterval / 1000; } /** * The interval (in seconds) at which to scan for changes to the file. * If the file has been changed, it is re-loaded and parsed. The default * value for the interval is 0, which disables scanning. * * @param scanInterval The scanInterval to set. */ public void setScanInterval(long scanInterval) { this.scanInterval = scanInterval * 1000; } /** * Whether or not initialization of the plugin should fail (throw an * exception) if the file cannot be found. Default is true. */ public boolean isFailOnFileNotFound() { return failOnFileNotFound; } /** * Whether or not initialization of the plugin should fail (throw an * exception) if the file cannot be found. Default is true. */ public void setFailOnFileNotFound(boolean failOnFileNotFound) { this.failOnFileNotFound = failOnFileNotFound; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * SchedulerPlugin Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Called during creation of the Scheduler in order to give * the SchedulerPlugin a chance to initialize. *

* * @throws org.quartz.SchedulerConfigException * if there is an error initializing. */ public void initialize(String name, final Scheduler scheduler, ClassLoadHelper schedulerFactoryClassLoadHelper) throws SchedulerException { super.initialize(name, scheduler); this.classLoadHelper = schedulerFactoryClassLoadHelper; getLog().info("Registering Quartz Job Initialization Plug-in."); // Create JobFile objects StringTokenizer stok = new StringTokenizer(fileNames, FILE_NAME_DELIMITERS); while (stok.hasMoreTokens()) { final String fileName = stok.nextToken(); final JobFile jobFile = new JobFile(fileName); jobFiles.put(fileName, jobFile); } } @Override public void start(UserTransaction userTransaction) { try { if (!jobFiles.isEmpty()) { if (scanInterval > 0) { getScheduler().getContext().put(JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName(), this); } Iterator iterator = jobFiles.values().iterator(); while (iterator.hasNext()) { JobFile jobFile = iterator.next(); if (scanInterval > 0) { String jobTriggerName = buildJobTriggerName(jobFile.getFileBasename()); TriggerKey tKey = new TriggerKey(jobTriggerName, JOB_INITIALIZATION_PLUGIN_NAME); // remove preexisting job/trigger, if any getScheduler().unscheduleJob(tKey); JobDetail job = newJob().withIdentity(jobTriggerName, JOB_INITIALIZATION_PLUGIN_NAME).ofType(FileScanJob.class) .usingJobData(FileScanJob.FILE_NAME, jobFile.getFileName()) .usingJobData(FileScanJob.FILE_SCAN_LISTENER_NAME, JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName()) .build(); SimpleTrigger trig = newTrigger().withIdentity(tKey).withSchedule( simpleSchedule().repeatForever().withIntervalInMilliseconds(scanInterval)) .forJob(job) .build(); getScheduler().scheduleJob(job, trig); getLog().debug("Scheduled file scan job for data file: {}, at interval: {}", jobFile.getFileName(), scanInterval); } processFile(jobFile); } } } catch(SchedulerException se) { getLog().error("Error starting background-task for watching jobs file.", se); } finally { started = true; } } /** * Helper method for generating unique job/trigger name for the * file scanning jobs (one per FileJob). The unique names are saved * in jobTriggerNameSet. */ private String buildJobTriggerName( String fileBasename) { // Name w/o collisions will be prefix + _ + filename (with '.' of filename replaced with '_') // For example: JobInitializationPlugin_jobInitializer_myjobs_xml String jobTriggerName = JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName() + '_' + fileBasename.replace('.', '_'); // If name is too long (DB column is 80 chars), then truncate to max length if (jobTriggerName.length() > MAX_JOB_TRIGGER_NAME_LEN) { jobTriggerName = jobTriggerName.substring(0, MAX_JOB_TRIGGER_NAME_LEN); } // Make sure this name is unique in case the same file name under different // directories is being checked, or had a naming collision due to length truncation. // If there is a conflict, keep incrementing a _# suffix on the name (being sure // not to get too long), until we find a unique name. int currentIndex = 1; while (!jobTriggerNameSet.add(jobTriggerName)) { // If not our first time through, then strip off old numeric suffix if (currentIndex > 1) { jobTriggerName = jobTriggerName.substring(0, jobTriggerName.lastIndexOf('_')); } String numericSuffix = "_" + currentIndex++; // If the numeric suffix would make the name too long, then make room for it. if (jobTriggerName.length() > (MAX_JOB_TRIGGER_NAME_LEN - numericSuffix.length())) { jobTriggerName = jobTriggerName.substring(0, (MAX_JOB_TRIGGER_NAME_LEN - numericSuffix.length())); } jobTriggerName += numericSuffix; } return jobTriggerName; } /** * Overridden to ignore wrapInUserTransaction because shutdown() * does not interact with the Scheduler. */ @Override public void shutdown() { // Since we have nothing to do, override base shutdown so don't // get extraneous UserTransactions. } private void processFile(JobFile jobFile) { if (jobFile == null || !jobFile.getFileFound()) { return; } try { XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(this.classLoadHelper); processor.addJobGroupToNeverDelete(JOB_INITIALIZATION_PLUGIN_NAME); processor.addTriggerGroupToNeverDelete(JOB_INITIALIZATION_PLUGIN_NAME); processor.processFileAndScheduleJobs( jobFile.getFileName(), jobFile.getFileName(), // systemId getScheduler()); } catch (Exception e) { getLog().error("Error scheduling jobs: {}", e.getMessage(), e); } } public void processFile(String filePath) { processFile((JobFile)jobFiles.get(filePath)); } /** * @see org.quartz.jobs.FileScanListener#fileUpdated(java.lang.String) */ public void fileUpdated(String fileName) { if (started) { processFile(fileName); } } class JobFile { private final String fileName; // These are set by initialize() private String filePath; private String fileBasename; private boolean fileFound; protected JobFile(String fileName) throws SchedulerException { this.fileName = fileName; initialize(); } protected String getFileName() { return fileName; } protected boolean getFileFound() { return fileFound; } protected String getFilePath() { return filePath; } protected String getFileBasename() { return fileBasename; } private void initialize() throws SchedulerException { InputStream f = null; try { String furl = null; File file = new File(getFileName()); // files in filesystem if (!file.exists()) { URL url = classLoadHelper.getResource(getFileName()); if(url != null) { furl = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8); file = new File(furl); try { f = url.openStream(); } catch (IOException ignore) { // Swallow the exception } } } else { try { f = new java.io.FileInputStream(file); }catch (FileNotFoundException e) { // ignore } } if (f == null) { if (isFailOnFileNotFound()) { throw new SchedulerException( "File named '" + getFileName() + "' does not exist."); } else { getLog().warn("File named '{}' does not exist.", getFileName()); } } else { fileFound = true; } filePath = (furl != null) ? furl : file.getAbsolutePath(); fileBasename = file.getName(); } finally { try { if (f != null) { f.close(); } } catch (IOException ioe) { getLog().warn("Error closing jobs file {}", getFileName(), ioe); } } } } } // EOF ================================================ FILE: quartz/src/main/java/org/quartz/simpl/CascadingClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; 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 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 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 * @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()); for(ClassLoadHelper loadHelper: loadHelpers) { 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 (Throwable t) { bestCandidate = null; } } Throwable throwable = null; for (ClassLoadHelper helper : loadHelpers) { try { Class clazz = helper.loadClass(name); bestCandidate = helper; return clazz; } catch (Throwable t) { throwable = t; } } if (throwable instanceof ClassNotFoundException) { throw (ClassNotFoundException) throwable; } else { throw new ClassNotFoundException( String.format( "Unable to load class %s by any known loaders.", name), throwable); } } @SuppressWarnings("unchecked") public Class loadClass(String name, Class clazz) throws ClassNotFoundException { return (Class) 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) { URL result = null; if (bestCandidate != null) { result = bestCandidate.getResource(name); if(result == null) { bestCandidate = null; } else { return result; } } ClassLoadHelper loadHelper = null; for (ClassLoadHelper helper : loadHelpers) { loadHelper = helper; 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) { InputStream result = null; if (bestCandidate != null) { result = bestCandidate.getResourceAsStream(name); if(result == null) { bestCandidate = null; } else { return result; } } ClassLoadHelper loadHelper = null; for (ClassLoadHelper helper : loadHelpers) { loadHelper = helper; result = loadHelper.getResourceAsStream(name); if (result != null) { break; } } bestCandidate = loadHelper; return result; } /** * Enable sharing of the "best" class-loader with 3rd party. * * @return the class-loader user be the helper. */ public ClassLoader getClassLoader() { return (this.bestCandidate == null) ? Thread.currentThread().getContextClassLoader() : this.bestCandidate.getClassLoader(); } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/HostnameInstanceIdGenerator.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.net.InetAddress; import org.quartz.SchedulerException; import 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); } } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/InitThreadContextClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 * @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); } @SuppressWarnings("unchecked") public Class loadClass(String name, Class clazz) throws ClassNotFoundException { return (Class) 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. * * @return the class-loader user be the helper. */ public ClassLoader getClassLoader() { return this.initClassLoader; } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/LoadingLoaderClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 * @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); } @SuppressWarnings("unchecked") public Class loadClass(String name, Class clazz) throws ClassNotFoundException { return (Class) 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. * * @return the class-loader user be the helper. */ public ClassLoader getClassLoader() { return this.getClass().getClassLoader(); } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/PropertySettingJobFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.Locale; import java.util.Map; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.Scheduler; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import 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 from the SchedulerContext and * the JobExecutionContext's merged JobDataMap onto * bean properties of the Job. * *

Set the warnIfPropertyNotFound property to true if you'd like noisy logging in * the case of values in the JobDataMap not mapping to properties on your Job * class. This may be useful for troubleshooting typos of property names, etc. * but very noisy if you regularly (and purposely) have extra things in your * JobDataMap.

* *

Also of possible interest is the throwIfPropertyNotFound property which * will throw exceptions on unmatched JobDataMap keys.

* * @see org.quartz.spi.JobFactory * @see SimpleJobFactory * @see SchedulerContext * @see org.quartz.JobExecutionContext#getMergedJobDataMap() * @see #setWarnIfPropertyNotFound(boolean) * @see #setThrowIfPropertyNotFound(boolean) * * @author jhouse */ public class PropertySettingJobFactory extends SimpleJobFactory { private boolean warnIfNotFound = false; private boolean throwIfNotFound = false; @Override public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException { Job job = super.newJob(bundle, scheduler); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.putAll(scheduler.getContext()); 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 (Map.Entry stringObjectEntry : data.getWrappedMap().entrySet()) { String name = (String) ((Map.Entry) stringObjectEntry).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 = ((Map.Entry) stringObjectEntry).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 = Integer.valueOf((String) o); } else if (o instanceof Integer) { parm = o; } } else if (paramType.equals(long.class)) { if (o instanceof String) { parm = Long.valueOf((String) o); } else if (o instanceof Long) { parm = o; } } else if (paramType.equals(float.class)) { if (o instanceof String) { parm = Float.valueOf((String) o); } else if (o instanceof Float) { parm = o; } } else if (paramType.equals(double.class)) { if (o instanceof String) { parm = Double.valueOf((String) o); } else if (o instanceof Double) { parm = o; } } else if (paramType.equals(boolean.class)) { if (o instanceof String) { parm = Boolean.valueOf((String) o); } else if (o instanceof Boolean) { parm = o; } } else if (paramType.equals(byte.class)) { if (o instanceof String) { parm = Byte.valueOf((String) o); } else if (o instanceof Byte) { parm = o; } } else if (paramType.equals(short.class)) { if (o instanceof String) { parm = Short.valueOf((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 = 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 (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 (PropertyDescriptor prop : props) { java.lang.reflect.Method wMeth = prop.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 property 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 property 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 property 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 property setter on the Job class. * * @param warnIfNotFound defaults to true. */ public void setWarnIfPropertyNotFound(boolean warnIfNotFound) { this.warnIfNotFound = warnIfNotFound; } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/RAMJobStore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicLong; import org.quartz.Calendar; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.JobPersistenceException; import org.quartz.ObjectAlreadyExistsException; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.Trigger.TriggerState; import org.quartz.Trigger.TriggerTimeComparator; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.matchers.StringMatcher; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.JobStore; import org.quartz.spi.OperableTrigger; import org.quartz.spi.SchedulerSignaler; import org.quartz.spi.TriggerFiredBundle; import org.quartz.spi.TriggerFiredResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This class implements a {@link org.quartz.spi.JobStore} that * utilizes RAM as its storage device. *

* *

* As you should know, the ramification of this is that access is extremely * 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 final HashMap jobsByKey = new HashMap<>(1000); protected final HashMap triggersByKey = new HashMap<>(1000); protected final HashMap> jobsByGroup = new HashMap<>(25); protected final HashMap> triggersByGroup = new HashMap<>(25); protected final TreeSet timeTriggers = new TreeSet<>(new TriggerWrapperComparator()); protected final HashMap calendarsByName = new HashMap<>(25); protected final Map> triggersByJob = new HashMap<>(1000); protected final Object lock = new Object(); protected final HashSet pausedTriggerGroups = new HashSet<>(); protected final HashSet pausedJobGroups = new HashSet<>(); protected final HashSet blockedJobs = new HashSet<>(); protected long misfireThreshold = 5000L; protected SchedulerSignaler signaler; private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a new RAMJobStore. *

*/ public RAMJobStore() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Logger 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 schedSignaler) { this.signaler = schedSignaler; getLog().info("RAMJobStore initialized."); } public void schedulerStarted() { // nothing to do } public void schedulerPaused() { // nothing to do } public void schedulerResumed() { // 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 the new misfire threshold */ @SuppressWarnings("UnusedDeclaration") public void setMisfireThreshold(long misfireThreshold) { if (misfireThreshold < 1) { throw new IllegalArgumentException("Misfire threshold 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; } /** * Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws JobPersistenceException */ public void clearAllSchedulingData() throws JobPersistenceException { synchronized (lock) { // unschedule jobs (delete triggers) List lst = getTriggerGroupNames(); for (String group: lst) { Set keys = getTriggerKeys(GroupMatcher.triggerGroupEquals(group)); for (TriggerKey key: keys) { removeTrigger(key); } } // delete jobs lst = getJobGroupNames(); for (String group: lst) { Set keys = getJobKeys(GroupMatcher.jobGroupEquals(group)); for (JobKey key: keys) { removeJob(key); } } // delete calendars lst = getCalendarNames(); for(String name: lst) { removeCalendar(name); } } } /** *

* Store the given {@link org.quartz.JobDetail} and {@link 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(JobDetail newJob, OperableTrigger newTrigger) throws JobPersistenceException { storeJob(newJob, false); storeTrigger(newTrigger, false); } /** *

* Store the given {@link org.quartz.Job}. *

* * @param newJob * The Job to be stored. * @param replaceExisting * If true, any Job existing in the * JobStore with the same name and 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(JobDetail newJob, boolean replaceExisting) throws ObjectAlreadyExistsException { JobWrapper jw = new JobWrapper((JobDetail)newJob.clone()); boolean repl = false; synchronized (lock) { if (jobsByKey.get(jw.key) != null) { if (!replaceExisting) { throw new ObjectAlreadyExistsException(newJob); } repl = true; } if (!repl) { // get job group HashMap grpMap = jobsByGroup.computeIfAbsent(newJob.getKey().getGroup(), k -> new HashMap<>(100)); // add to jobs by group grpMap.put(newJob.getKey(), jw); // add to jobs by FQN map jobsByKey.put(jw.key, jw); } else { // update job detail JobWrapper orig = jobsByKey.get(jw.key); orig.jobDetail = jw.jobDetail; // already cloned } } } /** *

* Remove (delete) the {@link org.quartz.Job} with the given * name, and any {@link org.quartz.Trigger} s that reference * it. *

* * @return true if a Job with the given name and * group was found and removed from the store. */ public boolean removeJob(JobKey jobKey) { boolean found = false; synchronized (lock) { List triggersOfJob = getTriggersForJob(jobKey); for (OperableTrigger trig: triggersOfJob) { this.removeTrigger(trig.getKey()); found = true; } found = (jobsByKey.remove(jobKey) != null) | found; if (found) { HashMap grpMap = jobsByGroup.get(jobKey.getGroup()); if (grpMap != null) { grpMap.remove(jobKey); if (grpMap.isEmpty()) { jobsByGroup.remove(jobKey.getGroup()); } } } } return found; } public boolean removeJobs(List jobKeys) throws JobPersistenceException { boolean allFound = true; synchronized (lock) { for(JobKey key: jobKeys) allFound = removeJob(key) && allFound; } return allFound; } public boolean removeTriggers(List triggerKeys) throws JobPersistenceException { boolean allFound = true; synchronized (lock) { for(TriggerKey key: triggerKeys) allFound = removeTrigger(key) && allFound; } return allFound; } public void storeJobsAndTriggers( Map> triggersAndJobs, boolean replace) throws JobPersistenceException { synchronized (lock) { // make sure there are no collisions... if(!replace) { for(Entry> e: triggersAndJobs.entrySet()) { if(checkExists(e.getKey().getKey())) throw new ObjectAlreadyExistsException(e.getKey()); for(Trigger trigger: e.getValue()) { if(checkExists(trigger.getKey())) throw new ObjectAlreadyExistsException(trigger); } } } // do bulk add... for(Entry> e: triggersAndJobs.entrySet()) { storeJob(e.getKey(), true); for(Trigger trigger: e.getValue()) { storeTrigger((OperableTrigger) trigger, true); } } } } /** *

* Store the given {@link org.quartz.Trigger}. *

* * @param newTrigger * The Trigger to be stored. * @param replaceExisting * If true, any Trigger existing in * the JobStore with the same name and group should * be over-written. * @throws ObjectAlreadyExistsException * if a Trigger with the same name/group already * exists, and replaceExisting is set to false. * * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) */ public void storeTrigger(OperableTrigger newTrigger, boolean replaceExisting) throws JobPersistenceException { TriggerWrapper tw = new TriggerWrapper((OperableTrigger)newTrigger.clone()); synchronized (lock) { if (triggersByKey.get(tw.key) != null) { if (!replaceExisting) { throw new ObjectAlreadyExistsException(newTrigger); } removeTrigger(newTrigger.getKey(), false); } if (retrieveJob(newTrigger.getJobKey()) == null) { throw new JobPersistenceException("The job (" + newTrigger.getJobKey() + ") referenced by the trigger does not exist."); } // add to triggers by job List jobList = triggersByJob.computeIfAbsent(tw.jobKey, k -> new ArrayList<>(1)); jobList.add(tw); // add to triggers by group HashMap grpMap = triggersByGroup.computeIfAbsent(newTrigger.getKey().getGroup(), k -> new HashMap<>(100)); grpMap.put(newTrigger.getKey(), tw); // add to triggers by FQN map triggersByKey.put(tw.key, tw); if (pausedTriggerGroups.contains(newTrigger.getKey().getGroup()) || pausedJobGroups.contains(newTrigger.getJobKey().getGroup())) { 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 org.quartz.Trigger} with the * given name. *

* * @return true if a Trigger with the given * name and group was found and removed from the store. */ public boolean removeTrigger(TriggerKey triggerKey) { return removeTrigger(triggerKey, true); } private boolean removeTrigger(TriggerKey key, boolean removeOrphanedJob) { boolean found; synchronized (lock) { // remove from triggers by FQN map TriggerWrapper tw = triggersByKey.remove(key); found = tw != null; if (found) { // remove from triggers by group HashMap grpMap = triggersByGroup.get(key.getGroup()); if (grpMap != null) { grpMap.remove(key); if (grpMap.isEmpty()) { triggersByGroup.remove(key.getGroup()); } } //remove from triggers by job List jobList = triggersByJob.get(tw.jobKey); if(jobList != null) { jobList.remove(tw); if(jobList.isEmpty()) { triggersByJob.remove(tw.jobKey); } } timeTriggers.remove(tw); if (removeOrphanedJob) { JobWrapper jw = jobsByKey.get(tw.jobKey); List trigs = getTriggersForJob(tw.jobKey); if ((trigs == null || trigs.isEmpty()) && !jw.jobDetail.isDurable()) { if (removeJob(jw.key)) { signaler.notifySchedulerListenersJobDeleted(jw.key); } } } } } return found; } /** * @see org.quartz.spi.JobStore#replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger) */ public boolean replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger) throws JobPersistenceException { boolean found; synchronized (lock) { // remove from triggers by FQN map TriggerWrapper tw = triggersByKey.remove(triggerKey); found = (tw != null); if (found) { if (!tw.getTrigger().getJobKey().equals(newTrigger.getJobKey())) { throw new JobPersistenceException("New trigger is not related to the same job as the old trigger."); } // remove from triggers by group HashMap grpMap = triggersByGroup.get(triggerKey.getGroup()); if (grpMap != null) { grpMap.remove(triggerKey); if (grpMap.isEmpty()) { triggersByGroup.remove(triggerKey.getGroup()); } } //remove from triggers by job List jobList = triggersByJob.get(tw.jobKey); if(jobList != null) { jobList.remove(tw); if(jobList.isEmpty()) { triggersByJob.remove(tw.jobKey); } } timeTriggers.remove(tw); try { storeTrigger(newTrigger, false); } catch(JobPersistenceException jpe) { storeTrigger(tw.getTrigger(), false); // put previous trigger back... throw jpe; } } } return found; } /** *

* Retrieve the {@link org.quartz.JobDetail} for the given * {@link org.quartz.Job}. *

* * @return The desired Job, or null if there is no match. */ public JobDetail retrieveJob(JobKey jobKey) { synchronized(lock) { JobWrapper jw = jobsByKey.get(jobKey); return (jw != null) ? (JobDetail)jw.jobDetail.clone() : null; } } /** *

* Retrieve all the matched {@link org.quartz.JobDetail} for the given * {@link org.quartz.impl.matchers.GroupMatcher}. *

* * @return A list of all the matched Jobs, or an empty list if there are no matches. */ public List getJobDetails(GroupMatcher matcher) { List outList = null; synchronized (lock) { StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); String compareToValue = matcher.getCompareToValue(); switch(operator) { case EQUALS: HashMap grpMap = jobsByGroup.get(compareToValue); if (grpMap != null) { outList = new LinkedList<>(); for (JobWrapper jw : grpMap.values()) { if (jw != null) { outList.add(jw.jobDetail); } } } break; default: for (Map.Entry> entry : jobsByGroup.entrySet()) { if(operator.evaluate(entry.getKey(), compareToValue) && entry.getValue() != null) { if(outList == null) { outList = new LinkedList<>(); } for (JobWrapper jobWrapper : entry.getValue().values()) { if(jobWrapper != null) { outList.add(jobWrapper.jobDetail); } } } } } } return outList == null ? java.util.Collections.emptyList() : outList; } public List getTriggersByJobAndTriggerGroup(GroupMatcher jobMatcher, GroupMatcher triggerMatcher) throws JobPersistenceException { List matchingTriggers = new ArrayList<>(); synchronized (lock) { // Get all matching jobs Set matchingJobKeys = getJobKeys(jobMatcher); for (JobKey jobKey : matchingJobKeys) { // Get triggers for the job List jobTriggers = getTriggersForJob(jobKey); for (OperableTrigger trigger : jobTriggers) { // Check if the trigger matches the trigger group if (triggerMatcher.getCompareWithOperator().evaluate(trigger.getKey().getGroup(), triggerMatcher.getCompareToValue())) { matchingTriggers.add(trigger); } } } } return matchingTriggers; } /** *

* Retrieve the given {@link org.quartz.Trigger}. *

* * @return The desired Trigger, or null if there is no * match. */ public OperableTrigger retrieveTrigger(TriggerKey triggerKey) { synchronized(lock) { TriggerWrapper tw = triggersByKey.get(triggerKey); return (tw != null) ? (OperableTrigger)tw.getTrigger().clone() : null; } } /** * Determine whether a {@link Job} with the given identifier already * exists within the scheduler. * * @param jobKey the identifier to check for * @return true if a Job exists with the given identifier * @throws JobPersistenceException */ public boolean checkExists(JobKey jobKey) throws JobPersistenceException { synchronized(lock) { JobWrapper jw = jobsByKey.get(jobKey); return (jw != null); } } /** * Determine whether a {@link Trigger} with the given identifier already * exists within the scheduler. * * @param triggerKey the identifier to check for * @return true if a Trigger exists with the given identifier * @throws JobPersistenceException */ public boolean checkExists(TriggerKey triggerKey) throws JobPersistenceException { synchronized(lock) { TriggerWrapper tw = triggersByKey.get(triggerKey); return (tw != null); } } /** *

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

* * @see TriggerState#NORMAL * @see TriggerState#PAUSED * @see TriggerState#COMPLETE * @see TriggerState#ERROR * @see TriggerState#BLOCKED * @see TriggerState#NONE */ public TriggerState getTriggerState(TriggerKey triggerKey) throws JobPersistenceException { synchronized(lock) { TriggerWrapper tw = triggersByKey.get(triggerKey); if (tw == null) { return TriggerState.NONE; } if (tw.state == TriggerWrapper.STATE_COMPLETE) { return TriggerState.COMPLETE; } if (tw.state == TriggerWrapper.STATE_PAUSED) { return TriggerState.PAUSED; } if (tw.state == TriggerWrapper.STATE_PAUSED_BLOCKED) { return TriggerState.PAUSED; } if (tw.state == TriggerWrapper.STATE_BLOCKED) { return TriggerState.BLOCKED; } if (tw.state == TriggerWrapper.STATE_ERROR) { return TriggerState.ERROR; } return TriggerState.NORMAL; } } /** * Reset the current state of the identified {@link Trigger} * from {@link TriggerState#ERROR} to {@link TriggerState#NORMAL} or * {@link TriggerState#PAUSED} as appropriate. * *

Only affects triggers that are in ERROR state - if identified trigger is not * in that state then the result is a no-op.

* *

The result will be the trigger returning to the normal, waiting to * be fired state, unless the trigger's group has been paused, in which * case it will go into the PAUSED state.

*/ public void resetTriggerFromErrorState(final TriggerKey triggerKey) throws JobPersistenceException { synchronized (lock) { TriggerWrapper tw = triggersByKey.get(triggerKey); // does the trigger exist? if (tw == null) { return; } // is the trigger in error state? if (tw.state != TriggerWrapper.STATE_ERROR) { return; } if(pausedTriggerGroups.contains(triggerKey.getGroup())) { tw.state = TriggerWrapper.STATE_PAUSED; } else { tw.state = TriggerWrapper.STATE_WAITING; timeTriggers.add(tw); } } } /** *

* Store the given {@link org.quartz.Calendar}. *

* * @param calendar * The Calendar to be stored. * @param replaceExisting * If true, any Calendar existing * in the JobStore with the same name and 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(String name, Calendar calendar, boolean replaceExisting, boolean updateTriggers) throws ObjectAlreadyExistsException { calendar = (Calendar) calendar.clone(); synchronized (lock) { Object obj = calendarsByName.get(name); if (obj != null && !replaceExisting) { throw new ObjectAlreadyExistsException( "Calendar with name '" + name + "' already exists."); } else if (obj != null) { calendarsByName.remove(name); } calendarsByName.put(name, calendar); if(obj != null && updateTriggers) { for (TriggerWrapper tw : getTriggerWrappersForCalendar(name)) { OperableTrigger trig = tw.getTrigger(); boolean removed = timeTriggers.remove(tw); trig.updateWithNewCalendar(calendar, getMisfireThreshold()); if (removed) { timeTriggers.add(tw); } } } } } /** *

* Remove (delete) the {@link org.quartz.Calendar} with the * given name. *

* *

* If removal of the Calendar would result in * Triggers pointing to nonexistent 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(String calName) throws JobPersistenceException { int numRefs = 0; synchronized (lock) { for (TriggerWrapper wrapper : triggersByKey.values()) { OperableTrigger trigger = wrapper.trigger; if (trigger.getCalendarName() != null && trigger.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 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(String calName) { synchronized (lock) { Calendar cal = calendarsByName.get(calName); if(cal != null) return (Calendar) cal.clone(); return null; } } /** *

* Get the number of {@link org.quartz.JobDetail} s that are * stored in the JobsStore. *

*/ public int getNumberOfJobs() { synchronized (lock) { return jobsByKey.size(); } } /** *

* Get the number of {@link org.quartz.Trigger} s that are * stored in the JobsStore. *

*/ public int getNumberOfTriggers() { synchronized (lock) { return triggersByKey.size(); } } /** *

* Get the number of {@link org.quartz.Calendar} s that are * stored in the JobsStore. *

*/ public int getNumberOfCalendars() { synchronized (lock) { return calendarsByName.size(); } } /** *

* Get the names of all of the {@link org.quartz.Job} s that * match the given groupMatcher. *

*/ public Set getJobKeys(GroupMatcher matcher) { Set outList = null; synchronized (lock) { StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); String compareToValue = matcher.getCompareToValue(); switch(operator) { case EQUALS: HashMap grpMap = jobsByGroup.get(compareToValue); if (grpMap != null) { outList = new HashSet<>(); for (JobWrapper jw : grpMap.values()) { if (jw != null) { outList.add(jw.jobDetail.getKey()); } } } break; default: for (Map.Entry> entry : jobsByGroup.entrySet()) { if(operator.evaluate(entry.getKey(), compareToValue) && entry.getValue() != null) { if(outList == null) { outList = new HashSet<>(); } for (JobWrapper jobWrapper : entry.getValue().values()) { if(jobWrapper != null) { outList.add(jobWrapper.jobDetail.getKey()); } } } } } } return outList == null ? java.util.Collections.emptySet() : outList; } /** *

* Get the names of all of the {@link 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 List getCalendarNames() { synchronized(lock) { return new LinkedList<>(calendarsByName.keySet()); } } /** *

* Get the names of all of the {@link org.quartz.Trigger} s * that match the given groupMatcher. *

*/ public Set getTriggerKeys(GroupMatcher matcher) { Set outList = null; synchronized (lock) { StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); String compareToValue = matcher.getCompareToValue(); switch(operator) { case EQUALS: HashMap grpMap = triggersByGroup.get(compareToValue); if (grpMap != null) { outList = new HashSet<>(); for (TriggerWrapper tw : grpMap.values()) { if (tw != null) { outList.add(tw.trigger.getKey()); } } } break; default: for (Map.Entry> entry : triggersByGroup.entrySet()) { if(operator.evaluate(entry.getKey(), compareToValue) && entry.getValue() != null) { if(outList == null) { outList = new HashSet<>(); } for (TriggerWrapper triggerWrapper : entry.getValue().values()) { if(triggerWrapper != null) { outList.add(triggerWrapper.trigger.getKey()); } } } } } } return outList == null ? Collections.emptySet() : outList; } /** *

* Get the names of all of the {@link org.quartz.Job} * groups. *

*/ public List getJobGroupNames() { List outList; synchronized (lock) { outList = new LinkedList<>(jobsByGroup.keySet()); } return outList; } /** *

* Get the names of all of the {@link org.quartz.Trigger} * groups. *

*/ public List getTriggerGroupNames() { LinkedList outList; synchronized (lock) { outList = new LinkedList<>(triggersByGroup.keySet()); } 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 List getTriggersForJob(JobKey jobKey) { ArrayList trigList = new ArrayList<>(); synchronized (lock) { List jobList = triggersByJob.get(jobKey); if(jobList != null) { for(TriggerWrapper tw : jobList) { trigList.add((OperableTrigger) tw.trigger.clone()); } } } return trigList; } protected ArrayList getTriggerWrappersForJob(JobKey jobKey) { ArrayList trigList = new ArrayList<>(); synchronized (lock) { List jobList = triggersByJob.get(jobKey); if(jobList != null) { trigList.addAll(jobList); } } return trigList; } protected ArrayList getTriggerWrappersForCalendar(String calName) { ArrayList trigList = new ArrayList<>(); synchronized (lock) { for (TriggerWrapper tw : triggersByKey.values()) { 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(TriggerKey triggerKey) { synchronized (lock) { TriggerWrapper tw = triggersByKey.get(triggerKey); // does the trigger exist? if (tw == null) { return; } // if the trigger is "complete" pausing it does not make sense... if (tw.state == TriggerWrapper.STATE_COMPLETE) { return; } 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 known {@link Trigger}s matching. *

* *

* The JobStore should "remember" the groups paused, and impose the * pause on any new triggers that are added to one of these groups while the group is * paused. *

* */ public List pauseTriggers(GroupMatcher matcher) { List pausedGroups; synchronized (lock) { pausedGroups = new LinkedList<>(); StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); switch (operator) { case EQUALS: if(pausedTriggerGroups.add(matcher.getCompareToValue())) { pausedGroups.add(matcher.getCompareToValue()); } break; default : for (String group : triggersByGroup.keySet()) { if(operator.evaluate(group, matcher.getCompareToValue())) { if(pausedTriggerGroups.add(matcher.getCompareToValue())) { pausedGroups.add(group); } } } } for (String pausedGroup : pausedGroups) { Set keys = getTriggerKeys(GroupMatcher.triggerGroupEquals(pausedGroup)); for (TriggerKey key: keys) { pauseTrigger(key); } } } return pausedGroups; } /** *

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

* */ public void pauseJob(JobKey jobKey) { synchronized (lock) { List triggersOfJob = getTriggersForJob(jobKey); for (OperableTrigger trigger: triggersOfJob) { pauseTrigger(trigger.getKey()); } } } /** *

* Pause all of the {@link 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 List pauseJobs(GroupMatcher matcher) { List pausedGroups = new LinkedList<>(); synchronized (lock) { StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); switch (operator) { case EQUALS: if (pausedJobGroups.add(matcher.getCompareToValue())) { pausedGroups.add(matcher.getCompareToValue()); } break; default : for (String group : jobsByGroup.keySet()) { if(operator.evaluate(group, matcher.getCompareToValue())) { if (pausedJobGroups.add(group)) { pausedGroups.add(group); } } } } for (String groupName : pausedGroups) { for (JobKey jobKey: getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { List triggersOfJob = getTriggersForJob(jobKey); for (OperableTrigger trigger: triggersOfJob) { pauseTrigger(trigger.getKey()); } } } } return pausedGroups; } /** *

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

* *

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

* */ public void resumeTrigger(TriggerKey triggerKey) { synchronized (lock) { TriggerWrapper tw = triggersByKey.get(triggerKey); // does the trigger exist? if (tw == null) { return; } OperableTrigger 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; } if(blockedJobs.contains( trig.getJobKey() )) { 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 List resumeTriggers(GroupMatcher matcher) { Set groups = new HashSet<>(); synchronized (lock) { Set keys = getTriggerKeys(matcher); for (TriggerKey triggerKey: keys) { groups.add(triggerKey.getGroup()); if(triggersByKey.get(triggerKey) != null) { String jobGroup = triggersByKey.get(triggerKey).jobKey.getGroup(); if(pausedJobGroups.contains(jobGroup)) { continue; } } resumeTrigger(triggerKey); } // Find all matching paused trigger groups, and then remove them. StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator(); LinkedList pausedGroups = new LinkedList<>(); String matcherGroup = matcher.getCompareToValue(); switch (operator) { case EQUALS: if(pausedTriggerGroups.contains(matcherGroup)) { pausedGroups.add(matcher.getCompareToValue()); } break; default : for (String group : pausedTriggerGroups) { if(operator.evaluate(group, matcherGroup)) { pausedGroups.add(group); } } } for (String pausedGroup : pausedGroups) { pausedTriggerGroups.remove(pausedGroup); } } return new ArrayList<>(groups); } /** *

* Resume (un-pause) the {@link 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(JobKey jobKey) { synchronized (lock) { List triggersOfJob = getTriggersForJob(jobKey); for (OperableTrigger trigger: triggersOfJob) { resumeTrigger(trigger.getKey()); } } } /** *

* Resume (un-pause) all of the {@link 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 Collection resumeJobs(GroupMatcher matcher) { Set resumedGroups = new HashSet<>(); synchronized (lock) { Set keys = getJobKeys(matcher); for (String pausedJobGroup : pausedJobGroups) { if(matcher.getCompareWithOperator().evaluate(pausedJobGroup, matcher.getCompareToValue())) { resumedGroups.add(pausedJobGroup); } } for (String resumedGroup : resumedGroups) { pausedJobGroups.remove(resumedGroup); } for (JobKey key: keys) { List triggersOfJob = getTriggersForJob(key); for (OperableTrigger trigger: triggersOfJob) { resumeTrigger(trigger.getKey()); } } } return resumedGroups; } /** *

* 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() * @see #pauseTrigger(org.quartz.TriggerKey) * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) */ public void pauseAll() { synchronized (lock) { List names = getTriggerGroupNames(); for (String name: names) { pauseTriggers(GroupMatcher.triggerGroupEquals(name)); } } } /** *

* 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() */ public void resumeAll() { synchronized (lock) { pausedJobGroups.clear(); resumeTriggers(GroupMatcher.anyTriggerGroup()); } } 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 || tw.trigger.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { return false; } Calendar cal = null; if (tw.trigger.getCalendarName() != null) { cal = retrieveCalendar(tw.trigger.getCalendarName()); } signaler.notifyTriggerListenersMisfired((OperableTrigger)tw.trigger.clone()); tw.trigger.updateAfterMisfire(cal); if (tw.trigger.getNextFireTime() == null) { tw.state = TriggerWrapper.STATE_COMPLETE; signaler.notifySchedulerListenersFinalized(tw.trigger); synchronized (lock) { timeTriggers.remove(tw); } } else return !tnft.equals(tw.trigger.getNextFireTime()); return true; } private static final AtomicLong ftrCtr = new AtomicLong(System.currentTimeMillis()); protected String getFiredTriggerRecordId() { return String.valueOf(ftrCtr.incrementAndGet()); } /** *

* Get a handle to the next trigger to be fired, and mark it as 'reserved' * by the calling scheduler. *

* * @see #releaseAcquiredTrigger(OperableTrigger) */ public List acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow) { synchronized (lock) { List result = new ArrayList<>(); Set acquiredJobKeysForNoConcurrentExec = new HashSet<>(); Set excludedTriggers = new HashSet<>(); long batchEnd = noLaterThan; // return empty list if store has no triggers. if (timeTriggers.isEmpty()) return result; while (true) { TriggerWrapper tw; try { tw = timeTriggers.first(); if (tw == null) break; timeTriggers.remove(tw); } catch (java.util.NoSuchElementException nsee) { break; } if (tw.trigger.getNextFireTime() == null) { continue; } if (applyMisfire(tw)) { if (tw.trigger.getNextFireTime() != null) { timeTriggers.add(tw); } continue; } if (tw.getTrigger().getNextFireTime().getTime() > batchEnd) { timeTriggers.add(tw); break; } // If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then // put it back into the timeTriggers set and continue to search for next trigger. JobKey jobKey = tw.trigger.getJobKey(); JobDetail job = jobsByKey.get(tw.trigger.getJobKey()).jobDetail; if (job.isConcurrentExecutionDisallowed()) { if (acquiredJobKeysForNoConcurrentExec.contains(jobKey)) { excludedTriggers.add(tw); continue; // go to next trigger in store. } else { acquiredJobKeysForNoConcurrentExec.add(jobKey); } } tw.state = TriggerWrapper.STATE_ACQUIRED; tw.trigger.setFireInstanceId(getFiredTriggerRecordId()); OperableTrigger trig = (OperableTrigger) tw.trigger.clone(); if (result.isEmpty()) { batchEnd = Math.max(tw.trigger.getNextFireTime().getTime(), System.currentTimeMillis()) + timeWindow; } result.add(trig); if (result.size() == maxCount) break; } // If we did excluded triggers to prevent ACQUIRE state due to DisallowConcurrentExecution, we need to add them back to store. if (!excludedTriggers.isEmpty()) timeTriggers.addAll(excludedTriggers); return result; } } /** *

* Inform the JobStore that the scheduler no longer plans to * fire the given Trigger, that it had previously acquired * (reserved). *

*/ public void releaseAcquiredTrigger(OperableTrigger trigger) { synchronized (lock) { TriggerWrapper tw = triggersByKey.get(trigger.getKey()); 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 List triggersFired(List firedTriggers) { synchronized (lock) { List results = new ArrayList<>(); for (OperableTrigger trigger : firedTriggers) { TriggerWrapper tw = triggersByKey.get(trigger.getKey()); // was the trigger deleted since being acquired? if (tw == null) { continue; } // was the trigger completed, paused, blocked, etc. since being acquired? if (tw.state != TriggerWrapper.STATE_ACQUIRED) { continue; } Calendar cal = null; if (tw.trigger.getCalendarName() != null) { cal = retrieveCalendar(tw.trigger.getCalendarName()); if(cal == null) continue; } Date prevFireTime = trigger.getPreviousFireTime(); // in case trigger was replaced between acquiring and firing 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 bundle = new TriggerFiredBundle(retrieveJob( tw.jobKey), trigger, cal, false, new Date(), trigger.getPreviousFireTime(), prevFireTime, trigger.getNextFireTime()); JobDetail job = bundle.getJobDetail(); if (job.isConcurrentExecutionDisallowed()) { ArrayList trigs = getTriggerWrappersForJob(job.getKey()); for (TriggerWrapper ttw : trigs) { 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(job.getKey()); } else if (tw.trigger.getNextFireTime() != null) { synchronized (lock) { timeTriggers.add(tw); } } results.add(new TriggerFiredResult(bundle)); } return results; } } /** *

* Inform the JobStore that the scheduler has completed the * firing of the given Trigger (and the execution its * associated Job), and that the {@link org.quartz.JobDataMap} * in the given JobDetail should be updated if the Job * is stateful. *

*/ public void triggeredJobComplete(OperableTrigger trigger, JobDetail jobDetail, CompletedExecutionInstruction triggerInstCode) { synchronized (lock) { JobWrapper jw = jobsByKey.get(jobDetail.getKey()); TriggerWrapper tw = triggersByKey.get(trigger.getKey()); // 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.isPersistJobDataAfterExecution()) { JobDataMap newData = jobDetail.getJobDataMap(); if (newData != null) { newData = (JobDataMap)newData.clone(); newData.clearDirtyFlag(); } jd = jd.getJobBuilder().setJobData(newData).build(); jw.jobDetail = jd; } if (jd.isConcurrentExecutionDisallowed()) { blockedJobs.remove(jd.getKey()); ArrayList trigs = getTriggerWrappersForJob(jd.getKey()); for(TriggerWrapper ttw : trigs) { 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(jobDetail.getKey()); } // check for trigger deleted during execution... if (tw != null) { if (triggerInstCode == CompletedExecutionInstruction.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(trigger.getKey()); } } else { removeTrigger(trigger.getKey()); signaler.signalSchedulingChange(0L); } } else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) { tw.state = TriggerWrapper.STATE_COMPLETE; timeTriggers.remove(tw); signaler.signalSchedulingChange(0L); } else if(triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_ERROR) { getLog().info("Trigger {} set to ERROR state.", trigger.getKey()); tw.state = TriggerWrapper.STATE_ERROR; signaler.signalSchedulingChange(0L); } else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR) { getLog().info("All triggers of Job {} set to ERROR state.", trigger.getJobKey()); setAllTriggersOfJobToState(trigger.getJobKey(), TriggerWrapper.STATE_ERROR); signaler.signalSchedulingChange(0L); } else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) { setAllTriggersOfJobToState(trigger.getJobKey(), TriggerWrapper.STATE_COMPLETE); signaler.signalSchedulingChange(0L); } } } } @Override public long getAcquireRetryDelay(int failureCount) { return 20; } protected void setAllTriggersOfJobToState(JobKey jobKey, int state) { ArrayList tws = getTriggerWrappersForJob(jobKey); for (TriggerWrapper tw : tws) { tw.state = state; if (state != TriggerWrapper.STATE_WAITING) { timeTriggers.remove(tw); } } } @SuppressWarnings("UnusedDeclaration") protected String peekTriggers() { StringBuilder str = new StringBuilder(); synchronized (lock) { for (TriggerWrapper triggerWrapper : triggersByKey.values()) { str.append(triggerWrapper.trigger.getKey().getName()); str.append("/"); } } str.append(" | "); synchronized (lock) { for (TriggerWrapper timeTrigger : timeTriggers) { str.append(timeTrigger.trigger.getKey().getName()); str.append("->"); } } return str.toString(); } /** * @see org.quartz.spi.JobStore#getPausedTriggerGroups() */ public Set getPausedTriggerGroups() throws JobPersistenceException { return new HashSet<>(pausedTriggerGroups); } public void setInstanceId(String schedInstId) { // } public void setInstanceName(String schedName) { // } public void setThreadPoolSize(final int poolSize) { // } public long getEstimatedTimeToReleaseAndAcquireTrigger() { return 5; } public boolean isClustered() { return false; } } /******************************************************************************* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Helper Classes. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ class TriggerWrapperComparator implements Comparator, java.io.Serializable { private static final long serialVersionUID = 8809557142191514261L; final TriggerTimeComparator ttc = new TriggerTimeComparator(); public int compare(TriggerWrapper trig1, TriggerWrapper trig2) { return ttc.compare(trig1.trigger, trig2.trigger); } @Override public boolean equals(Object obj) { return (obj instanceof TriggerWrapperComparator); } @Override public int hashCode() { return super.hashCode(); } } class JobWrapper { public final JobKey key; public JobDetail jobDetail; JobWrapper(JobDetail jobDetail) { this.jobDetail = jobDetail; key = jobDetail.getKey(); } @Override public boolean equals(Object obj) { if (obj instanceof JobWrapper) { JobWrapper jw = (JobWrapper) obj; return jw.key.equals(this.key); } return false; } @Override public int hashCode() { return key.hashCode(); } } class TriggerWrapper { public final TriggerKey key; public final JobKey jobKey; public final OperableTrigger trigger; public int state = STATE_WAITING; public static final int STATE_WAITING = 0; public static final int STATE_ACQUIRED = 1; @SuppressWarnings("UnusedDeclaration") 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(OperableTrigger trigger) { if(trigger == null) throw new IllegalArgumentException("Trigger cannot be null!"); this.trigger = trigger; key = trigger.getKey(); this.jobKey = trigger.getJobKey(); } @Override public boolean equals(Object obj) { if (obj instanceof TriggerWrapper) { TriggerWrapper tw = (TriggerWrapper) obj; return tw.key.equals(this.key); } return false; } @Override public int hashCode() { return key.hashCode(); } public OperableTrigger getTrigger() { return this.trigger; } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/SimpleClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 * @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); } @SuppressWarnings("unchecked") public Class loadClass(String name, Class clazz) throws ClassNotFoundException { return (Class) 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. * * @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) 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 (Throwable all) { // Use this class' class-loader return this.getClass().getClassLoader(); } } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/SimpleInstanceIdGenerator.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.net.InetAddress; import org.quartz.SchedulerException; import 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); } } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/SimpleJobFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.spi.JobFactory; import 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 Logger log = LoggerFactory.getLogger(getClass()); protected Logger getLog() { return log; } public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException { JobDetail jobDetail = bundle.getJobDetail(); Class jobClass = jobDetail.getJobClass(); try { if(log.isDebugEnabled()) { log.debug("Producing instance of Job '{}', class={}", jobDetail.getKey(), jobClass.getName()); } return jobClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new SchedulerException( "Problem instantiating class '" + jobDetail.getJobClass().getName() + "'", e); } } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/SimpleThreadPool.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.SchedulerConfigException; import org.quartz.spi.ThreadPool; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** *

* This is class is a simple implementation of a thread pool, based on the * {@link 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 final LinkedList availWorkers = new LinkedList<>(); private final LinkedList busyWorkers = new LinkedList<>(); private String threadNamePrefix; private final Logger log = LoggerFactory.getLogger(getClass()); private String schedulerInstanceName; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 Logger 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 prefix) { this.threadNamePrefix = prefix; } 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 setInstanceId(String schedInstId) { } public void setInstanceName(String schedName) { schedulerInstanceName = schedName; } public void initialize() throws SchedulerConfigException { if(workers != null && !workers.isEmpty()) // already initialized... return; 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, schedulerInstanceName + "-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 for (WorkerThread wt : createWorkerThreads(count)) { wt.start(); availWorkers.add(wt); } } protected List createWorkerThreads(int createCount) { workers = new LinkedList<>(); for (int i = 1; i<= createCount; ++i) { String threadPrefix = getThreadNamePrefix(); if (threadPrefix == null) { threadPrefix = schedulerInstanceName + "_Worker"; } WorkerThread wt = new WorkerThread(this, threadGroup, threadPrefix + "-" + 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) { getLog().debug("Shutting down threadpool..."); isShutdown = true; if(workers == null) // case where the pool wasn't even initialize()ed return; // signal each worker thread to shut down for (WorkerThread wt : workers) { 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) { boolean interrupted = false; try { // wait for hand-off in runInThread to complete... while(handoffPending) { try { nextRunnableLock.wait(100); } catch(InterruptedException e) { interrupted = true; } } // Wait until all worker threads are shut down while (!busyWorkers.isEmpty()) { WorkerThread wt = (WorkerThread) busyWorkers.getFirst(); try { getLog().debug("Waiting for thread {} to shut down", wt.getName()); // note: with waiting infinite time the // application may appear to 'hang'. nextRunnableLock.wait(2000); } catch (InterruptedException e) { interrupted = true; } } Iterator workerThreads = workers.iterator(); while(workerThreads.hasNext()) { WorkerThread wt = (WorkerThread) workerThreads.next(); try { wt.join(); workerThreads.remove(); } catch (InterruptedException e) { interrupted = true; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } getLog().debug("No executing jobs remaining, all threads stopped."); } getLog().debug("Shutdown of threadpool 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.isEmpty()) && !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.isEmpty() || 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(); } } protected void clearFromBusyWorkersList(WorkerThread wt) { synchronized(nextRunnableLock) { busyWorkers.remove(wt); nextRunnableLock.notifyAll(); } } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * WorkerThread Class. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* A Worker loops, waiting to execute tasks. *

*/ class WorkerThread extends Thread { private final Object lock = new Object(); // A flag that signals the WorkerThread to terminate. private final AtomicBoolean run = new AtomicBoolean(true); private final SimpleThreadPool tp; private Runnable runnable; private boolean runOnce = false; /** *

* 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; if(runnable != null) runOnce = true; setPriority(prio); setDaemon(isDaemon); } /** *

* Signal the thread that it should terminate. *

*/ void shutdown() { run.set(false); } public void run(Runnable newRunnable) { synchronized(lock) { if(runnable != null) { throw new IllegalStateException("Already running a Runnable!"); } runnable = newRunnable; lock.notifyAll(); } } /** *

* Loop, executing targets as they are received. *

*/ @Override public void run() { boolean ran = false; while (run.get()) { try { synchronized(lock) { while (runnable == null && run.get()) { lock.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 thread was interrupt()'ed.", unblock); } catch(Exception e) { // ignore to help with a tomcat glitch } } catch (Throwable exceptionInRunnable) { try { getLog().error("Error while executing the Runnable: ", exceptionInRunnable); } catch(Exception e) { // ignore to help with a tomcat glitch } } finally { synchronized(lock) { runnable = null; } // repair the thread in case the runnable mucked it up... if(getPriority() != tp.getThreadPriority()) { setPriority(tp.getThreadPriority()); } if (runOnce) { run.set(false); clearFromBusyWorkersList(this); } else if(ran) { ran = false; makeAvailable(this); } } } //if (log.isDebugEnabled()) try { getLog().debug("WorkerThread is shut down."); } catch(Exception e) { // ignore to help with a tomcat glitch } } } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/SimpleTimeBroker.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.util.Date; import org.quartz.SchedulerConfigException; import org.quartz.spi.TimeBroker; /** *

* The interface to be implemented by classes that want to provide a mechanism * by which the {@link org.quartz.core.QuartzScheduler} can * reliably determine the current time. *

* *

* In general, the default implementation of this interface ({@link 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 org.quartz.core.QuartzScheduler * * @author James House */ @SuppressWarnings("deprecation") 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... } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/SystemPropertyInstanceIdGenerator.java ================================================ package org.quartz.simpl; import org.quartz.SchedulerException; import org.quartz.spi.InstanceIdGenerator; /** * InstanceIdGenerator that will use a {@link SystemPropertyInstanceIdGenerator#SYSTEM_PROPERTY system property} * to configure the scheduler. The default system property name to use the value of {@link #SYSTEM_PROPERTY}, but * can be specified via the "systemPropertyName" property. * * You can also set the properties "postpend" and "prepend" to String values that will be added to the beginning * or end (respectively) of the value found in the system property. * * If no value set for the property, a {@link org.quartz.SchedulerException} is thrown * * @author Alex Snaps */ public class SystemPropertyInstanceIdGenerator implements InstanceIdGenerator { /** * System property to read the instanceId from */ public static final String SYSTEM_PROPERTY = "org.quartz.scheduler.instanceId"; private String prepend = null; private String postpend = null; private String systemPropertyName = SYSTEM_PROPERTY; /** * Returns the cluster wide value for this scheduler instance's id, based on a system property * @return the value of the system property named by the value of {@link #getSystemPropertyName()} - which defaults * to {@link #SYSTEM_PROPERTY}. * @throws SchedulerException Shouldn't a value be found */ public String generateInstanceId() throws SchedulerException { String property = System.getProperty(getSystemPropertyName()); if(property == null) { throw new SchedulerException("No value for '" + SYSTEM_PROPERTY + "' system property found, please configure your environment accordingly!"); } if(getPrepend() != null) property = getPrepend() + property; if(getPostpend() != null) property = property + getPostpend(); return property; } /** * A String of text to prepend (add to the beginning) to the instanceId * found in the system property. */ public String getPrepend() { return prepend; } /** * A String of text to prepend (add to the beginning) to the instanceId * found in the system property. * * @param prepend the value to prepend, or null if none is desired. */ public void setPrepend(String prepend) { this.prepend = prepend == null ? null : prepend.trim(); } /** * A String of text to postpend (add to the end) to the instanceId * found in the system property. */ public String getPostpend() { return postpend; } /** * A String of text to postpend (add to the end) to the instanceId * found in the system property. * * @param postpend the value to postpend, or null if none is desired. */ public void setPostpend(String postpend) { this.postpend = postpend == null ? null : postpend.trim(); } /** * The name of the system property from which to obtain the instanceId. * * Defaults to {@link #SYSTEM_PROPERTY}. * */ public String getSystemPropertyName() { return systemPropertyName; } /** * The name of the system property from which to obtain the instanceId. * * Defaults to {@link #SYSTEM_PROPERTY}. * * @param systemPropertyName the system property name */ public void setSystemPropertyName(String systemPropertyName) { this.systemPropertyName = systemPropertyName == null ? SYSTEM_PROPERTY : systemPropertyName.trim(); } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/ThreadContextClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 * @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); } @SuppressWarnings("unchecked") public Class loadClass(String name, Class clazz) throws ClassNotFoundException { return (Class) 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. * * @return the class-loader user be the helper. */ public ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/ZeroSizeThreadPool.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.SchedulerConfigException; import org.quartz.spi.ThreadPool; /** *

* This is class is a simple implementation of a zero size thread pool, based on the * {@link 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 Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a new ZeroSizeThreadPool. *

*/ public ZeroSizeThreadPool() { } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public Logger 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."); } public void setInstanceId(String schedInstId) { } public void setInstanceName(String schedName) { } } ================================================ FILE: quartz/src/main/java/org/quartz/simpl/package.html ================================================ Package org.quartz.simpl

Contains simple / light-weight implementations (with no dependencies on external libraries) of interfaces required by the org.quartz.core.QuartzScheduler.




See the Quartz project for more information. ================================================ FILE: quartz/src/main/java/org/quartz/spi/ClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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; /** * Return the class of the given type 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, Class clazz) 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(); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/InstanceIdGenerator.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import 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 org.quartz.simpl.SimpleInstanceIdGenerator */ public interface InstanceIdGenerator { /** * Generate the instance id for a Scheduler * * @return The clusterwide unique instance id. */ String generateInstanceId() throws SchedulerException; } ================================================ FILE: quartz/src/main/java/org/quartz/spi/JobFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import org.quartz.Job; import org.quartz.Scheduler; import 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 opportunity for dependency injection. *

* * @see org.quartz.Scheduler#setJobFactory(JobFactory) * @see org.quartz.simpl.SimpleJobFactory * @see 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 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 with instantiating the Job. *

* * @param bundle * The TriggerFiredBundle from which the JobDetail * and other info relating to the trigger firing can be obtained. * @param scheduler a handle to the scheduler that is about to execute the job. * @throws SchedulerException if there is a problem instantiating the Job. * @return the newly instantiated Job */ Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException; } ================================================ FILE: quartz/src/main/java/org/quartz/spi/JobStore.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import org.quartz.Calendar; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.JobPersistenceException; import org.quartz.ObjectAlreadyExistsException; import org.quartz.SchedulerConfigException; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.Trigger.TriggerState; import org.quartz.impl.matchers.GroupMatcher; /** *

* The interface to be implemented by classes that want to provide a {@link org.quartz.Job} * and {@link org.quartz.Trigger} storage mechanism for the * {@link 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 org.quartz.core.QuartzScheduler * @see org.quartz.Trigger * @see org.quartz.Job * @see org.quartz.JobDetail * @see org.quartz.JobDataMap * @see 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 * the scheduler has been paused. */ void schedulerPaused(); /** * Called by the QuartzScheduler to inform the JobStore that * the scheduler has resumed after being paused. */ void schedulerResumed(); /** * 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(); /** * How long (in milliseconds) the JobStore implementation * estimates that it will take to release a trigger and acquire a new one. */ long getEstimatedTimeToReleaseAndAcquireTrigger(); /** * Whether or not the JobStore implementation is clustered. */ boolean isClustered(); ///////////////////////////////////////////////////////////////////////////// // // Job & Trigger Storage methods // ///////////////////////////////////////////////////////////////////////////// /** * Store the given {@link org.quartz.JobDetail} and {@link 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(JobDetail newJob, OperableTrigger newTrigger) throws ObjectAlreadyExistsException, JobPersistenceException; /** * Store the given {@link org.quartz.JobDetail}. * * @param newJob * The JobDetail to be stored. * @param replaceExisting * If true, any Job existing in the * JobStore with the same name and 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(JobDetail newJob, boolean replaceExisting) throws ObjectAlreadyExistsException, JobPersistenceException; void storeJobsAndTriggers(Map> triggersAndJobs, boolean replace) throws ObjectAlreadyExistsException, JobPersistenceException; /** * Remove (delete) the {@link org.quartz.Job} with the given * key, and any {@link 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. *

* * @return true if a Job with the given name and * group was found and removed from the store. */ boolean removeJob(JobKey jobKey) throws JobPersistenceException; boolean removeJobs(List jobKeys) throws JobPersistenceException; /** * Retrieve the {@link org.quartz.JobDetail} for the given * {@link org.quartz.Job}. * * @return The desired Job, or null if there is no match. */ JobDetail retrieveJob(JobKey jobKey) throws JobPersistenceException; /** *

* Gets all the {@link org.quartz.JobDetail}s in the matching groups. *

* These will NOT necessarily be in lexicographical order, particularly on {@link org.quartz.simpl.RAMJobStore} * You may need to re-sort them yourself. * @param matcher Matcher to evaluate against known groups * @return List of all JobDetail matching * @throws JobPersistenceException On error */ List getJobDetails(GroupMatcher matcher) throws JobPersistenceException; /** * Store the given {@link org.quartz.Trigger}. * * @param newTrigger * The Trigger to be stored. * @param replaceExisting * If true, any Trigger existing in * the JobStore with the same name and group should * be over-written. * @throws ObjectAlreadyExistsException * if a Trigger with the same name/group already * exists, and replaceExisting is set to false. * * @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher) */ void storeTrigger(OperableTrigger newTrigger, boolean replaceExisting) throws ObjectAlreadyExistsException, JobPersistenceException; /** * Remove (delete) the {@link org.quartz.Trigger} with the * given key. * *

* 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. *

* * @return true if a Trigger with the given * name and group was found and removed from the store. */ boolean removeTrigger(TriggerKey triggerKey) throws JobPersistenceException; boolean removeTriggers(List triggerKeys) throws JobPersistenceException; /** * Remove (delete) the {@link org.quartz.Trigger} with the * given key, and store the new given one - which must be associated * with the same job. * * @param newTrigger * The new Trigger to be stored. * * @return true if a Trigger with the given * name and group was found and removed from the store. */ boolean replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger) throws JobPersistenceException; /** * Retrieve the given {@link org.quartz.Trigger}. * * @return The desired Trigger, or null if there is no * match. */ OperableTrigger retrieveTrigger(TriggerKey triggerKey) throws JobPersistenceException; /** * Determine whether a {@link Job} with the given identifier already * exists within the scheduler. * * @param jobKey the identifier to check for * @return true if a Job exists with the given identifier * @throws JobPersistenceException */ boolean checkExists(JobKey jobKey) throws JobPersistenceException; /** * Determine whether a {@link Trigger} with the given identifier already * exists within the scheduler. * * @param triggerKey the identifier to check for * @return true if a Trigger exists with the given identifier * @throws JobPersistenceException */ boolean checkExists(TriggerKey triggerKey) throws JobPersistenceException; /** * Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s * {@link Calendar}s. * * @throws JobPersistenceException */ void clearAllSchedulingData() throws JobPersistenceException; /** * Store the given {@link org.quartz.Calendar}. * * @param calendar * The Calendar to be stored. * @param replaceExisting * If true, any Calendar existing * in the JobStore with the same name and 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(String name, Calendar calendar, boolean replaceExisting, boolean updateTriggers) throws ObjectAlreadyExistsException, JobPersistenceException; /** * Remove (delete) the {@link org.quartz.Calendar} with the * given name. * *

* If removal of the Calendar would result in * Triggers pointing to nonexistent 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(String calName) throws JobPersistenceException; /** * Retrieve the given {@link 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(String calName) throws JobPersistenceException; ///////////////////////////////////////////////////////////////////////////// // // Informational methods // ///////////////////////////////////////////////////////////////////////////// /** * Get the number of {@link org.quartz.Job} s that are * stored in the JobsStore. */ int getNumberOfJobs() throws JobPersistenceException; /** * Get the number of {@link org.quartz.Trigger} s that are * stored in the JobsStore. */ int getNumberOfTriggers() throws JobPersistenceException; /** * Get the number of {@link org.quartz.Calendar} s that are * stored in the JobsStore. */ int getNumberOfCalendars() throws JobPersistenceException; /** * Get the keys of all of the {@link org.quartz.Job} s that * have the given group name. * *

* If there are no jobs in the given group name, the result should be * an empty collection (not null). *

*/ Set getJobKeys(GroupMatcher matcher) throws JobPersistenceException; /** * Get the names of all of the {@link 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). *

*/ Set getTriggerKeys(GroupMatcher matcher) throws JobPersistenceException; /** * Get the names of all of the {@link org.quartz.Job} * groups. * *

* If there are no known group names, the result should be a zero-length * array (not null). *

*/ List getJobGroupNames() throws JobPersistenceException; /** * Get the names of all of the {@link org.quartz.Trigger} * groups. * *

* If there are no known group names, the result should be a zero-length * array (not null). *

*/ List getTriggerGroupNames() throws JobPersistenceException; /** * Get the names of all of the {@link 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). *

*/ List getCalendarNames() throws JobPersistenceException; /** * Get all the Triggers that are associated to the given Job. * *

* If there are no matches, a zero-length array should be returned. *

*/ List getTriggersForJob(JobKey jobKey) throws JobPersistenceException; /** * Get all the Triggers that are associated with the Job Group * *

* If there are no matches, a zero-length array should be returned. *

* @param matcher A {@link GroupMatcher} to match job groups * @return list of triggers - may be empty * @throws JobPersistenceException thrown if there is an error * @see #getTriggersByTriggerGroup(GroupMatcher) * @see #getTriggersByJobAndTriggerGroup(GroupMatcher, GroupMatcher) * */ default List getTriggersByJobGroup(GroupMatcher matcher) throws JobPersistenceException { return getTriggersByJobAndTriggerGroup(matcher, GroupMatcher.anyGroup()); } /** * Get all the Triggers that are associated with the Job Group * *

* If there are no matches, a zero-length array should be returned. *

* @param matcher A {@link GroupMatcher} to match job groups * @return list of triggers - may be empty * @throws JobPersistenceException thrown if there is an error * @see #getTriggersByJobGroup(GroupMatcher) * @see #getTriggersByJobAndTriggerGroup(GroupMatcher, GroupMatcher) * */ default List getTriggersByTriggerGroup(GroupMatcher matcher) throws JobPersistenceException { return getTriggersByJobAndTriggerGroup(GroupMatcher.anyGroup(), matcher); } /** * Get all the Triggers that are associated with the Job & trigger group matcher *
* note: requires the use of enhanced statements * @param jobMatcher A {@link GroupMatcher} to match job groups * @param triggerMatcher A {@link GroupMatcher} to match trigger groups * @return list of triggers * @throws JobPersistenceException thrown if there is an error saving the job * @see #getTriggersByJobGroup(GroupMatcher) * @see #getTriggersByTriggerGroup(GroupMatcher) */ List getTriggersByJobAndTriggerGroup(GroupMatcher jobMatcher, GroupMatcher triggerMatcher) throws JobPersistenceException; /** * Get the current state of the identified {@link Trigger}. * * @see Trigger.TriggerState */ TriggerState getTriggerState(TriggerKey triggerKey) throws JobPersistenceException; /** * Reset the current state of the identified {@link Trigger} * from {@link TriggerState#ERROR} to {@link TriggerState#NORMAL} or * {@link TriggerState#PAUSED} as appropriate. * *

Only affects triggers that are in ERROR state - if identified trigger is not * in that state then the result is a no-op.

* *

The result will be the trigger returning to the normal, waiting to * be fired state, unless the trigger's group has been paused, in which * case it will go into the PAUSED state.

*/ void resetTriggerFromErrorState(TriggerKey triggerKey) throws JobPersistenceException; ///////////////////////////////////////////////////////////////////////////// // // Trigger State manipulation methods // ///////////////////////////////////////////////////////////////////////////// /** * Pause the {@link org.quartz.Trigger} with the given key. * * @see #resumeTrigger(TriggerKey) */ void pauseTrigger(TriggerKey triggerKey) throws JobPersistenceException; /** * Pause all of the {@link 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 #resumeTriggers(GroupMatcher) */ Collection pauseTriggers(GroupMatcher matcher) throws JobPersistenceException; /** * Pause the {@link org.quartz.Job} with the given name - by * pausing all of its current Triggers. * * @see #resumeJob(JobKey) */ void pauseJob(JobKey jobKey) throws JobPersistenceException; /** * Pause all of the {@link 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 #resumeJobs(GroupMatcher) */ Collection pauseJobs(GroupMatcher groupMatcher) throws JobPersistenceException; /** * Resume (un-pause) the {@link org.quartz.Trigger} with the * given key. * *

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

* * @see #pauseTrigger(TriggerKey) */ void resumeTrigger(TriggerKey triggerKey) throws JobPersistenceException; /** * Resume (un-pause) all of the {@link 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 #pauseTriggers(GroupMatcher) */ Collection resumeTriggers(GroupMatcher matcher) throws JobPersistenceException; /** * Gets the set of all the paused trigger group names. * @return a set of paused trigger group names. * @throws JobPersistenceException thrown if there is an error */ Set getPausedTriggerGroups() throws JobPersistenceException; /** * Resume (un-pause) the {@link org.quartz.Job} with the * given key. * *

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

* * @see #pauseJob(JobKey) */ void resumeJob(JobKey jobKey) throws JobPersistenceException; /** * Resume (un-pause) all of the {@link 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 #pauseJobs(GroupMatcher) */ Collection resumeJobs(GroupMatcher matcher) 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() * @see #pauseTriggers(GroupMatcher) */ void pauseAll() 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() */ void resumeAll() 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(OperableTrigger) */ List acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow) throws JobPersistenceException; /** * Inform the JobStore that the scheduler no longer plans to * fire the given Trigger, that it had previously acquired * (reserved). */ void releaseAcquiredTrigger(OperableTrigger trigger); /** * Inform the JobStore that the scheduler is now firing the * given Trigger (executing its associated Job), * that it had previously acquired (reserved). * * @return may return null if all the triggers or their calendars no longer exist, or * if the trigger was not successfully put into the 'executing' * state. Preference is to return an empty list if none of the triggers * could be fired. */ List triggersFired(List triggers) 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 org.quartz.JobDataMap} * in the given JobDetail should be updated if the Job * is stateful. */ void triggeredJobComplete(OperableTrigger trigger, JobDetail jobDetail, CompletedExecutionInstruction triggerInstCode); /** * Inform the JobStore of the Scheduler instance's Id, * prior to initialize being invoked. * * @since 1.7 */ void setInstanceId(String schedInstId); /** * Inform the JobStore of the Scheduler instance's name, * prior to initialize being invoked. * * @since 1.7 */ void setInstanceName(String schedName); /** * Tells the JobStore the pool size used to execute jobs * @param poolSize amount of threads allocated for job execution * @since 2.0 */ void setThreadPoolSize(int poolSize); /** * Get the amount of time (in ms) to wait when accessing this job store * repeatedly fails. * * Called by the executor thread(s) when calls to * {@link #acquireNextTriggers} fail more than once in succession, and the * thread thus wants to wait a bit before trying again, to not consume * 100% CPU, write huge amounts of errors into logs, etc. in cases like * the DB being offline/restarting. * * The delay returned by implementations should be between 20 and * 600000 milliseconds. * * @param failureCount the number of successive failures seen so far * @return the time (in milliseconds) to wait before trying again */ long getAcquireRetryDelay(int failureCount); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/MutableTrigger.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import java.util.Date; import org.quartz.Calendar; import org.quartz.CronTrigger; import org.quartz.JobDataMap; import org.quartz.JobKey; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerKey; public interface MutableTrigger extends Trigger { void setKey(TriggerKey key); void setJobKey(JobKey key); /** *

* 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. *

*/ void setDescription(String description); /** *

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

* * @param calendarName * use null to dis-associate a Calendar. */ void setCalendarName(String calendarName); /** *

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

*/ void setJobDataMap(JobDataMap jobDataMap); /** * 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 */ void setPriority(int priority); /** *

* 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. *

*/ void setStartTime(Date startTime); /** *

* Set the time at which the Trigger should quit repeating - * regardless of any remaining repeats (based on the trigger's particular * repeat settings). *

* * @see org.quartz.TriggerUtils#computeEndTimeToAllowParticularNumberOfFirings(org.quartz.spi.OperableTrigger, org.quartz.Calendar, int) */ void setEndTime(Date endTime); /** *

* 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 org.quartz.spi.OperableTrigger#updateAfterMisfire(org.quartz.Calendar) * @see SimpleTrigger * @see CronTrigger */ void setMisfireInstruction(int misfireInstruction); Object clone(); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/OperableTrigger.java ================================================ package org.quartz.spi; import java.util.Date; import org.quartz.Calendar; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerException; public interface OperableTrigger extends MutableTrigger { /** *

* 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) */ 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). */ Date computeFirstFireTime(Calendar calendar); /** *

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

* *

* Called after the {@link Scheduler} has executed the * {@link 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 CompletedExecutionInstruction constants. * * @see CompletedExecutionInstruction * @see #triggered(Calendar) */ CompletedExecutionInstruction executionComplete(JobExecutionContext context, JobExecutionException result); /** *

* 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. *

*/ 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 */ 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. */ void validate() throws SchedulerException; /** *

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

* *

* Usable by {@link org.quartz.spi.JobStore} * implementations, in order to facilitate 'recognizing' instances of fired * Trigger s as their jobs complete execution. *

* * */ void setFireInstanceId(String id); /** *

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

*/ String getFireInstanceId(); void setNextFireTime(Date nextFireTime); void setPreviousFireTime(Date previousFireTime); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/SchedulerPlugin.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import org.quartz.Scheduler; import 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 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, ClassLoadHelper)} 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. * @param loadHelper * The classLoadHelper the SchedulerFactory is * actually using * * @throws org.quartz.SchedulerConfigException * if there is an error initializing. */ void initialize(String name, Scheduler scheduler, ClassLoadHelper loadHelper) 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(); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/SchedulerSignaler.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import org.quartz.JobKey; import org.quartz.SchedulerException; import 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 notifySchedulerListenersJobDeleted(JobKey jobKey); void signalSchedulingChange(long candidateNewNextFireTime); void notifySchedulerListenersError(String string, SchedulerException jpe); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/ThreadExecutor.java ================================================ package org.quartz.spi; /** * Allows different strategies for scheduling threads. The {@link #initialize()} * method is required to be called before the first call to * {@link #execute(Thread)}. The Thread containing the work to be performed is * passed to execute and the work is scheduled by the underlying implementation. * * @author matt.accola * @version $Revision$ $Date$ */ public interface ThreadExecutor { /** * Submit a task for execution * * @param thread the thread to execute */ void execute(Thread thread); /** * Initialize any state prior to calling {@link #execute(Thread)} */ void initialize(); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/ThreadPool.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import org.quartz.SchedulerConfigException; /** *

* The interface to be implemented by classes that want to provide a thread * pool for the {@link 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 imbalance of load. *

* * @see 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(); /** *

* Must be called before the ThreadPool is * used, in order to give the it a chance to initialize. *

* *

Typically called by the SchedulerFactory.

*/ 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); /** *

Get the current number of threads in the ThreadPool.

*/ int getPoolSize(); /** *

Inform the ThreadPool of the Scheduler instance's Id, * prior to initialize being invoked.

* * @since 1.7 */ void setInstanceId(String schedInstId); /** *

Inform the ThreadPool of the Scheduler instance's name, * prior to initialize being invoked.

* * @since 1.7 */ void setInstanceName(String schedName); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/TimeBroker.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import java.util.Date; import org.quartz.SchedulerConfigException; import 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 org.quartz.core.QuartzScheduler} can * reliably determine the current time. *

* *

* In general, the default implementation of this interface ({@link 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 org.quartz.core.QuartzScheduler * @deprecated TimeBroker is not currently used in the Quartz code base. * @author James House */ @Deprecated 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(); } ================================================ FILE: quartz/src/main/java/org/quartz/spi/TriggerFiredBundle.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.spi; import java.util.Date; import org.quartz.Calendar; import org.quartz.JobDetail; /** *

* A simple class (structure) used for returning execution-time data from the * JobStore to the QuartzSchedulerThread. *

* * @see org.quartz.core.QuartzSchedulerThread * * @author James House */ public class TriggerFiredBundle implements java.io.Serializable { private static final long serialVersionUID = -6414106108306999265L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final JobDetail job; private final OperableTrigger trigger; private final Calendar cal; private final boolean jobIsRecovering; private final Date fireTime; private final Date scheduledFireTime; private final Date prevFireTime; private final Date nextFireTime; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public TriggerFiredBundle(JobDetail job, OperableTrigger 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 OperableTrigger 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; } } ================================================ FILE: quartz/src/main/java/org/quartz/spi/TriggerFiredResult.java ================================================ package org.quartz.spi; /** * @author lorban */ public class TriggerFiredResult { private TriggerFiredBundle triggerFiredBundle; private Exception exception; public TriggerFiredResult(TriggerFiredBundle triggerFiredBundle) { this.triggerFiredBundle = triggerFiredBundle; } public TriggerFiredResult(Exception exception) { this.exception = exception; } public TriggerFiredBundle getTriggerFiredBundle() { return triggerFiredBundle; } public Exception getException() { return exception; } } ================================================ FILE: quartz/src/main/java/org/quartz/spi/package.html ================================================ Package 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 for more information. ================================================ FILE: quartz/src/main/java/org/quartz/utils/C3p0PoolingConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import org.quartz.SchedulerException; import com.mchange.v2.c3p0.ComboPooledDataSource; /** *

* A ConnectionProvider implementation that creates its own * pool of connections. *

* *

* This class uses C3PO (http://www.mchange.com/projects/c3p0/index.html) as * the underlying pool implementation.

* * @see DBConnectionManager * @see ConnectionProvider * * @author Sharada Jambula * @author James House * @author Mohammad Rezaei */ public class C3p0PoolingConnectionProvider implements PoolingConnectionProvider { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * The maximum number of prepared statements that will be cached per connection in the pool. * Depending upon your JDBC Driver this may significantly help performance, or may slightly * hinder performance. * Default is 120, as Quartz uses over 100 unique statements. 0 disables the feature. */ public static final String DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = "maxCachedStatementsPerConnection"; /** * The number of seconds between tests of idle connections - only enabled * if the validation query property is set. Default is 50 seconds. */ public static final String DB_IDLE_VALIDATION_SECONDS = "idleConnectionValidationSeconds"; /** * Whether the database sql query to validate connections should be executed every time * a connection is retrieved from the pool to ensure that it is still valid. If false, * then validation will occur on check-in. Default is false. */ public static final String DB_VALIDATE_ON_CHECKOUT = "validateOnCheckout"; /** Discard connections after they have been idle this many seconds. 0 disables the feature. Default is 0.*/ public static final String DB_DISCARD_IDLE_CONNECTIONS_SECONDS = "maxIdleTime"; /** Default maximum number of database connections in the pool. */ public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private ComboPooledDataSource datasource; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public C3p0PoolingConnectionProvider(String dbDriver, String dbURL, String dbUser, String dbPassword, int maxConnections, String dbValidationQuery) throws SQLException, SchedulerException { initialize( dbDriver, dbURL, dbUser, dbPassword, maxConnections, DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION, dbValidationQuery, false, 50, 0); } /** * 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 C3p0PoolingConnectionProvider(Properties config) throws SchedulerException, 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.getIntProperty(DB_MAX_CACHED_STATEMENTS_PER_CONNECTION, DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION), cfg.getStringProperty(DB_VALIDATION_QUERY), cfg.getBooleanProperty(DB_VALIDATE_ON_CHECKOUT, false), cfg.getIntProperty(DB_IDLE_VALIDATION_SECONDS, 50), cfg.getIntProperty(DB_DISCARD_IDLE_CONNECTIONS_SECONDS, 0)); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Create the underlying C3PO ComboPooledDataSource with the * default supported properties. * @throws SchedulerException */ private void initialize( String dbDriver, String dbURL, String dbUser, String dbPassword, int maxConnections, int maxStatementsPerConnection, String dbValidationQuery, boolean validateOnCheckout, int idleValidationSeconds, int maxIdleSeconds) throws SQLException, SchedulerException { 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 ComboPooledDataSource(); try { datasource.setDriverClass(dbDriver); } catch (PropertyVetoException e) { throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e); } datasource.setJdbcUrl(dbURL); datasource.setUser(dbUser); datasource.setPassword(dbPassword); datasource.setMaxPoolSize(maxConnections); datasource.setMinPoolSize(1); datasource.setMaxIdleTime(maxIdleSeconds); datasource.setMaxStatementsPerConnection(maxStatementsPerConnection); if (dbValidationQuery != null) { datasource.setPreferredTestQuery(dbValidationQuery); if(!validateOnCheckout) datasource.setTestConnectionOnCheckin(true); else datasource.setTestConnectionOnCheckout(true); datasource.setIdleConnectionTestPeriod(idleValidationSeconds); } } /** * Get the C3PO ComboPooledDataSource created during initialization. * *

* This can be used to set additional data source properties in a * subclass's constructor. *

*/ public ComboPooledDataSource getDataSource() { return datasource; } public Connection getConnection() throws SQLException { return datasource.getConnection(); } public void shutdown() throws SQLException { datasource.close(); } public void initialize() throws SQLException { // do nothing, already initialized during constructor call } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/CircularLossyQueue.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils; import java.util.concurrent.atomic.AtomicReference; /** * An implementation of a CircularQueue data-structure. * When the number of items added exceeds the maximum capacity, items that were * added first are lost. * * @param * Type of the item's to add in this queue * * @author Abhishek Sanoujam * @since 1.7 */ public class CircularLossyQueue { private final AtomicReference[] circularArray; private final int maxSize; private int currentIndex = -1; private boolean isFull = false; /** * Constructs the circular queue with the specified capacity * * @param size */ @SuppressWarnings("unchecked") public CircularLossyQueue(int size) { this.circularArray = new AtomicReference[size]; for (int i = 0; i < size; i++) { this.circularArray[i] = new AtomicReference<>(); } this.maxSize = size; } /** * Adds a new item * * @param newVal */ public void push(T newVal) { int index = (++currentIndex) % maxSize; circularArray[index].set(newVal); isFull = isFull || currentIndex == maxSize; currentIndex = index; } /** * Returns an array of the current elements in the queue. The order of * elements is in reverse order of the order items were added. * * @param type * @return An array containing the current elements in the queue. The first * element of the array is the tail of the queue and the last * element is the head of the queue */ public T[] toArray(T[] type) { if (type.length > maxSize) { throw new IllegalArgumentException("Size of array passed in cannot be greater than " + maxSize); } int curIndex = currentIndex + maxSize; for (int k = 0; k < type.length; k++) { int index = (curIndex - k) % maxSize; type[k] = circularArray[index].get(); } return type; } /** * Returns value at the tail of the queue * * @return Value at the tail of the queue */ public T peek() { if (currentIndex == -1) { return null; } return circularArray[currentIndex].get(); } /** * Returns true if the queue is empty, otherwise false * * @return true if the queue is empty, false otherwise */ public boolean isEmpty() { return currentIndex == -1; } /** * Returns the number of items currently in the queue * * @return the number of items in the queue */ public int depth() { return isFull ? maxSize : currentIndex + 1; } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/ClassUtils.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; public class ClassUtils { public static boolean isAnnotationPresent(Class clazz, Class a) { for (Class c = clazz; c != null; c = c.getSuperclass()) { if (c.isAnnotationPresent(a)) return true; if(isAnnotationPresentOnInterfaces(c, a)) return true; } return false; } private static boolean isAnnotationPresentOnInterfaces(Class clazz, Class a) { for(Class i : clazz.getInterfaces()) { if( i.isAnnotationPresent(a) ) return true; if(isAnnotationPresentOnInterfaces(i, a)) return true; } return false; } public static T getAnnotation(Class clazz, Class aClazz) { //Check class hierarchy for (Class c = clazz; c != null; c = c.getSuperclass()) { T anno = c.getAnnotation(aClazz); if (anno != null) { return anno; } } //Check interfaces (breadth first) Queue> q = new LinkedList<>(); q.add(clazz); while (!q.isEmpty()) { Class c = q.remove(); if (c != null) { if (c.isInterface()) { T anno = c.getAnnotation(aClazz); if (anno != null) { return anno; } } else { q.add(c.getSuperclass()); } q.addAll(Arrays.asList(c.getInterfaces())); } } return null; } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/ConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 * * @author Mohammad Rezaei */ public interface ConnectionProvider { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * @return connection managed by this provider * @throws SQLException */ Connection getConnection() throws SQLException; void shutdown() throws SQLException; void initialize() throws SQLException; } ================================================ FILE: quartz/src/main/java/org/quartz/utils/DBConnectionManager.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 * * @author James House * @author Sharada Jambula * @author Mohammad Rezaei */ public class DBConnectionManager { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static final String DB_PROPS_PREFIX = "org.quartz.db."; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private static final DBConnectionManager instance = new DBConnectionManager(); private final 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 = 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(); } ConnectionProvider getConnectionProvider(String key) { return providers.get(key); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/DirtyFlagMap.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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(final 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(final int initialCapacity, final 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()) { dirty = true; } map.clear(); } public boolean containsKey(final Object key) { return map.containsKey(key); } public boolean containsValue(final Object val) { return map.containsValue(val); } public Set> entrySet() { return new DirtyFlagMapEntrySet(map.entrySet()); } @Override public boolean equals(final Object obj) { if (obj == null || !(obj instanceof DirtyFlagMap)) { return false; } return map.equals(((DirtyFlagMap) obj).getWrappedMap()); } @Override public int hashCode() { return map.hashCode(); } public V get(final Object key) { return map.get(key); } public boolean isEmpty() { return map.isEmpty(); } public Set keySet() { return new DirtyFlagSet<>(map.keySet()); } public V put(final K key, final V val) { dirty = true; return map.put(key, val); } public void putAll(final Map t) { if (!t.isEmpty()) { dirty = true; } map.putAll(t); } public V remove(final Object key) { V 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()); } @Override @SuppressWarnings("unchecked") // suppress warnings on generic cast of super.clone() and map.clone() lines. 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 final Collection collection; public DirtyFlagCollection(final Collection c) { collection = c; } protected Collection getWrappedCollection() { return collection; } public Iterator iterator() { return new DirtyFlagIterator<>(collection.iterator()); } public boolean remove(final Object o) { boolean removed = collection.remove(o); if (removed) { dirty = true; } return removed; } public boolean removeAll(final Collection c) { boolean changed = collection.removeAll(c); if (changed) { dirty = true; } return changed; } public boolean retainAll(final Collection c) { boolean changed = collection.retainAll(c); if (changed) { dirty = true; } return changed; } public void clear() { if (!collection.isEmpty()) { dirty = true; } collection.clear(); } // Pure wrapper methods public int size() { return collection.size(); } public boolean isEmpty() { return collection.isEmpty(); } public boolean contains(final Object o) { return collection.contains(o); } public boolean add(final T o) { return collection.add(o); } // Not supported public boolean addAll(final Collection c) { return collection.addAll(c); } // Not supported public boolean containsAll(final Collection c) { return collection.containsAll(c); } public Object[] toArray() { return collection.toArray(); } public U[] toArray(final U[] 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(final 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 final Iterator iterator; public DirtyFlagIterator(final Iterator iterator) { this.iterator = iterator; } public void remove() { dirty = true; iterator.remove(); } // Pure wrapper methods public boolean hasNext() { return iterator.hasNext(); } public T 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(final Set> set) { super(set); } @Override public Iterator> iterator() { return new DirtyFlagMapEntryIterator(getWrappedSet().iterator()); } @Override public Object[] toArray() { return toArray(new Object[super.size()]); } @SuppressWarnings("unchecked") // suppress warnings on both U[] and U casting. @Override public U[] toArray(final U[] array) { if (!array.getClass().getComponentType().isAssignableFrom(Entry.class)) { throw new IllegalArgumentException("Array must be of type assignable from Map.Entry"); } int size = super.size(); U[] result = array.length < size ? (U[])Array.newInstance(array.getClass().getComponentType(), size) : array; Iterator> entryIter = iterator(); // Will return DirtyFlagMapEntry objects for (int i = 0; i < size; i++) { result[i] = ( U ) 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(final Iterator> iterator) { super(iterator); } @Override public DirtyFlagMapEntry next() { return new DirtyFlagMapEntry(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 final Map.Entry entry; public DirtyFlagMapEntry(final Map.Entry entry) { this.entry = entry; } public V setValue(final V o) { dirty = true; return entry.setValue(o); } // Pure wrapper methods public K getKey() { return entry.getKey(); } public V getValue() { return entry.getValue(); } public boolean equals(Object o) { return entry.equals(o); } } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/FindbugsSuppressWarnings.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE, ElementType.PACKAGE }) @Retention(RetentionPolicy.CLASS) public @interface FindbugsSuppressWarnings { String[] value() default {}; } ================================================ FILE: quartz/src/main/java/org/quartz/utils/HikariCpPoolingConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import com.zaxxer.hikari.HikariDataSource; import org.quartz.SchedulerException; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** *

* A ConnectionProvider implementation that creates its own * pool of connections. *

* *

* This class uses HikariCP (https://brettwooldridge.github.io/HikariCP/) as * the underlying pool implementation.

* * @see DBConnectionManager * @see ConnectionProvider * * @author Ludovic Orban */ public class HikariCpPoolingConnectionProvider implements PoolingConnectionProvider { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** This pooling provider name. */ public static final String POOLING_PROVIDER_NAME = "hikaricp"; /** Discard connections after they have been idle this many seconds. 0 disables the feature. Default is 0.*/ private static final String DB_DISCARD_IDLE_CONNECTIONS_SECONDS = "discardIdleConnectionsSeconds"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private HikariDataSource datasource; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public HikariCpPoolingConnectionProvider(String dbDriver, String dbURL, String dbUser, String dbPassword, int maxConnections, String dbValidationQuery) throws SQLException, SchedulerException { initialize( dbDriver, dbURL, dbUser, dbPassword, maxConnections, dbValidationQuery, 0); } /** * 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 HikariCpPoolingConnectionProvider(Properties config) throws SchedulerException, 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), cfg.getIntProperty(DB_DISCARD_IDLE_CONNECTIONS_SECONDS, 0)); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Create the underlying C3PO ComboPooledDataSource with the * default supported properties. * @throws SchedulerException */ private void initialize( String dbDriver, String dbURL, String dbUser, String dbPassword, int maxConnections, String dbValidationQuery, int maxIdleSeconds) throws SQLException, SchedulerException { 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 HikariDataSource(); datasource.setDriverClassName(dbDriver); datasource.setJdbcUrl(dbURL); datasource.setUsername(dbUser); datasource.setPassword(dbPassword); datasource.setMaximumPoolSize(maxConnections); datasource.setIdleTimeout(maxIdleSeconds); if (dbValidationQuery != null) { datasource.setConnectionTestQuery(dbValidationQuery); } } /** * Get the HikariCP HikariDataSource created during initialization. * *

* This can be used to set additional data source properties in a * subclass's constructor. *

*/ public HikariDataSource getDataSource() { return datasource; } public Connection getConnection() throws SQLException { return datasource.getConnection(); } public void shutdown() throws SQLException { datasource.close(); } public void initialize() throws SQLException { // do nothing, already initialized during constructor call } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/JNDIConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* 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 final String url; private Properties props; private Object datasource; private boolean alwaysLookup; private final Logger log = LoggerFactory.getLogger(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 Logger 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 org.quartz.utils.ConnectionProvider#shutdown() */ public void shutdown() throws SQLException { // do nothing } public void initialize() throws SQLException { // do nothing, already initialized during constructor call } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/Key.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import java.io.Serializable; import java.util.UUID; /** *

* Object representing a job or trigger key. *

* * @author Jeffrey Wescott */ public class Key implements Serializable, Comparable> { private static final long serialVersionUID = -7141167957642391350L; /** * The default group for scheduling entities, with the value "DEFAULT". */ public static final String DEFAULT_GROUP = "DEFAULT"; private final String name; private final String group; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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) { if(name == null) throw new IllegalArgumentException("Name cannot be null."); this.name = name; if(group != null) this.group = group; else this.group = DEFAULT_GROUP; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Get the name portion of the key. *

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

* Get the group portion of the key. *

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

* Return the string representation of the key. The format will be: * <group>.<name>. *

* * @return the string representation of the key */ @Override public String toString() { return getGroup() + '.' + getName(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + group.hashCode(); result = prime * result + name.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("unchecked") Key other = (Key) obj; if (!group.equals(other.group)) return false; return name.equals(other.name); } public int compareTo(Key o) { if(group.equals(DEFAULT_GROUP) && !o.group.equals(DEFAULT_GROUP)) return -1; if(!group.equals(DEFAULT_GROUP) && o.group.equals(DEFAULT_GROUP)) return 1; int r = group.compareTo(o.getGroup()); if(r != 0) return r; return name.compareTo(o.getName()); } public static String createUniqueName(String group) { if(group == null) group = DEFAULT_GROUP; String n1 = UUID.randomUUID().toString(); String n2 = UUID.nameUUIDFromBytes(group.getBytes()).toString(); return String.format("%s-%s", n2.substring(24), n1); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/PoolingConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import javax.sql.DataSource; /** *

* ConnectionProviders supporting pooling of connections. *

* *

* Implementations must pool connections. *

* * @see DBConnectionManager * @see ConnectionProvider * @author Ludovic Orban */ public interface PoolingConnectionProvider extends ConnectionProvider { /** The pooling provider. */ String POOLING_PROVIDER = "provider"; /** The c3p0 pooling provider. */ String POOLING_PROVIDER_C3P0 = "c3p0"; /** The Hikari pooling provider. */ String POOLING_PROVIDER_HIKARICP = "hikaricp"; /** The JDBC database driver. */ String DB_DRIVER = "driver"; /** The JDBC database URL. */ String DB_URL = "URL"; /** The database user name. */ String DB_USER = "user"; /** The database user password. */ String DB_PASSWORD = "password"; /** The maximum number of database connections to have in the pool. Default is 10. */ String DB_MAX_CONNECTIONS = "maxConnections"; /** * The database sql query to execute every time a connection is returned * to the pool to ensure that it is still valid. */ String DB_VALIDATION_QUERY = "validationQuery"; /** Default maximum number of database connections in the pool. */ int DEFAULT_DB_MAX_CONNECTIONS = 10; DataSource getDataSource(); } ================================================ FILE: quartz/src/main/java/org/quartz/utils/PropertiesParser.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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 class used to parse the properties. *

* * @author James House */ public class PropertiesParser { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ Properties props; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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.isEmpty()) ? 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.parseBoolean(val); } 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(Integer.valueOf(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] = ints.get(i); } 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); i++) { exclude = key.startsWith(excludedPrefixes[i]); } } if (!exclude) { String value = getStringProperty(key, ""); if (stripPrefix) { group.put(key.substring(prefix.length()), value); } else { group.put(key, value); } } } } return group; } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/StringKeyDirtyFlagMap.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import java.io.Serializable; /** *

* 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 { private 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. */ @Deprecated private boolean allowsTransientData = false; public StringKeyDirtyFlagMap() { super(); } public StringKeyDirtyFlagMap(int initialCapacity) { super(initialCapacity); } public StringKeyDirtyFlagMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); } @Override public boolean equals(Object obj) { return super.equals(obj); } @Override public int hashCode() { return getWrappedMap().hashCode(); } /** * Get a copy of the Map's String keys in an array of Strings. */ public String[] getKeys() { return 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. */ @Deprecated 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. */ @Deprecated 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. */ @Deprecated public boolean containsTransientData() { if (!getAllowsTransientData()) { // short circuit... return false; } String[] keys = getKeys(); for (String key : keys) { Object o = super.get(key); 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. */ @Deprecated public void removeTransientData() { if (!getAllowsTransientData()) { // short circuit... return; } String[] keys = getKeys(); for (String key : keys) { Object o = super.get(key); if (!(o instanceof Serializable)) { remove(key); } } } // Due to Generic enforcement, this override method is no longer needed. // /** // *

// * Adds the name-value pairs in the given Map to the // * StringKeyDirtyFlagMap. // *

// * // *

// * All keys must be Strings. // *

// */ // @Override // 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, value); } /** *

* Adds the given long value to the StringKeyDirtyFlagMap. *

*/ public void put(String key, long value) { super.put(key, value); } /** *

* Adds the given float value to the StringKeyDirtyFlagMap. *

*/ public void put(String key, float value) { super.put(key, value); } /** *

* Adds the given double value to the StringKeyDirtyFlagMap. *

*/ public void put(String key, double value) { super.put(key, value); } /** *

* Adds the given boolean value to the StringKeyDirtyFlagMap. *

*/ public void put(String key, boolean value) { super.put(key, value); } /** *

* Adds the given char value to the StringKeyDirtyFlagMap. *

*/ public void put(String key, char value) { super.put(key, 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. *

*/ @Override public Object put(String key, Object value) { return super.put((String)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 { if(obj instanceof Integer) return (Integer) obj; return Integer.parseInt((String)obj); } 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 { if(obj instanceof Long) return (Long) obj; return Long.parseLong((String)obj); } 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 { if(obj instanceof Float) return (Float) obj; return Float.parseFloat((String)obj); } 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 { if(obj instanceof Double) return (Double) obj; return Double.parseDouble((String)obj); } 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 { if(obj instanceof Boolean) return (Boolean) obj; return Boolean.parseBoolean((String)obj); } 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 { if(obj instanceof Character) return (Character) obj; return ((String)obj).charAt(0); } 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."); } } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/Counter.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter; /** * A simple counter * * @author Abhishek Sanoujam * * @since 1.8 */ public interface Counter { /** * Increment the counter by 1 * * @return the value after incrementing */ long increment(); /** * Decrement the counter by 1 * * @return the value after decrementing */ long decrement(); /** * Returns the value of the counter and sets it to the new value * * @param newValue * @return Returns the old value */ long getAndSet(long newValue); /** * Gets current value of the counter * * @return current value of the counter */ long getValue(); /** * Increment the counter by given amount * * @param amount * @return the value of the counter after incrementing */ long increment(long amount); /** * Decrement the counter by given amount * * @param amount * @return the value of the counter after decrementing */ long decrement(long amount); /** * Sets the value of the counter to the supplied value * * @param newValue */ void setValue(long newValue); } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/CounterConfig.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter; /** * Config for a simple Counter * * @author Abhishek Sanoujam * @since 1.8 * */ public class CounterConfig { private final long initialValue; /** * Creates a config with the initial value * * @param initialValue */ public CounterConfig(long initialValue) { this.initialValue = initialValue; } /** * Gets the initial value * * @return the initial value of counters created by this config */ public final long getInitialValue() { return initialValue; } /** * Creates and returns a Counter based on the initial value * * @return The counter created by this config */ public Counter createCounter() { return new CounterImpl(initialValue); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/CounterImpl.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter; import java.io.Serializable; import java.util.concurrent.atomic.AtomicLong; /** * A simple counter implementation * * @author Abhishek Sanoujam * @since 1.8 * */ public class CounterImpl implements Counter, Serializable { private static final long serialVersionUID = -1529134342654953984L; private final AtomicLong value; /** * Default Constructor */ public CounterImpl() { this(0L); } /** * Constructor with initial value * * @param initialValue */ public CounterImpl(long initialValue) { this.value = new AtomicLong(initialValue); } /** * {@inheritDoc} */ public long increment() { return value.incrementAndGet(); } /** * {@inheritDoc} */ public long decrement() { return value.decrementAndGet(); } /** * {@inheritDoc} */ public long getAndSet(long newValue) { return value.getAndSet(newValue); } /** * {@inheritDoc} */ public long getValue() { return value.get(); } /** * {@inheritDoc} */ public long increment(long amount) { return value.addAndGet(amount); } /** * {@inheritDoc} */ public long decrement(long amount) { return value.addAndGet(amount * -1); } /** * {@inheritDoc} */ public void setValue(long newValue) { value.set(newValue); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/CounterManager.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter; /** * A Counter Manager that accepts a config to create counters. Creates counter's * based on {@link CounterConfig}. This manages the lifecycle of a counter * * @author Abhishek Sanoujam * @since 1.8 * */ public interface CounterManager { /** * Creates a Counter based on tha passed config * * @param config * @return The counter created and managed by this CounterManager */ Counter createCounter(CounterConfig config); /** * Shuts down this counter manager */ void shutdown(boolean killTimer); /** * Shuts down the counter * * @param counter */ void shutdownCounter(Counter counter); } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/CounterManagerImpl.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter; import java.util.ArrayList; import java.util.List; import java.util.Timer; import org.quartz.utils.counter.sampled.SampledCounter; import org.quartz.utils.counter.sampled.SampledCounterImpl; /** * An implementation of a {@link CounterManager}. * * @author Abhishek Sanoujam * @since 1.8 * */ public class CounterManagerImpl implements CounterManager { private final Timer timer; private boolean shutdown; private final List counters = new ArrayList<>(); /** * Constructor that accepts a timer that will be used for scheduling sampled * counter if any is created */ public CounterManagerImpl(Timer timer) { if (timer == null) { throw new IllegalArgumentException("Timer cannot be null"); } this.timer = timer; } /** * {@inheritDoc} */ public synchronized void shutdown(boolean killTimer) { if (shutdown) { return; } try { // shutdown the counters of this counterManager for (Counter counter : counters) { if (counter instanceof SampledCounter) { ((SampledCounter) counter).shutdown(); } } if(killTimer) timer.cancel(); } finally { shutdown = true; } } /** * {@inheritDoc} */ public synchronized Counter createCounter(CounterConfig config) { if (shutdown) { throw new IllegalStateException("counter manager is shutdown"); } if (config == null) { throw new NullPointerException("config cannot be null"); } Counter counter = config.createCounter(); if (counter instanceof SampledCounterImpl) { SampledCounterImpl sampledCounter = (SampledCounterImpl) counter; timer.schedule(sampledCounter.getTimerTask(), sampledCounter.getIntervalMillis(), sampledCounter.getIntervalMillis()); } counters.add(counter); return counter; } /** * {@inheritDoc} */ public void shutdownCounter(Counter counter) { if (counter instanceof SampledCounter) { SampledCounter sc = (SampledCounter) counter; sc.shutdown(); } } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/SampledCounter.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; import org.quartz.utils.counter.Counter; /** * Interface of a sampled counter -- a counter that keeps sampled values * * @author Abhishek Sanoujam * @since 1.8 * */ public interface SampledCounter extends Counter { /** * Shutdown this counter */ void shutdown(); /** * Returns the most recent sampled value * * @return Value of the most recent sampled value */ TimeStampedCounterValue getMostRecentSample(); /** * Returns all samples in history * * @return An array containing the TimeStampedCounterValue's */ TimeStampedCounterValue[] getAllSampleValues(); /** * Returns the current value of the counter and resets it to 0 * * @return current value of the counter */ long getAndReset(); } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/SampledCounterConfig.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; import org.quartz.utils.counter.Counter; import org.quartz.utils.counter.CounterConfig; /** * Config for a {@link SampledCounter} * * @author Abhishek Sanoujam * @since 1.7 * */ public class SampledCounterConfig extends CounterConfig { private final int intervalSecs; private final int historySize; private final boolean isReset; /** * Make a new timed counter config (duh) * * @param intervalSecs * the interval (in seconds) between sampling * @param historySize * number of counter samples that will be retained in memory * @param isResetOnSample * true if the counter should be reset to 0 upon each sample */ public SampledCounterConfig(int intervalSecs, int historySize, boolean isResetOnSample, long initialValue) { super(initialValue); if (intervalSecs < 1) { throw new IllegalArgumentException("Interval (" + intervalSecs + ") must be greater than or equal to 1"); } if (historySize < 1) { throw new IllegalArgumentException("History size (" + historySize + ") must be greater than or equal to 1"); } this.intervalSecs = intervalSecs; this.historySize = historySize; this.isReset = isResetOnSample; } /** * Returns the history size * * @return The history size */ public int getHistorySize() { return historySize; } /** * Returns the interval time (seconds) * * @return Interval of the sampling thread in seconds */ public int getIntervalSecs() { return intervalSecs; } /** * Returns true if counters created from this config will reset on each * sample * * @return true if values are reset to the initial value after each sample */ public boolean isResetOnSample() { return this.isReset; } /** * {@inheritDoc} */ @Override public Counter createCounter() { return new SampledCounterImpl(this); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/SampledCounterImpl.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; import java.util.TimerTask; import org.quartz.utils.CircularLossyQueue; import org.quartz.utils.counter.CounterImpl; /** * An implementation of {@link SampledCounter} * * @author Abhishek Sanoujam * @since 1.7 * */ public class SampledCounterImpl extends CounterImpl implements SampledCounter { private static final long serialVersionUID = -3605369302464131521L; private static final int MILLIS_PER_SEC = 1000; /** * The history of this counter */ protected final CircularLossyQueue history; /** * Should the counter reset on each sample? */ protected final boolean resetOnSample; private final TimerTask samplerTask; private final long intervalMillis; /** * Constructor accepting a {@link SampledCounterConfig} * * @param config */ public SampledCounterImpl(SampledCounterConfig config) { super(config.getInitialValue()); this.intervalMillis = config.getIntervalSecs() * MILLIS_PER_SEC; this.history = new CircularLossyQueue<>(config.getHistorySize()); this.resetOnSample = config.isResetOnSample(); this.samplerTask = new TimerTask() { @Override public void run() { recordSample(); } }; recordSample(); } /** * {@inheritDoc} */ public TimeStampedCounterValue getMostRecentSample() { return this.history.peek(); } /** * {@inheritDoc} */ public TimeStampedCounterValue[] getAllSampleValues() { return this.history.toArray(new TimeStampedCounterValue[this.history.depth()]); } /** * {@inheritDoc} */ public void shutdown() { if (samplerTask != null) { samplerTask.cancel(); } } /** * Returns the timer task for this sampled counter * * @return the timer task for this sampled counter */ public TimerTask getTimerTask() { return this.samplerTask; } /** * Returns the sampling thread interval in millis * * @return the sampling thread interval in millis */ public long getIntervalMillis() { return intervalMillis; } /** * {@inheritDoc} */ void recordSample() { final long sample; if (resetOnSample) { sample = getAndReset(); } else { sample = getValue(); } final long now = System.currentTimeMillis(); TimeStampedCounterValue timedSample = new TimeStampedCounterValue(now, sample); history.push(timedSample); } /** * {@inheritDoc} */ public long getAndReset() { return getAndSet(0L); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/SampledRateCounter.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; /** * Interface of a sampled rate counter -- a counter that keeps sampled values of * rates * * @author Abhishek Sanoujam * @since 1.8 * */ public interface SampledRateCounter extends SampledCounter { /** * Increments the numerator and denominator by the passed values * * @param numerator * @param denominator */ void increment(long numerator, long denominator); /** * Decrements the numerator and denominator by the passed values * * @param numerator * @param denominator */ void decrement(long numerator, long denominator); /** * Sets the values of the numerator and denominator to the passed values * * @param numerator * @param denominator */ void setValue(long numerator, long denominator); /** * Sets the value of the numerator to the passed value * * @param newValue */ void setNumeratorValue(long newValue); /** * Sets the value of the denominator to the passed value * * @param newValue */ void setDenominatorValue(long newValue); } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/SampledRateCounterConfig.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; import org.quartz.utils.counter.Counter; /** * An implementation of {@link SampledCounterConfig} * * @author Abhishek Sanoujam * @since 1.8 * */ public class SampledRateCounterConfig extends SampledCounterConfig { private final long initialNumeratorValue; private final long initialDenominatorValue; /** * Constructor accepting the interval time in seconds, history-size and * whether counters should reset on each sample or not. * Initial values of both numerator and denominator are zeroes * * @param intervalSecs * @param historySize * @param isResetOnSample */ public SampledRateCounterConfig(int intervalSecs, int historySize, boolean isResetOnSample) { this(intervalSecs, historySize, isResetOnSample, 0, 0); } /** * Constructor accepting the interval time in seconds, history-size and * whether counters should reset on each sample or not. Also the initial * values for the numerator and the denominator * * @param intervalSecs * @param historySize * @param isResetOnSample * @param initialNumeratorValue * @param initialDenominatorValue */ public SampledRateCounterConfig(int intervalSecs, int historySize, boolean isResetOnSample, long initialNumeratorValue, long initialDenominatorValue) { super(intervalSecs, historySize, isResetOnSample, 0); this.initialNumeratorValue = initialNumeratorValue; this.initialDenominatorValue = initialDenominatorValue; } /** * {@inheritDoc} */ @Override public Counter createCounter() { SampledRateCounterImpl sampledRateCounter = new SampledRateCounterImpl(this); sampledRateCounter.setValue(initialNumeratorValue, initialDenominatorValue); return sampledRateCounter; } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/SampledRateCounterImpl.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; /** * An implementation of {@link SampledRateCounter} * * @author Abhishek Sanoujam * @since 1.8 * */ public class SampledRateCounterImpl extends SampledCounterImpl implements SampledRateCounter { private static final long serialVersionUID = 6531350452676920607L; private static final String OPERATION_NOT_SUPPORTED_MSG = "This operation is not supported. Use SampledCounter Or Counter instead"; private long numeratorValue; private long denominatorValue; /** * Constructor accepting the config * * @param config */ public SampledRateCounterImpl(SampledRateCounterConfig config) { super(config); } /** * {@inheritDoc} */ public synchronized void setValue(long numerator, long denominator) { this.numeratorValue = numerator; this.denominatorValue = denominator; } /** * {@inheritDoc} */ public synchronized void increment(long numerator, long denominator) { this.numeratorValue += numerator; this.denominatorValue += denominator; } /** * {@inheritDoc} */ public synchronized void decrement(long numerator, long denominator) { this.numeratorValue -= numerator; this.denominatorValue -= denominator; } /** * {@inheritDoc} */ public synchronized void setDenominatorValue(long newValue) { this.denominatorValue = newValue; } /** * {@inheritDoc} */ public synchronized void setNumeratorValue(long newValue) { this.numeratorValue = newValue; } /** * {@inheritDoc} */ @Override public synchronized long getValue() { return denominatorValue == 0 ? 0 : (numeratorValue / denominatorValue); } /** * {@inheritDoc} */ @Override public synchronized long getAndReset() { long prevVal = getValue(); setValue(0, 0); return prevVal; } // ====== unsupported operations. These operations need multiple params for // this class /** * throws {@link UnsupportedOperationException} */ @Override public long getAndSet(long newValue) { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ @Override public synchronized void setValue(long newValue) { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ @Override public long decrement() { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ @Override public long decrement(long amount) { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ public long getMaxValue() { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ public long getMinValue() { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ @Override public long increment() { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } /** * throws {@link UnsupportedOperationException} */ @Override public long increment(long amount) { throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED_MSG); } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/counter/sampled/TimeStampedCounterValue.java ================================================ /** * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * 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 org.quartz.utils.counter.sampled; import java.io.Serializable; /** * A counter value at a particular time instance * * @author Abhishek Sanoujam * @since 1.8 */ public class TimeStampedCounterValue implements Serializable { private static final long serialVersionUID = 1931111347823687672L; private final long counterValue; private final long timestamp; /** * Constructor accepting the value of both timestamp and the counter value. * * @param timestamp * @param value */ public TimeStampedCounterValue(long timestamp, long value) { this.timestamp = timestamp; this.counterValue = value; } /** * Get the counter value * * @return The counter value */ public long getCounterValue() { return this.counterValue; } /** * Get value of the timestamp * * @return the timestamp associated with the current value */ public long getTimestamp() { return this.timestamp; } /** * {@inheritDoc} */ @Override public String toString() { return "value: " + this.counterValue + ", timestamp: " + this.timestamp; } } ================================================ FILE: quartz/src/main/java/org/quartz/utils/weblogic/WeblogicConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils.weblogic; import java.sql.Connection; import java.sql.SQLException; import org.quartz.utils.ConnectionProvider; import weblogic.jdbc.jts.Driver; /** *

* Provides connections via Weblogic's JTS driver. *

* * @see org.quartz.utils.ConnectionProvider * @see org.quartz.utils.DBConnectionManager * * @author Mohammad Rezaei * @author James House */ @SuppressWarnings("deprecation") public class WeblogicConnectionProvider implements ConnectionProvider { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private final String poolName; private weblogic.jdbc.jts.Driver driver; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public WeblogicConnectionProvider(String poolName) { this.poolName = poolName; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public Connection getConnection() throws SQLException { return driver.connect("jdbc:weblogic:jts:" + poolName, (java.util.Properties) null); } public void initialize() throws SQLException { try { driver = (Driver) weblogic.jdbc.jts.Driver.class.newInstance(); } catch (Exception e) { throw new SQLException( "Could not get weblogic pool connection with name '" + poolName + "': " + e.getClass().getName() + ": " + e.getMessage()); } } public void shutdown() throws SQLException { // do nothing } } ================================================ FILE: quartz/src/main/java/org/quartz/xml/ValidationException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.xml; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; /** * Reports JobSchedulingDataLoader validation exceptions. * * @author Chris Bonham */ public class ValidationException extends Exception { private static final long serialVersionUID = -1697832087051681357L; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Collection validationExceptions = new ArrayList<>(); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Constructor for ValidationException. */ public ValidationException() { super(); } /** * Constructor for ValidationException. * * @param message * exception message. */ public ValidationException(String message) { super(message); } /** * Constructor for ValidationException. * * @param errors * collection of validation exceptions. */ public ValidationException(Collection errors) { this(); this.validationExceptions = Collections .unmodifiableCollection(validationExceptions); initCause(errors.iterator().next()); } /** * Constructor for ValidationException. * * @param message * exception message. * @param errors * collection of validation exceptions. */ public ValidationException(String message, Collection errors) { this(message); this.validationExceptions = Collections .unmodifiableCollection(validationExceptions); initCause(errors.iterator().next()); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Returns collection of errors. * * @return collection of errors. */ public Collection getValidationExceptions() { return validationExceptions; } /** * Returns the detail message string. * * @return the detail message string. */ @Override public String getMessage() { if (getValidationExceptions().isEmpty()) { return super.getMessage(); } StringBuilder messageBuilder = new StringBuilder(); boolean isFirst = true; for (Exception e : getValidationExceptions()) { if (!isFirst) { messageBuilder.append('\n'); } isFirst = false; messageBuilder.append(e.getMessage()); } return messageBuilder.toString(); } } ================================================ FILE: quartz/src/main/java/org/quartz/xml/XMLSchedulingDataProcessor.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.xml; import static org.quartz.CalendarIntervalScheduleBuilder.calendarIntervalSchedule; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import static org.quartz.TriggerKey.triggerKey; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathException; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.quartz.*; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.MutableTrigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import jakarta.xml.bind.DatatypeConverter; /** * Parses an XML file that declares Jobs and their schedules (Triggers), and processes the related data. * * The xml document must conform to the format defined in * "job_scheduling_data_2_0.xsd" * * The same instance can be used again and again, however a single instance is not thread-safe. * * @author James House * @author Past contributions from Chris Bonham * @author Past contributions from pl47ypus * * @since Quartz 1.8 */ public class XMLSchedulingDataProcessor implements ErrorHandler { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static final String QUARTZ_NS = "http://www.quartz-scheduler.org/xml/JobSchedulingData"; public static final String QUARTZ_SCHEMA_WEB_URL = "http://www.quartz-scheduler.org/xml/job_scheduling_data_2_0.xsd"; public static final String QUARTZ_XSD_PATH_IN_JAR = "org/quartz/xml/job_scheduling_data_2_0.xsd"; public static final String QUARTZ_XML_DEFAULT_FILE_NAME = "quartz_data.xml"; public static final String QUARTZ_SYSTEM_ID_JAR_PREFIX = "jar:"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // pre-processing commands protected final List jobGroupsToDelete = new LinkedList<>(); protected final List triggerGroupsToDelete = new LinkedList<>(); protected final List jobsToDelete = new LinkedList<>(); protected final List triggersToDelete = new LinkedList<>(); // scheduling commands protected final List loadedJobs = new LinkedList<>(); protected final List loadedTriggers = new LinkedList<>(); // directives private boolean overWriteExistingData = true; private boolean ignoreDuplicates = false; protected final Collection validationExceptions = new ArrayList<>(); protected final ClassLoadHelper classLoadHelper; protected final List jobGroupsToNeverDelete = new LinkedList<>(); protected final List triggerGroupsToNeverDelete = new LinkedList<>(); private DocumentBuilder docBuilder = null; private XPath xpath = null; private final Logger log = LoggerFactory.getLogger(getClass()); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Constructor for JobSchedulingDataLoader. * * @param clh class-loader helper to share with digester. * @throws ParserConfigurationException if the XML parser cannot be configured as needed. */ public XMLSchedulingDataProcessor(ClassLoadHelper clh) throws ParserConfigurationException { this.classLoadHelper = clh; initDocumentParser(); } /** * Initializes the XML parser. * @throws ParserConfigurationException */ protected void initDocumentParser() throws ParserConfigurationException { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(true); docBuilderFactory.setValidating(true); docBuilderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); docBuilderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", resolveSchemaSource()); docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); docBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); docBuilderFactory.setXIncludeAware(false); docBuilderFactory.setExpandEntityReferences(false); docBuilder = docBuilderFactory.newDocumentBuilder(); docBuilder.setErrorHandler(this); NamespaceContext nsContext = new NamespaceContext() { public String getNamespaceURI(String prefix) { if (prefix == null) throw new IllegalArgumentException("Null prefix"); switch (prefix) { case XMLConstants.XML_NS_PREFIX: return XMLConstants.XML_NS_URI; case XMLConstants.XMLNS_ATTRIBUTE: return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; case "q": return QUARTZ_NS; } return XMLConstants.NULL_NS_URI; } public Iterator getPrefixes(String namespaceURI) { // This method isn't necessary for XPath processing. throw new UnsupportedOperationException(); } public String getPrefix(String namespaceURI) { // This method isn't necessary for XPath processing. throw new UnsupportedOperationException(); } }; xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(nsContext); } protected Object resolveSchemaSource() { InputSource inputSource; InputStream is = null; try { is = classLoadHelper.getResourceAsStream(QUARTZ_XSD_PATH_IN_JAR); } finally { if (is != null) { inputSource = new InputSource(is); inputSource.setSystemId(QUARTZ_SCHEMA_WEB_URL); log.debug("Utilizing schema packaged in local quartz distribution jar."); } else { log.info("Unable to load local schema packaged in quartz distribution jar. Utilizing schema online at " + QUARTZ_SCHEMA_WEB_URL); return QUARTZ_SCHEMA_WEB_URL; } } return inputSource; } /** * Whether the existing scheduling data (with same identifiers) will be * overwritten. * * If false, and IgnoreDuplicates is not false, and jobs or * triggers with the same names already exist as those in the file, an * error will occur. * * @see #isIgnoreDuplicates() */ public boolean isOverWriteExistingData() { return overWriteExistingData; } /** * Whether the existing scheduling data (with same identifiers) will be * overwritten. * * If false, and IgnoreDuplicates is not false, and jobs or * triggers with the same names already exist as those in the file, an * error will occur. * * @see #setIgnoreDuplicates(boolean) */ protected void setOverWriteExistingData(boolean overWriteExistingData) { this.overWriteExistingData = overWriteExistingData; } /** * If true (and OverWriteExistingData is false) then any * job/triggers encountered in this file that have names that already exist * in the scheduler will be ignored, and no error will be produced. * * @see #isOverWriteExistingData() */ public boolean isIgnoreDuplicates() { return ignoreDuplicates; } /** * If true (and OverWriteExistingData is false) then any * job/triggers encountered in this file that have names that already exist * in the scheduler will be ignored, and no error will be produced. * * @see #setOverWriteExistingData(boolean) */ public void setIgnoreDuplicates(boolean ignoreDuplicates) { this.ignoreDuplicates = ignoreDuplicates; } /** * Add the given group to the list of job groups that will never be * deleted by this processor, even if a pre-processing-command to * delete the group is encountered. */ public void addJobGroupToNeverDelete(String group) { if(group != null) jobGroupsToNeverDelete.add(group); } /** * Remove the given group to the list of job groups that will never be * deleted by this processor, even if a pre-processing-command to * delete the group is encountered. */ public boolean removeJobGroupToNeverDelete(String group) { return group != null && jobGroupsToNeverDelete.remove(group); } /** * Get the (unmodifiable) list of job groups that will never be * deleted by this processor, even if a pre-processing-command to * delete the group is encountered. */ public List getJobGroupsToNeverDelete() { return Collections.unmodifiableList(jobGroupsToDelete); } /** * Add the given group to the list of trigger groups that will never be * deleted by this processor, even if a pre-processing-command to * delete the group is encountered. */ public void addTriggerGroupToNeverDelete(String group) { if(group != null) triggerGroupsToNeverDelete.add(group); } /** * Remove the given group to the list of trigger groups that will never be * deleted by this processor, even if a pre-processing-command to * delete the group is encountered. */ public boolean removeTriggerGroupToNeverDelete(String group) { if(group != null) return triggerGroupsToNeverDelete.remove(group); return false; } /** * Get the (unmodifiable) list of trigger groups that will never be * deleted by this processor, even if a pre-processing-command to * delete the group is encountered. */ public List getTriggerGroupsToNeverDelete() { return Collections.unmodifiableList(triggerGroupsToDelete); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Process the xml file in the default location (a file named * "quartz_jobs.xml" in the current working directory). * */ protected void processFile() throws Exception { processFile(QUARTZ_XML_DEFAULT_FILE_NAME); } /** * Process the xml file named fileName. * * @param fileName * meta data file name. */ protected void processFile(String fileName) throws Exception { processFile(fileName, getSystemIdForFileName(fileName)); } /** * For the given fileName, attempt to expand it to its full path * for use as a system id. * * @see #getURL(String) * @see #processFile() * @see #processFile(String) * @see #processFileAndScheduleJobs(Scheduler, boolean) * @see #processFileAndScheduleJobs(String, org.quartz.Scheduler) */ protected String getSystemIdForFileName(String fileName) { File file = new File(fileName); // files in filesystem if (file.exists()) { try { new FileInputStream(file).close(); return file.toURI().toString(); }catch (IOException ignore) { return fileName; } } else { URL url = getURL(fileName); if (url == null) { return fileName; } else { try { url.openStream().close(); return url.toString(); } catch (IOException ignore) { return fileName; } } } } /** * Returns an URL from the fileName as a resource. * * @param fileName * file name. * @return an URL from the fileName as a resource. */ protected URL getURL(String fileName) { return classLoadHelper.getResource(fileName); } protected void prepForProcessing() { clearValidationExceptions(); setOverWriteExistingData(true); setIgnoreDuplicates(false); jobGroupsToDelete.clear(); jobsToDelete.clear(); triggerGroupsToDelete.clear(); triggersToDelete.clear(); loadedJobs.clear(); loadedTriggers.clear(); } /** * Process the xmlfile named fileName with the given system * ID. * * @param fileName * meta data file name. * @param systemId * system ID. */ protected void processFile(String fileName, String systemId) throws ValidationException, ParserConfigurationException, SAXException, IOException, SchedulerException, ClassNotFoundException, ParseException, XPathException { prepForProcessing(); log.info("Parsing XML file: {} with systemId: {}", fileName, systemId); InputSource is = new InputSource(getInputStream(fileName)); is.setSystemId(systemId); process(is); maybeThrowValidationException(); } /** * Process the xmlfile named fileName with the given system * ID. * * @param stream * an input stream containing the xml content. * @param systemId * system ID. */ public void processStreamAndScheduleJobs(InputStream stream, String systemId, Scheduler sched) throws ValidationException, ParserConfigurationException, SAXException, XPathException, IOException, SchedulerException, ClassNotFoundException, ParseException { prepForProcessing(); log.info("Parsing XML from stream with systemId: {}", systemId); InputSource is = new InputSource(stream); is.setSystemId(systemId); process(is); executePreProcessCommands(sched); scheduleJobs(sched); maybeThrowValidationException(); } @SuppressWarnings("ConstantConditions") protected void process(InputSource is) throws SAXException, IOException, ParseException, XPathException, ClassNotFoundException { // load the document Document document = docBuilder.parse(is); // // Extract pre-processing commands // NodeList deleteJobGroupNodes = (NodeList) xpath.evaluate( "/q:job-scheduling-data/q:pre-processing-commands/q:delete-jobs-in-group", document, XPathConstants.NODESET); log.debug("Found {} delete job group commands.", deleteJobGroupNodes.getLength()); for (int i = 0; i < deleteJobGroupNodes.getLength(); i++) { Node node = deleteJobGroupNodes.item(i); String t = node.getTextContent(); if(t == null || (t = t.trim()).isEmpty()) continue; jobGroupsToDelete.add(t); } NodeList deleteTriggerGroupNodes = (NodeList) xpath.evaluate( "/q:job-scheduling-data/q:pre-processing-commands/q:delete-triggers-in-group", document, XPathConstants.NODESET); log.debug("Found {} delete trigger group commands.", deleteTriggerGroupNodes.getLength()); for (int i = 0; i < deleteTriggerGroupNodes.getLength(); i++) { Node node = deleteTriggerGroupNodes.item(i); String t = node.getTextContent(); if(t == null || (t = t.trim()).isEmpty()) continue; triggerGroupsToDelete.add(t); } NodeList deleteJobNodes = (NodeList) xpath.evaluate( "/q:job-scheduling-data/q:pre-processing-commands/q:delete-job", document, XPathConstants.NODESET); log.debug("Found {} delete job commands.", deleteJobNodes.getLength()); for (int i = 0; i < deleteJobNodes.getLength(); i++) { Node node = deleteJobNodes.item(i); String name = getTrimmedToNullString(xpath, "q:name", node); String group = getTrimmedToNullString(xpath, "q:group", node); if(name == null) throw new ParseException("Encountered a 'delete-job' command without a name specified.", -1); jobsToDelete.add(new JobKey(name, group)); } NodeList deleteTriggerNodes = (NodeList) xpath.evaluate( "/q:job-scheduling-data/q:pre-processing-commands/q:delete-trigger", document, XPathConstants.NODESET); log.debug("Found {} delete trigger commands.", deleteTriggerNodes.getLength()); for (int i = 0; i < deleteTriggerNodes.getLength(); i++) { Node node = deleteTriggerNodes.item(i); String name = getTrimmedToNullString(xpath, "q:name", node); String group = getTrimmedToNullString(xpath, "q:group", node); if(name == null) throw new ParseException("Encountered a 'delete-trigger' command without a name specified.", -1); triggersToDelete.add(new TriggerKey(name, group)); } // // Extract directives // Boolean overWrite = getBoolean(xpath, "/q:job-scheduling-data/q:processing-directives/q:overwrite-existing-data", document); if(overWrite == null) { log.debug("Directive 'overwrite-existing-data' not specified, defaulting to {}", isOverWriteExistingData()); } else { log.debug("Directive 'overwrite-existing-data' specified as: {}", overWrite); setOverWriteExistingData(overWrite); } Boolean ignoreDupes = getBoolean(xpath, "/q:job-scheduling-data/q:processing-directives/q:ignore-duplicates", document); if(ignoreDupes == null) { log.debug("Directive 'ignore-duplicates' not specified, defaulting to {}", isIgnoreDuplicates()); } else { log.debug("Directive 'ignore-duplicates' specified as: {}", ignoreDupes); setIgnoreDuplicates(ignoreDupes); } // // Extract Job definitions... // NodeList jobNodes = (NodeList) xpath.evaluate("/q:job-scheduling-data/q:schedule/q:job", document, XPathConstants.NODESET); log.debug("Found {} job definitions.", jobNodes.getLength()); for (int i = 0; i < jobNodes.getLength(); i++) { Node jobDetailNode = jobNodes.item(i); String t; String jobName = getTrimmedToNullString(xpath, "q:name", jobDetailNode); String jobGroup = getTrimmedToNullString(xpath, "q:group", jobDetailNode); String jobDescription = getTrimmedToNullString(xpath, "q:description", jobDetailNode); String jobClassName = getTrimmedToNullString(xpath, "q:job-class", jobDetailNode); t = getTrimmedToNullString(xpath, "q:durability", jobDetailNode); boolean jobDurability = (t != null) && t.equals("true"); t = getTrimmedToNullString(xpath, "q:recover", jobDetailNode); boolean jobRecoveryRequested = (t != null) && t.equals("true"); Class jobClass = classLoadHelper.loadClass(jobClassName, Job.class); JobDetail jobDetail = newJob(jobClass) .withIdentity(jobName, jobGroup) .withDescription(jobDescription) .storeDurably(jobDurability) .requestRecovery(jobRecoveryRequested) .build(); NodeList jobDataEntries = (NodeList) xpath.evaluate( "q:job-data-map/q:entry", jobDetailNode, XPathConstants.NODESET); for (int k = 0; k < jobDataEntries.getLength(); k++) { Node entryNode = jobDataEntries.item(k); String key = getTrimmedToNullString(xpath, "q:key", entryNode); String value = getTrimmedToNullString(xpath, "q:value", entryNode); jobDetail.getJobDataMap().put(key, value); } if(log.isDebugEnabled()) log.debug("Parsed job definition: {}", jobDetail); addJobToSchedule(jobDetail); } // // Extract Trigger definitions... // NodeList triggerEntries = (NodeList) xpath.evaluate( "/q:job-scheduling-data/q:schedule/q:trigger/*", document, XPathConstants.NODESET); log.debug("Found {} trigger definitions.", triggerEntries.getLength()); for (int j = 0; j < triggerEntries.getLength(); j++) { Node triggerNode = triggerEntries.item(j); String triggerName = getTrimmedToNullString(xpath, "q:name", triggerNode); String triggerGroup = getTrimmedToNullString(xpath, "q:group", triggerNode); String triggerDescription = getTrimmedToNullString(xpath, "q:description", triggerNode); String triggerMisfireInstructionConst = getTrimmedToNullString(xpath, "q:misfire-instruction", triggerNode); String triggerPriorityString = getTrimmedToNullString(xpath, "q:priority", triggerNode); String triggerCalendarRef = getTrimmedToNullString(xpath, "q:calendar-name", triggerNode); String triggerJobName = getTrimmedToNullString(xpath, "q:job-name", triggerNode); String triggerJobGroup = getTrimmedToNullString(xpath, "q:job-group", triggerNode); int triggerPriority = Trigger.DEFAULT_PRIORITY; if(triggerPriorityString != null) triggerPriority = Integer.valueOf(triggerPriorityString); String startTimeString = getTrimmedToNullString(xpath, "q:start-time", triggerNode); String startTimeFutureSecsString = getTrimmedToNullString(xpath, "q:start-time-seconds-in-future", triggerNode); String endTimeString = getTrimmedToNullString(xpath, "q:end-time", triggerNode); //QTZ-273 : use of DatatypeConverter.parseDateTime() instead of SimpleDateFormat Date triggerStartTime; if(startTimeFutureSecsString != null) triggerStartTime = new Date(System.currentTimeMillis() + (Long.valueOf(startTimeFutureSecsString) * 1000L)); else triggerStartTime = (startTimeString == null || startTimeString.isEmpty() ? new Date() : DatatypeConverter.parseDateTime(startTimeString).getTime()); Date triggerEndTime = endTimeString == null || endTimeString.isEmpty() ? null : DatatypeConverter.parseDateTime(endTimeString).getTime(); TriggerKey triggerKey = triggerKey(triggerName, triggerGroup); ScheduleBuilder sched; switch (triggerNode.getNodeName()) { case "simple": { String repeatCountString = getTrimmedToNullString(xpath, "q:repeat-count", triggerNode); String repeatIntervalString = getTrimmedToNullString(xpath, "q:repeat-interval", triggerNode); int repeatCount = repeatCountString == null ? 0 : Integer.parseInt(repeatCountString); long repeatInterval = repeatIntervalString == null ? 0 : Long.parseLong(repeatIntervalString); sched = simpleSchedule() .withIntervalInMilliseconds(repeatInterval) .withRepeatCount(repeatCount); if (triggerMisfireInstructionConst != null && !triggerMisfireInstructionConst.isEmpty()) { switch (triggerMisfireInstructionConst) { case "MISFIRE_INSTRUCTION_FIRE_NOW": ((SimpleScheduleBuilder) sched).withMisfireHandlingInstructionFireNow(); break; case "MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT": ((SimpleScheduleBuilder) sched).withMisfireHandlingInstructionNextWithExistingCount(); break; case "MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT": ((SimpleScheduleBuilder) sched).withMisfireHandlingInstructionNextWithRemainingCount(); break; case "MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT": ((SimpleScheduleBuilder) sched).withMisfireHandlingInstructionNowWithExistingCount(); break; case "MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT": ((SimpleScheduleBuilder) sched).withMisfireHandlingInstructionNowWithRemainingCount(); break; case "MISFIRE_INSTRUCTION_SMART_POLICY": // do nothing.... (smart policy is default) break; default: throw new ParseException("Unexpected/Unhandleable Misfire Instruction encountered '" + triggerMisfireInstructionConst + "', for trigger: " + triggerKey, -1); } } break; } case "cron": String cronExpression = getTrimmedToNullString(xpath, "q:cron-expression", triggerNode); String timezoneString = getTrimmedToNullString(xpath, "q:time-zone", triggerNode); TimeZone tz = timezoneString == null ? null : TimeZone.getTimeZone(timezoneString); sched = cronSchedule(cronExpression) .inTimeZone(tz); if (triggerMisfireInstructionConst != null && !triggerMisfireInstructionConst.isEmpty()) { switch (triggerMisfireInstructionConst) { case "MISFIRE_INSTRUCTION_DO_NOTHING": ((CronScheduleBuilder) sched).withMisfireHandlingInstructionDoNothing(); break; case "MISFIRE_INSTRUCTION_FIRE_ONCE_NOW": ((CronScheduleBuilder) sched).withMisfireHandlingInstructionFireAndProceed(); break; case "MISFIRE_INSTRUCTION_SMART_POLICY": // do nothing.... (smart policy is default) break; default: throw new ParseException("Unexpected/Unhandleable Misfire Instruction encountered '" + triggerMisfireInstructionConst + "', for trigger: " + triggerKey, -1); } } break; case "calendar-interval": { String repeatIntervalString = getTrimmedToNullString(xpath, "q:repeat-interval", triggerNode); String repeatUnitString = getTrimmedToNullString(xpath, "q:repeat-interval-unit", triggerNode); int repeatInterval = Integer.parseInt(repeatIntervalString); IntervalUnit repeatUnit = IntervalUnit.valueOf(repeatUnitString); sched = calendarIntervalSchedule() .withInterval(repeatInterval, repeatUnit); if (triggerMisfireInstructionConst != null && !triggerMisfireInstructionConst.isEmpty()) { switch (triggerMisfireInstructionConst) { case "MISFIRE_INSTRUCTION_DO_NOTHING": ((CalendarIntervalScheduleBuilder) sched).withMisfireHandlingInstructionDoNothing(); break; case "MISFIRE_INSTRUCTION_FIRE_ONCE_NOW": ((CalendarIntervalScheduleBuilder) sched).withMisfireHandlingInstructionFireAndProceed(); break; case "MISFIRE_INSTRUCTION_SMART_POLICY": // do nothing.... (smart policy is default) break; default: throw new ParseException("Unexpected/Unhandleable Misfire Instruction encountered '" + triggerMisfireInstructionConst + "', for trigger: " + triggerKey, -1); } } break; } default: throw new ParseException("Unknown trigger type: " + triggerNode.getNodeName(), -1); } MutableTrigger trigger = (MutableTrigger) newTrigger() .withIdentity(triggerName, triggerGroup) .withDescription(triggerDescription) .forJob(triggerJobName, triggerJobGroup) .startAt(triggerStartTime) .endAt(triggerEndTime) .withPriority(triggerPriority) .modifiedByCalendar(triggerCalendarRef) .withSchedule(sched) .build(); NodeList jobDataEntries = (NodeList) xpath.evaluate( "q:job-data-map/q:entry", triggerNode, XPathConstants.NODESET); for (int k = 0; k < jobDataEntries.getLength(); k++) { Node entryNode = jobDataEntries.item(k); String key = getTrimmedToNullString(xpath, "q:key", entryNode); String value = getTrimmedToNullString(xpath, "q:value", entryNode); trigger.getJobDataMap().put(key, value); } if(log.isDebugEnabled()) log.debug("Parsed trigger definition: {}", trigger); addTriggerToSchedule(trigger); } } protected String getTrimmedToNullString(XPath xpathToElement, String elementName, Node parentNode) throws XPathExpressionException { String str = (String) xpathToElement.evaluate(elementName, parentNode, XPathConstants.STRING); if(str != null) str = str.trim(); if(str != null && str.isEmpty()) str = null; return str; } protected Boolean getBoolean(XPath xpathToElement, String elementName, Document document) throws XPathExpressionException { Node directive = (Node) xpathToElement.evaluate(elementName, document, XPathConstants.NODE); if(directive == null || directive.getTextContent() == null) return null; String val = directive.getTextContent(); if(val.equalsIgnoreCase("true") || val.equalsIgnoreCase("yes") || val.equalsIgnoreCase("y")) return Boolean.TRUE; return Boolean.FALSE; } /** * Process the xml file in the default location, and schedule all of the * jobs defined within it. * *

Note that we will set overWriteExistingJobs after the default xml is parsed. */ public void processFileAndScheduleJobs(Scheduler sched, boolean overWriteExistingJobs) throws Exception { String fileName = QUARTZ_XML_DEFAULT_FILE_NAME; processFile(fileName, getSystemIdForFileName(fileName)); // The overWriteExistingJobs flag was set by processFile() -> prepForProcessing(), then by xml parsing, and then now // we need to reset it again here by this method parameter to override it. setOverWriteExistingData(overWriteExistingJobs); executePreProcessCommands(sched); scheduleJobs(sched); } /** * Process the xml file in the given location, and schedule all of the * jobs defined within it. * * @param fileName * meta data file name. */ public void processFileAndScheduleJobs(String fileName, Scheduler sched) throws Exception { processFileAndScheduleJobs(fileName, getSystemIdForFileName(fileName), sched); } /** * Process the xml file in the given location, and schedule all of the * jobs defined within it. * * @param fileName * meta data file name. */ public void processFileAndScheduleJobs(String fileName, String systemId, Scheduler sched) throws Exception { processFile(fileName, systemId); executePreProcessCommands(sched); scheduleJobs(sched); } /** * Returns a List of jobs loaded from the xml file. * * @return a List of jobs. */ protected List getLoadedJobs() { return Collections.unmodifiableList(loadedJobs); } /** * Returns a List of triggers loaded from the xml file. * * @return a List of triggers. */ protected List getLoadedTriggers() { return Collections.unmodifiableList(loadedTriggers); } /** * Returns an InputStream from the fileName as a resource. * * @param fileName * file name. * @return an InputStream from the fileName as a resource. */ protected InputStream getInputStream(String fileName) { return this.classLoadHelper.getResourceAsStream(fileName); } protected void addJobToSchedule(JobDetail job) { loadedJobs.add(job); } protected void addTriggerToSchedule(MutableTrigger trigger) { loadedTriggers.add(trigger); } private Map> buildTriggersByFQJobNameMap(List triggers) { Map> triggersByFQJobName = new HashMap<>(); for(MutableTrigger trigger: triggers) { List triggersOfJob = triggersByFQJobName.computeIfAbsent(trigger.getJobKey(), k -> new LinkedList<>()); triggersOfJob.add(trigger); } return triggersByFQJobName; } protected void executePreProcessCommands(Scheduler scheduler) throws SchedulerException { for(String group: jobGroupsToDelete) { if(group.equals("*")) { log.info("Deleting all jobs in ALL groups."); for (String groupName : scheduler.getJobGroupNames()) { if (!jobGroupsToNeverDelete.contains(groupName)) { for (JobKey key : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { scheduler.deleteJob(key); } } } } else { if(!jobGroupsToNeverDelete.contains(group)) { log.info("Deleting all jobs in group: {}", group); for (JobKey key : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group))) { scheduler.deleteJob(key); } } } } for(String group: triggerGroupsToDelete) { if(group.equals("*")) { log.info("Deleting all triggers in ALL groups."); for (String groupName : scheduler.getTriggerGroupNames()) { if (!triggerGroupsToNeverDelete.contains(groupName)) { for (TriggerKey key : scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(groupName))) { scheduler.unscheduleJob(key); } } } } else { if(!triggerGroupsToNeverDelete.contains(group)) { log.info("Deleting all triggers in group: {}", group); for (TriggerKey key : scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(group))) { scheduler.unscheduleJob(key); } } } } for(JobKey key: jobsToDelete) { if(!jobGroupsToNeverDelete.contains(key.getGroup())) { log.info("Deleting job: {}", key); scheduler.deleteJob(key); } } for(TriggerKey key: triggersToDelete) { if(!triggerGroupsToNeverDelete.contains(key.getGroup())) { log.info("Deleting trigger: {}", key); scheduler.unscheduleJob(key); } } } /** * Schedules the given sets of jobs and triggers. * * @param sched * job scheduler. * @exception SchedulerException * if the Job or Trigger cannot be added to the Scheduler, or * there is an internal Scheduler error. */ @SuppressWarnings("ConstantConditions") protected void scheduleJobs(Scheduler sched) throws SchedulerException { List jobs = new LinkedList<>(getLoadedJobs()); List triggers = new LinkedList<>(getLoadedTriggers()); log.info("Adding {} jobs, {} triggers.", jobs.size(), triggers.size()); Map> triggersByFQJobName = buildTriggersByFQJobNameMap(triggers); // add each job, and it's associated triggers Iterator itr = jobs.iterator(); while(itr.hasNext()) { JobDetail detail = itr.next(); itr.remove(); // remove jobs as we handle them... JobDetail dupeJ = null; try { // The existing job could have been deleted, and Quartz API doesn't allow us to query this without // loading the job class, so use try/catch to handle it. dupeJ = sched.getJobDetail(detail.getKey()); } catch (JobPersistenceException e) { if (e.getCause() instanceof ClassNotFoundException && isOverWriteExistingData()) { // We are going to replace jobDetail anyway, so just delete it first. log.info("Removing job: {}", detail.getKey()); sched.deleteJob(detail.getKey()); } else { throw e; } } if ((dupeJ != null)) { if(!isOverWriteExistingData() && isIgnoreDuplicates()) { log.info("Not overwriting existing job: {}", dupeJ.getKey()); continue; // just ignore the entry } if(!isOverWriteExistingData() && !isIgnoreDuplicates()) { throw new ObjectAlreadyExistsException(detail); } } if (dupeJ != null) { log.info("Replacing job: {}", detail.getKey()); } else { log.info("Adding job: {}", detail.getKey()); } List triggersOfJob = triggersByFQJobName.get(detail.getKey()); if (!detail.isDurable() && (triggersOfJob == null || triggersOfJob.isEmpty())) { if (dupeJ == null) { throw new SchedulerException( "A new job defined without any triggers must be durable: " + detail.getKey()); } if ((dupeJ.isDurable() && (sched.getTriggersOfJob( detail.getKey()).isEmpty()))) { throw new SchedulerException( "Can't change existing durable job without triggers to non-durable: " + detail.getKey()); } } if(dupeJ != null || detail.isDurable()) { // add the job only if a replacement or durable, else exception will throw! sched.addJob(detail, true, triggersOfJob != null && !triggersOfJob.isEmpty()); // add the job regardless is durable or not b/c we have trigger to add } else { boolean addJobWithFirstSchedule = true; // Add triggers related to the job... for (MutableTrigger trigger : triggersOfJob) { triggers.remove(trigger); // remove triggers as we handle them... if (trigger.getStartTime() == null) { trigger.setStartTime(new Date()); } Trigger dupeT = sched.getTrigger(trigger.getKey()); if (dupeT != null) { if (isOverWriteExistingData()) { if (log.isDebugEnabled()) { log.debug("Rescheduling job: {} with updated trigger: {}", trigger.getJobKey(), trigger.getKey()); } } else if (isIgnoreDuplicates()) { log.info("Not overwriting existing trigger: {}", dupeT.getKey()); continue; // just ignore the trigger (and possibly job) } else { throw new ObjectAlreadyExistsException(trigger); } if (!dupeT.getJobKey().equals(trigger.getJobKey())) { log.warn("Possibly duplicately named ({}) triggers in jobs xml file! ", trigger.getKey()); } sched.rescheduleJob(trigger.getKey(), trigger); } else { if (log.isDebugEnabled()) { log.debug("Scheduling job: {} with trigger: {}", trigger.getJobKey(), trigger.getKey()); } try { if (addJobWithFirstSchedule) { sched.scheduleJob(detail, trigger); // add the job if it's not in yet... addJobWithFirstSchedule = false; } else { sched.scheduleJob(trigger); } } catch (ObjectAlreadyExistsException e) { if (log.isDebugEnabled()) { log.debug("Adding trigger: {} for job: {} failed because the trigger already existed. This is likely due to a race condition between multiple instances in the cluster. Will try to reschedule instead.", trigger.getKey(), detail.getKey()); } // Let's try one more time as reschedule. sched.rescheduleJob(trigger.getKey(), trigger); } } } } } // add triggers that weren't associated with a new job... (those we already handled were removed above) for(MutableTrigger trigger: triggers) { if(trigger.getStartTime() == null) { trigger.setStartTime(new Date()); } Trigger dupeT = sched.getTrigger(trigger.getKey()); if (dupeT != null) { if(isOverWriteExistingData()) { if (log.isDebugEnabled()) { log.debug("Rescheduling job: {} with updated trigger: {}", trigger.getJobKey(), trigger.getKey()); } } else if(isIgnoreDuplicates()) { log.info("Not overwriting existing trigger: {}", dupeT.getKey()); continue; // just ignore the trigger } else { throw new ObjectAlreadyExistsException(trigger); } if(!dupeT.getJobKey().equals(trigger.getJobKey())) { log.warn("Possibly duplicately named ({}) triggers in jobs xml file! ", trigger.getKey()); } sched.rescheduleJob(trigger.getKey(), trigger); } else { if (log.isDebugEnabled()) { log.debug("Scheduling job: {} with trigger: {}", trigger.getJobKey(), trigger.getKey()); } try { sched.scheduleJob(trigger); } catch (ObjectAlreadyExistsException e) { if (log.isDebugEnabled()) { log.debug("Adding trigger: {} for job: {} failed because the trigger already existed. This is likely due to a race condition between multiple instances in the cluster. Will try to reschedule instead.", trigger.getKey(), trigger.getJobKey()); } // Let's rescheduleJob one more time. sched.rescheduleJob(trigger.getKey(), trigger); } } } } /** * ErrorHandler interface. * * Receive notification of a warning. * * @param e * The error information encapsulated in a SAX parse exception. * @exception SAXException * Any SAX exception, possibly wrapping another exception. */ public void warning(SAXParseException e) throws SAXException { addValidationException(e); } /** * ErrorHandler interface. * * Receive notification of a recoverable error. * * @param e * The error information encapsulated in a SAX parse exception. * @exception SAXException * Any SAX exception, possibly wrapping another exception. */ public void error(SAXParseException e) throws SAXException { addValidationException(e); } /** * ErrorHandler interface. * * Receive notification of a non-recoverable error. * * @param e * The error information encapsulated in a SAX parse exception. * @exception SAXException * Any SAX exception, possibly wrapping another exception. */ public void fatalError(SAXParseException e) throws SAXException { addValidationException(e); } /** * Adds a detected validation exception. * * @param e * SAX exception. */ protected void addValidationException(SAXException e) { validationExceptions.add(e); } /** * Resets the number of detected validation exceptions. */ protected void clearValidationExceptions() { validationExceptions.clear(); } /** * Throws a ValidationException if the number of validationExceptions * detected is greater than zero. * * @exception ValidationException * DTD validation exception. */ protected void maybeThrowValidationException() throws ValidationException { if (!validationExceptions.isEmpty()) { throw new ValidationException("Encountered " + validationExceptions.size() + " validation exceptions.", validationExceptions); } } } ================================================ FILE: quartz/src/main/java/overview.html ================================================ This document is the API specification for Quartz. ================================================ FILE: quartz/src/main/resources/checkstyle.xml ================================================ ================================================ FILE: quartz/src/main/resources/org/quartz/core/quartz-build.properties ================================================ fullname=$fullname name=$name version=$version ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/liquibase.quartz.init.xml ================================================ ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_cloudscape.sql ================================================ # # Thanks to Srinivas Venkatarangaiah for submitting this file's contents # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.CloudscapeDelegate # # Known to work with Cloudscape 3.6.4 (should work with others) # create table qrtz_job_details ( sched_name varchar(120) not null, job_name varchar(200) not null, job_group varchar(200) not null, description varchar(250) , job_class_name varchar(250) not null, is_durable varchar(5) not null, is_nonconcurrent varchar(5) not null, is_update_data varchar(5) not null, requests_recovery varchar(5) not null, job_data long varbinary, primary key (sched_name,job_name,job_group) ); create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, job_name varchar(200) not null, job_group varchar(200) not null, description varchar(250) , next_fire_time longint, prev_fire_time longint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time longint not null, end_time longint, calendar_name varchar(200), misfire_instr smallint, job_data long varbinary, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, repeat_count longint not null, repeat_interval longint not null, times_triggered longint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); CREATE TABLE qrtz_simprop_triggers ( sched_name varchar(120) not null, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 longint NULL, LONG_PROP_2 longint NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 varchar(5) NULL, BOOL_PROP_2 varchar(5) NULL, PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) ); create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, blob_data long varbinary , primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(200) not null, calendar long varbinary not null, primary key (sched_name,calendar_name) ); create table qrtz_paused_trigger_grps ( sched_name varchar(120) not null, trigger_group varchar(200) not null, primary key (sched_name,trigger_group) ); create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, instance_name varchar(200) not null, fired_time longint not null, sched_time longint not null, priority integer not null, state varchar(16) not null, job_name varchar(200) null, job_group varchar(200) null, is_nonconcurrent varchar(5) null, requests_recovery varchar(5) null, primary key (sched_name,entry_id) ); create table qrtz_scheduler_state ( sched_name varchar(120) not null, instance_name varchar(200) not null, last_checkin_time longint not null, checkin_interval longint not null, primary key (sched_name,instance_name) ); create table qrtz_locks ( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_cubrid.sql ================================================ -- Thanks to Timothy Anyona for this script -- CUBRID 8.4.1+ -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.CUBRIDDelegate DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE BIT(1) NOT NULL, IS_NONCONCURRENT BIT(1) NOT NULL, IS_UPDATE_DATA BIT(1) NOT NULL, REQUESTS_RECOVERY BIT(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT NULL, PREV_FIRE_TIME BIGINT NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT NOT NULL, END_TIME BIGINT NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT NOT NULL, REPEAT_INTERVAL BIGINT NOT NULL, TIMES_TRIGGERED BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(200) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 BIT(1) NULL, BOOL_PROP_2 BIT(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT NOT NULL, SCHED_TIME BIGINT NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT BIT(1) NULL, REQUESTS_RECOVERY BIT(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT NOT NULL, CHECKIN_INTERVAL BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_db2.sql ================================================ # # Thanks to Horia Muntean for submitting this.... # # .. known to work with DB2 7.1 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver" # .. likely to work with others... # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # # If you're using DB2 6.x you'll want to set this property to # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate # create table qrtz_job_details ( sched_name varchar(120) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120) null, job_class_name varchar(128) not null, is_durable varchar(1) not null, is_nonconcurrent varchar(1) not null, is_update_data varchar(1) not null, requests_recovery varchar(1) not null, job_data blob, primary key (sched_name,job_name,job_group) ) create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120) null, next_fire_time bigint, prev_fire_time bigint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time bigint not null, end_time bigint, calendar_name varchar(80), misfire_instr smallint, job_data blob, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ) create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, repeat_count bigint not null, repeat_interval bigint not null, times_triggered bigint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ) create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ) CREATE TABLE qrtz_simprop_triggers ( sched_name varchar(120) not null, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) ) create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, blob_data blob null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ) create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(80) not null, calendar blob not null, primary key (sched_name,calendar_name) ) create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, instance_name varchar(80) not null, fired_time bigint not null, sched_time bigint not null, priority integer not null, state varchar(16) not null, job_name varchar(80) null, job_group varchar(80) null, is_nonconcurrent varchar(1) null, requests_recovery varchar(1) null, primary key (sched_name,entry_id) ); create table qrtz_paused_trigger_grps( sched_name varchar(120) not null, trigger_group varchar(80) not null, primary key (sched_name,trigger_group) ); create table qrtz_scheduler_state ( sched_name varchar(120) not null, instance_name varchar(80) not null, last_checkin_time bigint not null, checkin_interval bigint not null, primary key (sched_name,instance_name) ); create table qrtz_locks ( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_db2_v72.sql ================================================ -- -- Thanks to Horia Muntean for submitting this, Mikkel Heisterberg for updating it -- -- .. known to work with DB2 7.2 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver" -- .. likely to work with others... -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v7Delegate -- -- or -- -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate -- -- If you're using DB2 6.x you'll want to set this property to -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate -- -- Note that the blob column size (e.g. blob(2000)) dictates the amount of data that can be stored in -- that blob - i.e. limits the amount of data you can put into your JobDataMap -- DROP TABLE QRTZ_FIRED_TRIGGERS; DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE QRTZ_SCHEDULER_STATE; DROP TABLE QRTZ_LOCKS; DROP TABLE QRTZ_SIMPLE_TRIGGERS; DROP TABLE QRTZ_SIMPROP_TRIGGERS; DROP TABLE QRTZ_CRON_TRIGGERS; DROP TABLE QRTZ_TRIGGERS; DROP TABLE QRTZ_JOB_DETAILS; DROP TABLE QRTZ_CALENDARS; DROP TABLE QRTZ_BLOB_TRIGGERS; create table qrtz_job_details ( sched_name varchar(120) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120), job_class_name varchar(128) not null, is_durable varchar(1) not null, is_nonconcurrent varchar(1) not null, is_update_data varchar(1) not null, requests_recovery varchar(1) not null, job_data blob(2000), primary key (sched_name,job_name,job_group) ); create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120), next_fire_time bigint, prev_fire_time bigint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time bigint not null, end_time bigint, calendar_name varchar(80), misfire_instr smallint, job_data blob(2000), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, repeat_count bigint not null, repeat_interval bigint not null, times_triggered bigint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); CREATE TABLE qrtz_simprop_triggers ( sched_name varchar(120) not null, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) ); create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, blob_data blob(2000), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(80) not null, calendar blob(2000) not null, primary key (sched_name,calendar_name) ); create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, instance_name varchar(80) not null, fired_time bigint not null, sched_time bigint not null, priority integer not null, state varchar(16) not null, job_name varchar(80), job_group varchar(80), is_nonconcurrent varchar(1), requests_recovery varchar(1), primary key (sched_name,entry_id) ); create table qrtz_paused_trigger_grps( sched_name varchar(120) not null, trigger_group varchar(80) not null, primary key (sched_name,trigger_group) ); create table qrtz_scheduler_state ( sched_name varchar(120) not null, instance_name varchar(80) not null, last_checkin_time bigint not null, checkin_interval bigint not null, primary key (sched_name,instance_name) ); create table qrtz_locks ( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_db2_v8.sql ================================================ # # Updated by Claudiu Crisan (claudiu.crisan@schartner.net) # SQL scripts for DB2 ver 8.1 # # Changes: # - "varchar(1)" replaced with "integer" # - "field_name varchar(xxx) not null" replaced with "field_name varchar(xxx)" # DROP TABLE QRTZ_FIRED_TRIGGERS; DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE QRTZ_SCHEDULER_STATE; DROP TABLE QRTZ_LOCKS; DROP TABLE QRTZ_SIMPLE_TRIGGERS; DROP TABLE QRTZ_SIMPROP_TRIGGERS; DROP TABLE QRTZ_CRON_TRIGGERS; DROP TABLE QRTZ_TRIGGERS; DROP TABLE QRTZ_JOB_DETAILS; DROP TABLE QRTZ_CALENDARS; DROP TABLE QRTZ_BLOB_TRIGGERS; create table qrtz_job_details( sched_name varchar(120) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120), job_class_name varchar(128) not null, is_durable integer not null, is_nonconcurrent integer not null, is_update_data integer not null, requests_recovery integer not null, job_data blob(2000), primary key (sched_name,job_name,job_group) ); create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120), next_fire_time bigint, prev_fire_time bigint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time bigint not null, end_time bigint, calendar_name varchar(80), misfire_instr smallint, job_data blob(2000), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, repeat_count bigint not null, repeat_interval bigint not null, times_triggered bigint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); CREATE TABLE qrtz_simprop_triggers ( sched_name varchar(120) not null, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) ); create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, blob_data blob(2000), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(80) not null, calendar blob(2000) not null, primary key (calendar_name) ); create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, instance_name varchar(80) not null, fired_time bigint not null, sched_time bigint not null, priority integer not null, state varchar(16) not null, job_name varchar(80), job_group varchar(80), is_nonconcurrent integer, requests_recovery integer, primary key (sched_name,entry_id) ); create table qrtz_paused_trigger_grps( sched_name varchar(120) not null, trigger_group varchar(80) not null, primary key (sched_name,trigger_group) ); create table qrtz_scheduler_state( sched_name varchar(120) not null, instance_name varchar(80) not null, last_checkin_time bigint not null, checkin_interval bigint not null, primary key (sched_name,instance_name) ); create table qrtz_locks( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_db2_v95.sql ================================================ DROP TABLE QRTZ_FIRED_TRIGGERS; DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE QRTZ_SCHEDULER_STATE; DROP TABLE QRTZ_LOCKS; DROP TABLE QRTZ_SIMPLE_TRIGGERS; DROP TABLE QRTZ_SIMPROP_TRIGGERS; DROP TABLE QRTZ_CRON_TRIGGERS; DROP TABLE QRTZ_TRIGGERS; DROP TABLE QRTZ_JOB_DETAILS; DROP TABLE QRTZ_CALENDARS; DROP TABLE QRTZ_BLOB_TRIGGERS; create table qrtz_job_details( sched_name varchar(120) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120), job_class_name varchar(128) not null, is_durable integer not null, is_nonconcurrent integer not null, is_update_data integer not null, requests_recovery integer not null, job_data blob(2000), primary key (sched_name,job_name,job_group) ); create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120), next_fire_time bigint, prev_fire_time bigint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time bigint not null, end_time bigint, calendar_name varchar(80), misfire_instr smallint, job_data blob(2000), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, repeat_count bigint not null, repeat_interval bigint not null, times_triggered bigint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); CREATE TABLE qrtz_simprop_triggers ( sched_name varchar(120) not null, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512), STR_PROP_2 VARCHAR(512), STR_PROP_3 VARCHAR(512), INT_PROP_1 INT, INT_PROP_2 INT, LONG_PROP_1 BIGINT, LONG_PROP_2 BIGINT, DEC_PROP_1 NUMERIC(13,4), DEC_PROP_2 NUMERIC(13,4), BOOL_PROP_1 VARCHAR(1), BOOL_PROP_2 VARCHAR(1), PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) ); create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, blob_data blob(2000), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(80) not null, calendar blob(2000) not null, primary key (calendar_name) ); create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, instance_name varchar(80) not null, fired_time bigint not null, sched_time bigint not null, priority integer not null, state varchar(16) not null, job_name varchar(80), job_group varchar(80), is_nonconcurrent integer, requests_recovery integer, primary key (sched_name,entry_id) ); create table qrtz_paused_trigger_grps( sched_name varchar(120) not null, trigger_group varchar(80) not null, primary key (sched_name,trigger_group) ); create table qrtz_scheduler_state( sched_name varchar(120) not null, instance_name varchar(80) not null, last_checkin_time bigint not null, checkin_interval bigint not null, primary key (sched_name,instance_name) ); create table qrtz_locks( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_derby.sql ================================================ -- -- Apache Derby scripts by Steve Stewart, updated by Ronald Pomeroy -- Based on Srinivas Venkatarangaiah's file for Cloudscape -- -- Known to work with Apache Derby 10.0.2.1, or 10.6.2.1 -- -- Updated by Zemian Deng on 08/21/2011 -- * Fixed nullable fields on qrtz_simprop_triggers table. -- * Added Derby QuickStart comments and drop tables statements. -- -- DerbyDB + Quartz Quick Guide: -- * Derby comes with Oracle JDK! For Java6, it default install into C:/Program Files/Sun/JavaDB on Windows. -- 1. Create a derby.properties file under JavaDB directory, and have the following: -- derby.connection.requireAuthentication = true -- derby.authentication.provider = BUILTIN -- derby.user.quartz2=quartz2123 -- 2. Start the DB server by running bin/startNetworkServer script. -- 3. On a new terminal, run bin/ij tool to bring up an SQL prompt, then run: -- connect 'jdbc:derby://localhost:1527/quartz2;user=quartz2;password=quartz2123;create=true'; -- run 'quartz/docs/dbTables/tables_derby.sql'; -- Now in quartz.properties, you may use these properties: -- org.quartz.dataSource.quartzDataSource.driver = org.apache.derby.jdbc.ClientDriver -- org.quartz.dataSource.quartzDataSource.URL = jdbc:derby://localhost:1527/quartz2 -- org.quartz.dataSource.quartzDataSource.user = quartz2 -- org.quartz.dataSource.quartzDataSource.password = quartz2123 -- -- Auto drop and reset tables -- Derby doesn't support if exists condition on table drop, so user must manually do this step if needed to. -- drop table qrtz_fired_triggers; -- drop table qrtz_paused_trigger_grps; -- drop table qrtz_scheduler_state; -- drop table qrtz_locks; -- drop table qrtz_simple_triggers; -- drop table qrtz_simprop_triggers; -- drop table qrtz_cron_triggers; -- drop table qrtz_blob_triggers; -- drop table qrtz_triggers; -- drop table qrtz_job_details; -- drop table qrtz_calendars; create table qrtz_job_details ( sched_name varchar(120) not null, job_name varchar(200) not null, job_group varchar(200) not null, description varchar(250) , job_class_name varchar(250) not null, is_durable varchar(5) not null, is_nonconcurrent varchar(5) not null, is_update_data varchar(5) not null, requests_recovery varchar(5) not null, job_data blob, primary key (sched_name,job_name,job_group) ); create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, job_name varchar(200) not null, job_group varchar(200) not null, description varchar(250), next_fire_time bigint, prev_fire_time bigint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time bigint not null, end_time bigint, calendar_name varchar(200), misfire_instr smallint, job_data blob, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, repeat_count bigint not null, repeat_interval bigint not null, times_triggered bigint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_simprop_triggers ( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, str_prop_1 varchar(512), str_prop_2 varchar(512), str_prop_3 varchar(512), int_prop_1 int, int_prop_2 int, long_prop_1 bigint, long_prop_2 bigint, dec_prop_1 numeric(13,4), dec_prop_2 numeric(13,4), bool_prop_1 varchar(5), bool_prop_2 varchar(5), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, blob_data blob, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(200) not null, calendar blob not null, primary key (sched_name,calendar_name) ); create table qrtz_paused_trigger_grps ( sched_name varchar(120) not null, trigger_group varchar(200) not null, primary key (sched_name,trigger_group) ); create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, instance_name varchar(200) not null, fired_time bigint not null, sched_time bigint not null, priority integer not null, state varchar(16) not null, job_name varchar(200), job_group varchar(200), is_nonconcurrent varchar(5), requests_recovery varchar(5), primary key (sched_name,entry_id) ); create table qrtz_scheduler_state ( sched_name varchar(120) not null, instance_name varchar(200) not null, last_checkin_time bigint not null, checkin_interval bigint not null, primary key (sched_name,instance_name) ); create table qrtz_locks ( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_derby_previous.sql ================================================ -- -- Apache Derby scripts by Steve Stewart. -- Based on Srinivas Venkatarangaiah's file for Cloudscape -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.CloudscapeDelegate -- -- Known to work with Apache Derby 10.0.2.1 -- create table qrtz_job_details ( sched_name varchar(120) not null, job_name varchar(200) not null, job_group varchar(200) not null, description varchar(250) , job_class_name varchar(250) not null, is_durable varchar(5) not null, is_nonconcurrent varchar(5) not null, is_update_data varchar(5) not null, requests_recovery varchar(5) not null, job_data blob, primary key (sched_name,job_name,job_group) ); create table qrtz_triggers ( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, job_name varchar(200) not null, job_group varchar(200) not null, description varchar(250) , next_fire_time bigint, prev_fire_time bigint, priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time bigint not null, end_time bigint, calendar_name varchar(200), misfire_instr smallint, job_data blob, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers ( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, repeat_count bigint not null, repeat_interval bigint not null, times_triggered bigint not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers ( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_simprop_triggers ( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, str_prop_1 varchar(512), str_prop_2 varchar(512), str_prop_3 varchar(512), int_prop_1 int, int_prop_2 int, long_prop_1 bigint, long_prop_2 bigint, dec_prop_1 numeric(13,4), dec_prop_2 numeric(13,4), bool_prop_1 varchar(5), bool_prop_2 varchar(5), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_blob_triggers ( sched_name varchar(120) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, blob_data blob , primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars ( sched_name varchar(120) not null, calendar_name varchar(200) not null, calendar blob not null, primary key (sched_name,calendar_name) ); create table qrtz_paused_trigger_grps ( sched_name varchar(120) not null, trigger_group varchar(200) not null, primary key (sched_name,trigger_group) ); create table qrtz_fired_triggers ( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(200) not null, trigger_group varchar(200) not null, instance_name varchar(200) not null, fired_time bigint not null, sched_time bigint not null, priority integer not null, state varchar(16) not null, job_name varchar(200), job_group varchar(200), is_nonconcurrent varchar(5), requests_recovery varchar(5), primary key (sched_name,entry_id) ); create table qrtz_scheduler_state ( sched_name varchar(120) not null, instance_name varchar(200) not null, last_checkin_time bigint not null, checkin_interval bigint not null, primary key (sched_name,instance_name) ); create table qrtz_locks ( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); commit; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_firebird.sql ================================================ -- -- Thanks to Leonardo Alves -- DROP TABLE QRTZ_FIRED_TRIGGERS; DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE QRTZ_SCHEDULER_STATE; DROP TABLE QRTZ_LOCKS; DROP TABLE QRTZ_SIMPLE_TRIGGERS; DROP TABLE QRTZ_SIMPROP_TRIGGERS; DROP TABLE QRTZ_CRON_TRIGGERS; DROP TABLE QRTZ_BLOB_TRIGGERS; DROP TABLE QRTZ_TRIGGERS; DROP TABLE QRTZ_JOB_DETAILS; DROP TABLE QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(60) NOT NULL, JOB_GROUP VARCHAR(60) NOT NULL, DESCRIPTION VARCHAR(120), JOB_CLASS_NAME VARCHAR(128) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB, CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(60) NOT NULL, TRIGGER_GROUP VARCHAR(60) NOT NULL, JOB_NAME VARCHAR(60) NOT NULL, JOB_GROUP VARCHAR(60) NOT NULL, DESCRIPTION VARCHAR(120), NEXT_FIRE_TIME BIGINT, PREV_FIRE_TIME BIGINT, PRIORITY INTEGER, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT NOT NULL, END_TIME BIGINT, CALENDAR_NAME VARCHAR(60), MISFIRE_INSTR SMALLINT, JOB_DATA BLOB, CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT FK_QRTZ_TRIGGERS_1 FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(60) NOT NULL, TRIGGER_GROUP VARCHAR(60) NOT NULL, REPEAT_COUNT BIGINT NOT NULL, REPEAT_INTERVAL BIGINT NOT NULL, TIMES_TRIGGERED BIGINT NOT NULL, CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_1 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_1 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(60) NOT NULL, TRIGGER_GROUP VARCHAR(60) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(60), CONSTRAINT PK_QRTZ_SIMPLE_TRG PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT FK_QRTZ_SIMPLE_TRG_1 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(60) NOT NULL, TRIGGER_GROUP VARCHAR(60) NOT NULL, BLOB_DATA BLOB, CONSTRAINT PK_QRTZ_BLOB_TRIGGERS PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT FK_QRTZ_BLOB_TRIGGERS_1 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(60) NOT NULL, CALENDAR BLOB NOT NULL, CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(60) NOT NULL, CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(60) NOT NULL, TRIGGER_GROUP VARCHAR(60) NOT NULL, INSTANCE_NAME VARCHAR(80) NOT NULL, FIRED_TIME BIGINT NOT NULL, SCHED_TIME BIGINT NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(60), JOB_GROUP VARCHAR(60), IS_NONCONCURRENT VARCHAR(1), REQUESTS_RECOVERY VARCHAR(1), CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(80) NOT NULL, LAST_CHECKIN_TIME BIGINT NOT NULL, CHECKIN_INTERVAL BIGINT NOT NULL, CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); COMMIT; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_gauss_default_compatibility.sql ================================================ -- Notes: Original sql of this file is based on tables_postgres.sql. -- Thanks to Patrick Lightbody for submitting this... -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.GaussDBDelegate DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE BOOL NOT NULL, IS_NONCONCURRENT BOOL NOT NULL, IS_UPDATE_DATA BOOL NOT NULL, REQUESTS_RECOVERY BOOL NOT NULL, JOB_DATA BYTEA NULL, PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT NULL, PREV_FIRE_TIME BIGINT NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT NOT NULL, END_TIME BIGINT NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT NULL, JOB_DATA BYTEA NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT NOT NULL, REPEAT_INTERVAL BIGINT NOT NULL, TIMES_TRIGGERED BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13, 4) NULL, DEC_PROP_2 NUMERIC(13, 4) NULL, BOOL_PROP_1 BOOL NULL, BOOL_PROP_2 BOOL NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BYTEA NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BYTEA NOT NULL, PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT NOT NULL, SCHED_TIME BIGINT NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT BOOL NULL, REQUESTS_RECOVERY BOOL NULL, PRIMARY KEY (SCHED_NAME, ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT NOT NULL, CHECKIN_INTERVAL BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME, LOCK_NAME) ); CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); COMMIT; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_gauss_m_compatibility.sql ================================================ -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE BOOL NOT NULL, IS_NONCONCURRENT BOOL NOT NULL, IS_UPDATE_DATA BOOL NOT NULL, REQUESTS_RECOVERY BOOL NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT NULL, PREV_FIRE_TIME BIGINT NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT NOT NULL, END_TIME BIGINT NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT NUMERIC(7) NOT NULL, REPEAT_INTERVAL NUMERIC(12) NOT NULL, TIMES_TRIGGERED NUMERIC(10) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INTEGER NULL, INT_PROP_2 INTEGER NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13, 4) NULL, DEC_PROP_2 NUMERIC(13, 4) NULL, BOOL_PROP_1 BOOL NULL, BOOL_PROP_2 BOOL NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT NOT NULL, SCHED_TIME BIGINT NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT BOOL NULL, REQUESTS_RECOVERY BOOL NULL, PRIMARY KEY (SCHED_NAME, ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT NOT NULL, CHECKIN_INTERVAL BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME, LOCK_NAME) ); CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME, CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME, INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME, TRIGGER_GROUP); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_h2.sql ================================================ -- Thanks to Amir Kibbar and Peter Rietzler for contributing the schema for H2 database, -- and verifying that it works with Quartz's StdJDBCDelegate -- -- Note, Quartz depends on row-level locking which means you must use the MVCC=TRUE -- setting on your H2 database, or you will experience dead-locks -- -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR (200) NOT NULL , CALENDAR IMAGE NOT NULL ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , CRON_EXPRESSION VARCHAR (120) NOT NULL , TIME_ZONE_ID VARCHAR (80) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR (95) NOT NULL , TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , INSTANCE_NAME VARCHAR (200) NOT NULL , FIRED_TIME BIGINT NOT NULL , SCHED_TIME BIGINT NOT NULL , PRIORITY INTEGER NOT NULL , STATE VARCHAR (16) NOT NULL, JOB_NAME VARCHAR (200) NULL , JOB_GROUP VARCHAR (200) NULL , IS_NONCONCURRENT BOOLEAN NULL , REQUESTS_RECOVERY BOOLEAN NULL ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR (200) NOT NULL ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR (200) NOT NULL , LAST_CHECKIN_TIME BIGINT NOT NULL , CHECKIN_INTERVAL BIGINT NOT NULL ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR (40) NOT NULL ); CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR (200) NOT NULL , JOB_GROUP VARCHAR (200) NOT NULL , DESCRIPTION VARCHAR (250) NULL , JOB_CLASS_NAME VARCHAR (250) NOT NULL , IS_DURABLE BOOLEAN NOT NULL , IS_NONCONCURRENT BOOLEAN NOT NULL , IS_UPDATE_DATA BOOLEAN NOT NULL , REQUESTS_RECOVERY BOOLEAN NOT NULL , JOB_DATA IMAGE NULL ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , REPEAT_COUNT BIGINT NOT NULL , REPEAT_INTERVAL BIGINT NOT NULL , TIMES_TRIGGERED BIGINT NOT NULL ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INTEGER NULL, INT_PROP_2 INTEGER NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 BOOLEAN NULL, BOOL_PROP_2 BOOLEAN NULL ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , BLOB_DATA IMAGE NULL ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , JOB_NAME VARCHAR (200) NOT NULL , JOB_GROUP VARCHAR (200) NOT NULL , DESCRIPTION VARCHAR (250) NULL , NEXT_FIRE_TIME BIGINT NULL , PREV_FIRE_TIME BIGINT NULL , PRIORITY INTEGER NULL , TRIGGER_STATE VARCHAR (16) NOT NULL , TRIGGER_TYPE VARCHAR (8) NOT NULL , START_TIME BIGINT NOT NULL , END_TIME BIGINT NULL , CALENDAR_NAME VARCHAR (200) NULL , MISFIRE_INSTR SMALLINT NULL , JOB_DATA IMAGE NULL ); ALTER TABLE QRTZ_CALENDARS ADD CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY ( SCHED_NAME, CALENDAR_NAME ); ALTER TABLE QRTZ_CRON_TRIGGERS ADD CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_FIRED_TRIGGERS ADD CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY ( SCHED_NAME, ENTRY_ID ); ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY ( SCHED_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_SCHEDULER_STATE ADD CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY ( SCHED_NAME, INSTANCE_NAME ); ALTER TABLE QRTZ_LOCKS ADD CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY ( SCHED_NAME, LOCK_NAME ); ALTER TABLE QRTZ_JOB_DETAILS ADD CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY ( SCHED_NAME, JOB_NAME, JOB_GROUP ); ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_TRIGGERS ADD CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_CRON_TRIGGERS ADD CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) ON DELETE CASCADE; ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) ON DELETE CASCADE; ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) ON DELETE CASCADE; ALTER TABLE QRTZ_TRIGGERS ADD CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY ( SCHED_NAME, JOB_NAME, JOB_GROUP ) REFERENCES QRTZ_JOB_DETAILS ( SCHED_NAME, JOB_NAME, JOB_GROUP ); COMMIT; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_hsqldb.sql ================================================ -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.HSQLDBDelegate -- DROP TABLE qrtz_locks IF EXISTS; DROP TABLE qrtz_scheduler_state IF EXISTS; DROP TABLE qrtz_fired_triggers IF EXISTS; DROP TABLE qrtz_paused_trigger_grps IF EXISTS; DROP TABLE qrtz_calendars IF EXISTS; DROP TABLE qrtz_blob_triggers IF EXISTS; DROP TABLE qrtz_cron_triggers IF EXISTS; DROP TABLE qrtz_simple_triggers IF EXISTS; DROP TABLE qrtz_simprop_triggers IF EXISTS; DROP TABLE qrtz_triggers IF EXISTS; DROP TABLE qrtz_job_details IF EXISTS; CREATE TABLE qrtz_job_details ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE BOOLEAN NOT NULL, IS_NONCONCURRENT BOOLEAN NOT NULL, IS_UPDATE_DATA BOOLEAN NOT NULL, REQUESTS_RECOVERY BOOLEAN NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME NUMERIC(13) NULL, PREV_FIRE_TIME NUMERIC(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME NUMERIC(13) NOT NULL, END_TIME NUMERIC(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR NUMERIC(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_simple_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT NUMERIC(7) NOT NULL, REPEAT_INTERVAL NUMERIC(12) NOT NULL, TIMES_TRIGGERED NUMERIC(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_cron_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 NUMERIC(9) NULL, INT_PROP_2 NUMERIC(9) NULL, LONG_PROP_1 NUMERIC(13) NULL, LONG_PROP_2 NUMERIC(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 BOOLEAN NULL, BOOL_PROP_2 BOOLEAN NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_blob_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_calendars ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE qrtz_paused_trigger_grps ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_fired_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME NUMERIC(13) NOT NULL, SCHED_TIME NUMERIC(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT BOOLEAN NULL, REQUESTS_RECOVERY BOOLEAN NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE qrtz_scheduler_state ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME NUMERIC(13) NOT NULL, CHECKIN_INTERVAL NUMERIC(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE qrtz_locks ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_hsqldb_old.sql ================================================ # # Thanks to Joseph Wilkicki for submitting this file's contents # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.HSQLDBDelegate # # Some users report the need to change the fields # with datatype "OTHER" to datatype "BINARY" with # particular versions (e.g. 1.7.1) of HSQLDB # CREATE TABLE qrtz_job_details ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME LONGVARCHAR(80) NOT NULL, JOB_GROUP LONGVARCHAR(80) NOT NULL, DESCRIPTION LONGVARCHAR(120) NULL, JOB_CLASS_NAME LONGVARCHAR(128) NOT NULL, IS_DURABLE LONGVARCHAR(1) NOT NULL, IS_NONCONCURRENT LONGVARCHAR(1) NOT NULL, IS_UPDATE_DATA LONGVARCHAR(1) NOT NULL, REQUESTS_RECOVERY LONGVARCHAR(1) NOT NULL, JOB_DATA OTHER NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME LONGVARCHAR(80) NOT NULL, TRIGGER_GROUP LONGVARCHAR(80) NOT NULL, JOB_NAME LONGVARCHAR(80) NOT NULL, JOB_GROUP LONGVARCHAR(80) NOT NULL, DESCRIPTION LONGVARCHAR(120) NULL, NEXT_FIRE_TIME NUMERIC(13) NULL, PREV_FIRE_TIME NUMERIC(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE LONGVARCHAR(16) NOT NULL, TRIGGER_TYPE LONGVARCHAR(8) NOT NULL, START_TIME NUMERIC(13) NOT NULL, END_TIME NUMERIC(13) NULL, CALENDAR_NAME LONGVARCHAR(80) NULL, MISFIRE_INSTR NUMERIC(2) NULL, JOB_DATA OTHER NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_simple_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME LONGVARCHAR(80) NOT NULL, TRIGGER_GROUP LONGVARCHAR(80) NOT NULL, REPEAT_COUNT NUMERIC(7) NOT NULL, REPEAT_INTERVAL NUMERIC(12) NOT NULL, TIMES_TRIGGERED NUMERIC(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_cron_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME LONGVARCHAR(80) NOT NULL, TRIGGER_GROUP LONGVARCHAR(80) NOT NULL, CRON_EXPRESSION LONGVARCHAR(120) NOT NULL, TIME_ZONE_ID LONGVARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME LONGVARCHAR(200) NOT NULL, TRIGGER_GROUP LONGVARCHAR(200) NOT NULL, STR_PROP_1 LONGVARCHAR(512) NULL, STR_PROP_2 LONGVARCHAR(512) NULL, STR_PROP_3 LONGVARCHAR(512) NULL, INT_PROP_1 NUMERIC(9) NULL, INT_PROP_2 NUMERIC(9) NULL, LONG_PROP_1 NUMERIC(13) NULL, LONG_PROP_2 NUMERIC(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 LONGVARCHAR(1) NULL, BOOL_PROP_2 LONGVARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_blob_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME LONGVARCHAR(80) NOT NULL, TRIGGER_GROUP LONGVARCHAR(80) NOT NULL, BLOB_DATA OTHER NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_calendars ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME LONGVARCHAR(80) NOT NULL, CALENDAR OTHER NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE qrtz_paused_trigger_grps ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP LONGVARCHAR(80) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_fired_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID LONGVARCHAR(95) NOT NULL, TRIGGER_NAME LONGVARCHAR(80) NOT NULL, TRIGGER_GROUP LONGVARCHAR(80) NOT NULL, INSTANCE_NAME LONGVARCHAR(80) NOT NULL, FIRED_TIME NUMERIC(13) NOT NULL, SCHED_TIME NUMERIC(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE LONGVARCHAR(16) NOT NULL, JOB_NAME LONGVARCHAR(80) NULL, JOB_GROUP LONGVARCHAR(80) NULL, IS_NONCONCURRENT LONGVARCHAR(1) NULL, REQUESTS_RECOVERY LONGVARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE qrtz_scheduler_state ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME LONGVARCHAR(80) NOT NULL, LAST_CHECKIN_TIME NUMERIC(13) NOT NULL, CHECKIN_INTERVAL NUMERIC(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE qrtz_locks ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME LONGVARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); commit; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_informix.sql ================================================ { } { Thanks to Keith Chew for submitting this. } { } { use the StdJDBCDelegate with Informix. } { } { note that Informix has a 18 character limit on the table name, so the prefix had to be shortened to "q" instead of "qrtz_" } CREATE TABLE qblob_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME varchar(80) NOT NULL, TRIGGER_GROUP varchar(80) NOT NULL, BLOB_DATA byte in table ); ALTER TABLE qblob_triggers ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP); CREATE TABLE qcalendars ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME varchar(80) NOT NULL, CALENDAR byte in table NOT NULL ); ALTER TABLE qcalendars ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,CALENDAR_NAME); CREATE TABLE qcron_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME varchar(80) NOT NULL, TRIGGER_GROUP varchar(80) NOT NULL, CRON_EXPRESSION varchar(120) NOT NULL, TIME_ZONE_ID varchar(80) ); ALTER TABLE qcron_triggers ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP); CREATE TABLE qfired_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID varchar(95) NOT NULL, TRIGGER_NAME varchar(80) NOT NULL, TRIGGER_GROUP varchar(80) NOT NULL, INSTANCE_NAME varchar(80) NOT NULL, FIRED_TIME numeric(13) NOT NULL, SCHED_TIME numeric(13) NOT NULL, PRIORITY integer NOT NULL, STATE varchar(16) NOT NULL, JOB_NAME varchar(80), JOB_GROUP varchar(80), IS_NONCONCURRENT varchar(1), REQUESTS_RECOVERY varchar(1) ); ALTER TABLE qfired_triggers ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,ENTRY_ID); CREATE TABLE qpaused_trigger_grps ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP varchar(80) NOT NULL ); ALTER TABLE qpaused_trigger_grps ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP); CREATE TABLE qscheduler_state ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME varchar(80) NOT NULL, LAST_CHECKIN_TIME numeric(13) NOT NULL, CHECKIN_INTERVAL numeric(13) NOT NULL ); ALTER TABLE qscheduler_state ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,INSTANCE_NAME); CREATE TABLE qlocks ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME varchar(40) NOT NULL ); ALTER TABLE qlocks ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,LOCK_NAME); CREATE TABLE qjob_details ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME varchar(80) NOT NULL, JOB_GROUP varchar(80) NOT NULL, DESCRIPTION varchar(120), JOB_CLASS_NAME varchar(128) NOT NULL, IS_DURABLE varchar(1) NOT NULL, IS_NONCONCURRENT varchar(1) NOT NULL, IS_UPDATE_DATA varchar(1) NOT NULL, REQUESTS_RECOVERY varchar(1) NOT NULL, JOB_DATA byte in table ); ALTER TABLE qjob_details ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,JOB_NAME, JOB_GROUP); CREATE TABLE qsimple_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME varchar(80) NOT NULL, TRIGGER_GROUP varchar(80) NOT NULL, REPEAT_COUNT numeric(7) NOT NULL, REPEAT_INTERVAL numeric(12) NOT NULL, TIMES_TRIGGERED numeric(10) NOT NULL ); ALTER TABLE qsimple_triggers ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP); CREATE TABLE qsimprop_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 NUMERIC(9) NULL, INT_PROP_2 NUMERIC(9) NULL, LONG_PROP_1 NUMERIC(13) NULL, LONG_PROP_2 NUMERIC(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, ); ALTER TABLE qsimprop_triggers ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP); CREATE TABLE qtriggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME varchar(80) NOT NULL, TRIGGER_GROUP varchar(80) NOT NULL, JOB_NAME varchar(80) NOT NULL, JOB_GROUP varchar(80) NOT NULL, DESCRIPTION varchar(120), NEXT_FIRE_TIME numeric(13), PREV_FIRE_TIME numeric(13), PRIORITY integer, TRIGGER_STATE varchar(16) NOT NULL, TRIGGER_TYPE varchar(8) NOT NULL, START_TIME numeric(13) NOT NULL, END_TIME numeric(13), CALENDAR_NAME varchar(80), MISFIRE_INSTR numeric(2), JOB_DATA byte in table ); ALTER TABLE qtriggers ADD CONSTRAINT PRIMARY KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP); ALTER TABLE qblob_triggers ADD CONSTRAINT FOREIGN KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) REFERENCES qtriggers; ALTER TABLE qcron_triggers ADD CONSTRAINT FOREIGN KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) REFERENCES qtriggers; ALTER TABLE qsimple_triggers ADD CONSTRAINT FOREIGN KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) REFERENCES qtriggers; ALTER TABLE qsimprop_triggers ADD CONSTRAINT FOREIGN KEY (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) REFERENCES qtriggers; ALTER TABLE qtriggers ADD CONSTRAINT FOREIGN KEY (SCHED_NAME,JOB_NAME, JOB_GROUP) REFERENCES qjob_details; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql.sql ================================================ # # Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar # # PLEASE consider using mysql with innodb tables to avoid locking issues # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(200) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); commit; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql ================================================ # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # # # By: Ron Cordell - roncordell # I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM. DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(190) NOT NULL, JOB_GROUP VARCHAR(190) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, JOB_NAME VARCHAR(190) NOT NULL, JOB_GROUP VARCHAR(190) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(190) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(190) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) ENGINE=InnoDB; CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, INSTANCE_NAME VARCHAR(190) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(190) NULL, JOB_GROUP VARCHAR(190) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID)) ENGINE=InnoDB; CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(190) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) ENGINE=InnoDB; CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME)) ENGINE=InnoDB; CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); commit; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_oracle.sql ================================================ -- -- A hint submitted by a user: Oracle DB MUST be created as "shared" and the -- job_queue_processes parameter must be greater than 2 -- However, these settings are pretty much standard after any -- Oracle install, so most users need not worry about this. -- -- Many other users (including the primary author of Quartz) have had success -- running in dedicated mode, so only consider the above as a hint ;-) -- delete from qrtz_fired_triggers; delete from qrtz_simple_triggers; delete from qrtz_simprop_triggers; delete from qrtz_cron_triggers; delete from qrtz_blob_triggers; delete from qrtz_triggers; delete from qrtz_job_details; delete from qrtz_calendars; delete from qrtz_paused_trigger_grps; delete from qrtz_locks; delete from qrtz_scheduler_state; drop table qrtz_calendars; drop table qrtz_fired_triggers; drop table qrtz_blob_triggers; drop table qrtz_cron_triggers; drop table qrtz_simple_triggers; drop table qrtz_simprop_triggers; drop table qrtz_triggers; drop table qrtz_job_details; drop table qrtz_paused_trigger_grps; drop table qrtz_locks; drop table qrtz_scheduler_state; CREATE TABLE qrtz_job_details ( SCHED_NAME VARCHAR2(120) NOT NULL, JOB_NAME VARCHAR2(200) NOT NULL, JOB_GROUP VARCHAR2(200) NOT NULL, DESCRIPTION VARCHAR2(250) NULL, JOB_CLASS_NAME VARCHAR2(250) NOT NULL, IS_DURABLE VARCHAR2(1) NOT NULL, IS_NONCONCURRENT VARCHAR2(1) NOT NULL, IS_UPDATE_DATA VARCHAR2(1) NOT NULL, REQUESTS_RECOVERY VARCHAR2(1) NOT NULL, JOB_DATA BLOB NULL, CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, JOB_NAME VARCHAR2(200) NOT NULL, JOB_GROUP VARCHAR2(200) NOT NULL, DESCRIPTION VARCHAR2(250) NULL, NEXT_FIRE_TIME NUMBER(13) NULL, PREV_FIRE_TIME NUMBER(13) NULL, PRIORITY NUMBER(13) NULL, TRIGGER_STATE VARCHAR2(16) NOT NULL, TRIGGER_TYPE VARCHAR2(8) NOT NULL, START_TIME NUMBER(13) NOT NULL, END_TIME NUMBER(13) NULL, CALENDAR_NAME VARCHAR2(200) NULL, MISFIRE_INSTR NUMBER(2) NULL, JOB_DATA BLOB NULL, CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_simple_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, REPEAT_COUNT NUMBER(7) NOT NULL, REPEAT_INTERVAL NUMBER(12) NOT NULL, TIMES_TRIGGERED NUMBER(10) NOT NULL, CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_cron_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, CRON_EXPRESSION VARCHAR2(120) NOT NULL, TIME_ZONE_ID VARCHAR2(80), CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, STR_PROP_1 VARCHAR2(512) NULL, STR_PROP_2 VARCHAR2(512) NULL, STR_PROP_3 VARCHAR2(512) NULL, INT_PROP_1 NUMBER(10) NULL, INT_PROP_2 NUMBER(10) NULL, LONG_PROP_1 NUMBER(13) NULL, LONG_PROP_2 NUMBER(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR2(1) NULL, BOOL_PROP_2 VARCHAR2(1) NULL, CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_blob_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, BLOB_DATA BLOB NULL, CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_calendars ( SCHED_NAME VARCHAR2(120) NOT NULL, CALENDAR_NAME VARCHAR2(200) NOT NULL, CALENDAR BLOB NOT NULL, CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE qrtz_paused_trigger_grps ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_fired_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, ENTRY_ID VARCHAR2(95) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, INSTANCE_NAME VARCHAR2(200) NOT NULL, FIRED_TIME NUMBER(13) NOT NULL, SCHED_TIME NUMBER(13) NOT NULL, PRIORITY NUMBER(13) NOT NULL, STATE VARCHAR2(16) NOT NULL, JOB_NAME VARCHAR2(200) NULL, JOB_GROUP VARCHAR2(200) NULL, IS_NONCONCURRENT VARCHAR2(1) NULL, REQUESTS_RECOVERY VARCHAR2(1) NULL, CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE qrtz_scheduler_state ( SCHED_NAME VARCHAR2(120) NOT NULL, INSTANCE_NAME VARCHAR2(200) NOT NULL, LAST_CHECKIN_TIME NUMBER(13) NOT NULL, CHECKIN_INTERVAL NUMBER(13) NOT NULL, CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE qrtz_locks ( SCHED_NAME VARCHAR2(120) NOT NULL, LOCK_NAME VARCHAR2(40) NOT NULL, CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY); create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP); create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP); create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP); create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME); create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP); create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE); create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME); create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP); create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP); create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_oracle23.sql ================================================ -- -- A hint submitted by a user: Oracle DB MUST be created as "shared" and the -- job_queue_processes parameter must be greater than 2 -- However, these settings are pretty much standard after any -- Oracle install, so most users need not worry about this. -- -- Many other users (including the primary author of Quartz) have had success -- running in dedicated mode, so only consider the above as a hint ;-) -- delete from qrtz_fired_triggers; delete from qrtz_simple_triggers; delete from qrtz_simprop_triggers; delete from qrtz_cron_triggers; delete from qrtz_blob_triggers; delete from qrtz_triggers; delete from qrtz_job_details; delete from qrtz_calendars; delete from qrtz_paused_trigger_grps; delete from qrtz_locks; delete from qrtz_scheduler_state; drop table qrtz_calendars; drop table qrtz_fired_triggers; drop table qrtz_blob_triggers; drop table qrtz_cron_triggers; drop table qrtz_simple_triggers; drop table qrtz_simprop_triggers; drop table qrtz_triggers; drop table qrtz_job_details; drop table qrtz_paused_trigger_grps; drop table qrtz_locks; drop table qrtz_scheduler_state; CREATE TABLE qrtz_job_details ( SCHED_NAME VARCHAR2(120) NOT NULL, JOB_NAME VARCHAR2(200) NOT NULL, JOB_GROUP VARCHAR2(200) NOT NULL, DESCRIPTION VARCHAR2(250) NULL, JOB_CLASS_NAME VARCHAR2(250) NOT NULL, IS_DURABLE BOOLEAN NOT NULL, IS_NONCONCURRENT BOOLEAN NOT NULL, IS_UPDATE_DATA BOOLEAN NOT NULL, REQUESTS_RECOVERY BOOLEAN NOT NULL, JOB_DATA BLOB NULL, CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, JOB_NAME VARCHAR2(200) NOT NULL, JOB_GROUP VARCHAR2(200) NOT NULL, DESCRIPTION VARCHAR2(250) NULL, NEXT_FIRE_TIME NUMBER(13) NULL, PREV_FIRE_TIME NUMBER(13) NULL, PRIORITY NUMBER(13) NULL, TRIGGER_STATE VARCHAR2(16) NOT NULL, TRIGGER_TYPE VARCHAR2(8) NOT NULL, START_TIME NUMBER(13) NOT NULL, END_TIME NUMBER(13) NULL, CALENDAR_NAME VARCHAR2(200) NULL, MISFIRE_INSTR NUMBER(2) NULL, JOB_DATA BLOB NULL, CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_simple_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, REPEAT_COUNT NUMBER(7) NOT NULL, REPEAT_INTERVAL NUMBER(12) NOT NULL, TIMES_TRIGGERED NUMBER(10) NOT NULL, CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_cron_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, CRON_EXPRESSION VARCHAR2(120) NOT NULL, TIME_ZONE_ID VARCHAR2(80), CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, STR_PROP_1 VARCHAR2(512) NULL, STR_PROP_2 VARCHAR2(512) NULL, STR_PROP_3 VARCHAR2(512) NULL, INT_PROP_1 NUMBER(10) NULL, INT_PROP_2 NUMBER(10) NULL, LONG_PROP_1 NUMBER(13) NULL, LONG_PROP_2 NUMBER(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 BOOLEAN NULL, BOOL_PROP_2 BOOLEAN NULL, CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_blob_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, BLOB_DATA BLOB NULL, CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_calendars ( SCHED_NAME VARCHAR2(120) NOT NULL, CALENDAR_NAME VARCHAR2(200) NOT NULL, CALENDAR BLOB NOT NULL, CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE qrtz_paused_trigger_grps ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_fired_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, ENTRY_ID VARCHAR2(95) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, INSTANCE_NAME VARCHAR2(200) NOT NULL, FIRED_TIME NUMBER(13) NOT NULL, SCHED_TIME NUMBER(13) NOT NULL, PRIORITY NUMBER(13) NOT NULL, STATE VARCHAR2(16) NOT NULL, JOB_NAME VARCHAR2(200) NULL, JOB_GROUP VARCHAR2(200) NULL, IS_NONCONCURRENT BOOLEAN NULL, REQUESTS_RECOVERY BOOLEAN NULL, CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE qrtz_scheduler_state ( SCHED_NAME VARCHAR2(120) NOT NULL, INSTANCE_NAME VARCHAR2(200) NOT NULL, LAST_CHECKIN_TIME NUMBER(13) NOT NULL, CHECKIN_INTERVAL NUMBER(13) NOT NULL, CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE qrtz_locks ( SCHED_NAME VARCHAR2(120) NOT NULL, LOCK_NAME VARCHAR2(40) NOT NULL, CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY); create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP); create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP); create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP); create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME); create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP); create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE); create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME); create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP); create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP); create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP); ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_pointbase.sql ================================================ # # Thanks to Gregg Freeman # # # ...you may want to change defined the size of the "blob" columns before # creating the tables (particularly for the qrtz_job_details.job_data column), # if you will be storing large amounts of data in them # # delete from qrtz_fired_triggers; delete from qrtz_simple_triggers; delete from qrtz_simprop_triggers; delete from qrtz_cron_triggers; delete from qrtz_blob_triggers; delete from qrtz_triggers; delete from qrtz_job_details; delete from qrtz_calendars; delete from qrtz_paused_trigger_grps; delete from qrtz_locks; delete from qrtz_scheduler_state; drop table qrtz_calendars; drop table qrtz_fired_triggers; drop table qrtz_blob_triggers; drop table qrtz_cron_triggers; drop table qrtz_simple_triggers; drop table qrtz_simprop_triggers; drop table qrtz_triggers; drop table qrtz_job_details; drop table qrtz_paused_trigger_grps; drop table qrtz_locks; drop table qrtz_scheduler_state; CREATE TABLE qrtz_job_details ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR2(80) NOT NULL, JOB_GROUP VARCHAR2(80) NOT NULL, DESCRIPTION VARCHAR2(120) NULL, JOB_CLASS_NAME VARCHAR2(128) NOT NULL, IS_DURABLE BOOLEAN NOT NULL, IS_NONCONCURRENT BOOLEAN NOT NULL, IS_UPDATE_DATA BOOLEAN NOT NULL, REQUESTS_RECOVERY BOOLEAN NOT NULL, JOB_DATA BLOB(4K) NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR2(80) NOT NULL, TRIGGER_GROUP VARCHAR2(80) NOT NULL, JOB_NAME VARCHAR2(80) NOT NULL, JOB_GROUP VARCHAR2(80) NOT NULL, DESCRIPTION VARCHAR2(120) NULL, NEXT_FIRE_TIME NUMBER(13) NULL, PREV_FIRE_TIME NUMBER(13) NULL, PRIORITY NUMBER(13) NULL, TRIGGER_STATE VARCHAR2(16) NOT NULL, TRIGGER_TYPE VARCHAR2(8) NOT NULL, START_TIME NUMBER(13) NOT NULL, END_TIME NUMBER(13) NULL, CALENDAR_NAME VARCHAR2(80) NULL, MISFIRE_INSTR NUMBER(2) NULL, JOB_DATA BLOB(4K) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_simple_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR2(80) NOT NULL, TRIGGER_GROUP VARCHAR2(80) NOT NULL, REPEAT_COUNT NUMBER(7) NOT NULL, REPEAT_INTERVAL NUMBER(12) NOT NULL, TIMES_TRIGGERED NUMBER(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 NUMBER(10) NULL, INT_PROP_2 NUMBER(10) NULL, LONG_PROP_1 NUMBER(13) NULL, LONG_PROP_2 NUMBER(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 BOOLEAN NULL, BOOL_PROP_2 BOOLEAN NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_cron_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR2(80) NOT NULL, TRIGGER_GROUP VARCHAR2(80) NOT NULL, CRON_EXPRESSION VARCHAR2(120) NOT NULL, TIME_ZONE_ID VARCHAR2(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_blob_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR2(80) NOT NULL, TRIGGER_GROUP VARCHAR2(80) NOT NULL, BLOB_DATA BLOB(4K) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_calendars ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR2(80) NOT NULL, CALENDAR BLOB(4K) NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE qrtz_paused_trigger_grps ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR2(80) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_fired_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR2(95) NOT NULL, TRIGGER_NAME VARCHAR2(80) NOT NULL, TRIGGER_GROUP VARCHAR2(80) NOT NULL, INSTANCE_NAME VARCHAR2(80) NOT NULL, FIRED_TIME NUMBER(13) NOT NULL, SCHED_TIME NUMBER(13) NOT NULL, PRIORITY NUMBER(13) NOT NULL, STATE VARCHAR2(16) NOT NULL, JOB_NAME VARCHAR2(80) NULL, JOB_GROUP VARCHAR2(80) NULL, IS_NONCONCURRENT BOOLEAN NULL, REQUESTS_RECOVERY BOOLEAN NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE qrtz_scheduler_state ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR2(80) NOT NULL, LAST_CHECKIN_TIME NUMBER(13) NOT NULL, CHECKIN_INTERVAL NUMBER(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE qrtz_locks ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR2(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); commit; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql ================================================ -- Thanks to Patrick Lightbody for submitting this... -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE BOOL NOT NULL, IS_NONCONCURRENT BOOL NOT NULL, IS_UPDATE_DATA BOOL NOT NULL, REQUESTS_RECOVERY BOOL NOT NULL, JOB_DATA BYTEA NULL, PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT NULL, PREV_FIRE_TIME BIGINT NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT NOT NULL, END_TIME BIGINT NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT NULL, JOB_DATA BYTEA NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT NOT NULL, REPEAT_INTERVAL BIGINT NOT NULL, TIMES_TRIGGERED BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13, 4) NULL, DEC_PROP_2 NUMERIC(13, 4) NULL, BOOL_PROP_1 BOOL NULL, BOOL_PROP_2 BOOL NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BYTEA NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BYTEA NOT NULL, PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT NOT NULL, SCHED_TIME BIGINT NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT BOOL NULL, REQUESTS_RECOVERY BOOL NULL, PRIMARY KEY (SCHED_NAME, ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT NOT NULL, CHECKIN_INTERVAL BIGINT NOT NULL, PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME, LOCK_NAME) ); CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); COMMIT; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_sapdb.sql ================================================ # # Thanks to Andrew Perepelytsya for submitting this file. # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(128) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA LONG BYTE NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME FIXED(13) NULL, PREV_FIRE_TIME FIXED(13) NULL, PRIORITY FIXED(13) NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME FIXED(13) NOT NULL, END_TIME FIXED(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR FIXED(2) NULL, JOB_DATA LONG BYTE NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT FIXED(7) NOT NULL, REPEAT_INTERVAL FIXED(12) NOT NULL, TIMES_TRIGGERED FIXED(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 FIXED(10) NULL, INT_PROP_2 FIXED(10) NULL, LONG_PROP_1 FIXED(13) NULL, LONG_PROP_2 FIXED(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA LONG BYTE NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, CALENDAR LONG BYTE NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME FIXED(13) NOT NULL, SCHED_TIME FIXED(13) NOT NULL, PRIORITY FIXED(13) NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME FIXED(13) NOT NULL, CHECKIN_INTERVAL FIXED(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); commit; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_solid.sql ================================================ DROP TABLE qrtz_locks; DROP TABLE qrtz_scheduler_state; DROP TABLE qrtz_fired_triggers; DROP TABLE qrtz_paused_trigger_grps; DROP TABLE qrtz_calendars; DROP TABLE qrtz_blob_triggers; DROP TABLE qrtz_cron_triggers; DROP TABLE qrtz_simple_triggers; DROP TABLE qrtz_simprop_triggers; DROP TABLE qrtz_triggers; DROP TABLE qrtz_job_details; create table qrtz_job_details ( sched_name varchar(120) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120) , job_class_name varchar(128) not null, is_durable varchar(5) not null, is_nonconcurrent varchar(5) not null, is_update_data varchar(5) not null, requests_recovery varchar(5) not null, job_data long varbinary, primary key (sched_name,job_name,job_group) ); create table qrtz_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, job_name varchar(80) not null, job_group varchar(80) not null, description varchar(120) , next_fire_time numeric(13), prev_fire_time numeric(13), priority integer, trigger_state varchar(16) not null, trigger_type varchar(8) not null, start_time numeric(13) not null, end_time numeric(13), calendar_name varchar(80), misfire_instr smallint, job_data long varbinary, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) ); create table qrtz_simple_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, repeat_count numeric(13) not null, repeat_interval numeric(13) not null, times_triggered numeric(13) not null, primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); CREATE TABLE qrtz_simprop_triggers ( sched_name varchar(120) not null, trigger_name VARCHAR(200) NOT NULL, trigger_group VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INTEGER NULL, INT_PROP_2 INTEGER NULL, LONG_PROP_1 NUMERIC(13) NULL, LONG_PROP_2 NUMERIC(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(5) NULL, BOOL_PROP_2 VARCHAR(5) NULL, PRIMARY KEY (sched_name,trigger_name,trigger_group), FOREIGN KEY (sched_name,trigger_name,trigger_group) REFERENCES qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_cron_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, cron_expression varchar(120) not null, time_zone_id varchar(80), primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_blob_triggers( sched_name varchar(120) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, blob_data long varbinary , primary key (sched_name,trigger_name,trigger_group), foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) ); create table qrtz_calendars( sched_name varchar(120) not null, calendar_name varchar(80) not null, calendar long varbinary not null, primary key (sched_name,calendar_name) ); create table qrtz_paused_trigger_grps ( sched_name varchar(120) not null, trigger_group varchar(80) not null, primary key (sched_name,trigger_group) ); create table qrtz_fired_triggers( sched_name varchar(120) not null, entry_id varchar(95) not null, trigger_name varchar(80) not null, trigger_group varchar(80) not null, instance_name varchar(80) not null, fired_time numeric(13) not null, sched_time numeric(13) not null, priority integer not null, state varchar(16) not null, job_name varchar(80) null, job_group varchar(80) null, is_nonconcurrent varchar(5) null, requests_recovery varchar(5) null, primary key (sched_name,entry_id) ); create table qrtz_scheduler_state ( sched_name varchar(120) not null, instance_name varchar(80) not null, last_checkin_time numeric(13) not null, checkin_interval numeric(13) not null, primary key (sched_name,instance_name) ); create table qrtz_locks ( sched_name varchar(120) not null, lock_name varchar(40) not null, primary key (sched_name,lock_name) ); commit work; ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_sqlServer.sql ================================================ --# thanks to George Papastamatopoulos for submitting this ... and Marko Lahma for --# updating it. --# --# In your Quartz properties file, you'll need to set --# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate --# --# you shouse enter your DB instance's name on the next line in place of "enter_db_name_here" --# --# --# From a helpful (but anonymous) Quartz user: --# --# Regarding this error message: --# --# [Microsoft][SQLServer 2000 Driver for JDBC]Can't start a cloned connection while in manual transaction mode. --# --# --# I added "SelectMethod=cursor;" to my Connection URL in the config file. --# It Seems to work, hopefully no side effects. --# --# example: --# "jdbc:microsoft:sqlserver://dbmachine:1433;SelectMethod=cursor"; --# --# Another user has pointed out that you will probably need to use the --# JTDS driver --# USE [enter_db_name_here] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1) ALTER TABLE [dbo].[QRTZ_TRIGGERS] DROP CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1) ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] DROP CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1) ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] DROP CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1) ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] DROP CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_BLOB_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1) ALTER TABLE [dbo].[QRTZ_BLOB_TRIGGERS] DROP CONSTRAINT FK_QRTZ_BLOB_TRIGGERS_QRTZ_TRIGGERS GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_CALENDARS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_CALENDARS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_CRON_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_CRON_TRIGGERS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_BLOB_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_BLOB_TRIGGERS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_FIRED_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_FIRED_TRIGGERS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_PAUSED_TRIGGER_GRPS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_SCHEDULER_STATE]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_SCHEDULER_STATE] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_LOCKS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_LOCKS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_JOB_DETAILS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_JOB_DETAILS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_SIMPLE_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_SIMPROP_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1) DROP TABLE [dbo].[QRTZ_TRIGGERS] GO CREATE TABLE [dbo].[QRTZ_CALENDARS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [CALENDAR_NAME] [NVARCHAR] (200) NOT NULL , [CALENDAR] [VARBINARY] (max) NOT NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_CRON_TRIGGERS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [TRIGGER_NAME] [NVARCHAR] (200) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL , [CRON_EXPRESSION] [NVARCHAR] (120) NOT NULL , [TIME_ZONE_ID] [NVARCHAR] (80) ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_FIRED_TRIGGERS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [ENTRY_ID] [NVARCHAR] (95) NOT NULL , [TRIGGER_NAME] [NVARCHAR] (200) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL , [INSTANCE_NAME] [NVARCHAR] (200) NOT NULL , [FIRED_TIME] [BIGINT] NOT NULL , [SCHED_TIME] [BIGINT] NOT NULL , [PRIORITY] [INTEGER] NOT NULL , [STATE] [NVARCHAR] (16) NOT NULL, [JOB_NAME] [NVARCHAR] (200) NULL , [JOB_GROUP] [NVARCHAR] (200) NULL , [IS_NONCONCURRENT] [NVARCHAR] (1) NULL , [REQUESTS_RECOVERY] [NVARCHAR] (1) NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_SCHEDULER_STATE] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [INSTANCE_NAME] [NVARCHAR] (200) NOT NULL , [LAST_CHECKIN_TIME] [BIGINT] NOT NULL , [CHECKIN_INTERVAL] [BIGINT] NOT NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_LOCKS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [LOCK_NAME] [NVARCHAR] (40) NOT NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_JOB_DETAILS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [JOB_NAME] [NVARCHAR] (200) NOT NULL , [JOB_GROUP] [NVARCHAR] (200) NOT NULL , [DESCRIPTION] [NVARCHAR] (250) NULL , [JOB_CLASS_NAME] [NVARCHAR] (250) NOT NULL , [IS_DURABLE] [NVARCHAR] (1) NOT NULL , [IS_NONCONCURRENT] [NVARCHAR] (1) NOT NULL , [IS_UPDATE_DATA] [NVARCHAR] (1) NOT NULL , [REQUESTS_RECOVERY] [NVARCHAR] (1) NOT NULL , [JOB_DATA] [VARBINARY] (max) NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [TRIGGER_NAME] [NVARCHAR] (200) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL , [REPEAT_COUNT] [BIGINT] NOT NULL , [REPEAT_INTERVAL] [BIGINT] NOT NULL , [TIMES_TRIGGERED] [BIGINT] NOT NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [TRIGGER_NAME] [NVARCHAR] (200) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL , [STR_PROP_1] [NVARCHAR] (512) NULL, [STR_PROP_2] [NVARCHAR] (512) NULL, [STR_PROP_3] [NVARCHAR] (512) NULL, [INT_PROP_1] [INT] NULL, [INT_PROP_2] [INT] NULL, [LONG_PROP_1] [BIGINT] NULL, [LONG_PROP_2] [BIGINT] NULL, [DEC_PROP_1] [NUMERIC] (13,4) NULL, [DEC_PROP_2] [NUMERIC] (13,4) NULL, [BOOL_PROP_1] [NVARCHAR] (1) NULL, [BOOL_PROP_2] [NVARCHAR] (1) NULL, ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_BLOB_TRIGGERS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [TRIGGER_NAME] [NVARCHAR] (200) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL , [BLOB_DATA] [VARBINARY] (max) NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[QRTZ_TRIGGERS] ( [SCHED_NAME] [NVARCHAR] (120) NOT NULL , [TRIGGER_NAME] [NVARCHAR] (200) NOT NULL , [TRIGGER_GROUP] [NVARCHAR] (200) NOT NULL , [JOB_NAME] [NVARCHAR] (200) NOT NULL , [JOB_GROUP] [NVARCHAR] (200) NOT NULL , [DESCRIPTION] [NVARCHAR] (250) NULL , [NEXT_FIRE_TIME] [BIGINT] NULL , [PREV_FIRE_TIME] [BIGINT] NULL , [PRIORITY] [INTEGER] NULL , [TRIGGER_STATE] [NVARCHAR] (16) NOT NULL , [TRIGGER_TYPE] [NVARCHAR] (8) NOT NULL , [START_TIME] [BIGINT] NOT NULL , [END_TIME] [BIGINT] NULL , [CALENDAR_NAME] [NVARCHAR] (200) NULL , [MISFIRE_INSTR] [SMALLINT] NULL , [JOB_DATA] [VARBINARY] (max) NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_CALENDARS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_CALENDARS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [CALENDAR_NAME] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_CRON_TRIGGERS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_FIRED_TRIGGERS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [ENTRY_ID] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_PAUSED_TRIGGER_GRPS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [TRIGGER_GROUP] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_SCHEDULER_STATE] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [INSTANCE_NAME] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_LOCKS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_LOCKS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [LOCK_NAME] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_JOB_DETAILS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [JOB_NAME], [JOB_GROUP] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_SIMPLE_TRIGGERS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_SIMPROP_TRIGGERS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_TRIGGERS] WITH NOCHECK ADD CONSTRAINT [PK_QRTZ_TRIGGERS] PRIMARY KEY CLUSTERED ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON [PRIMARY] GO ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON DELETE CASCADE GO ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON DELETE CASCADE GO ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON DELETE CASCADE GO ALTER TABLE [dbo].[QRTZ_BLOB_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_BLOB_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( [SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP] ) ON DELETE CASCADE GO ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS] FOREIGN KEY ( [SCHED_NAME], [JOB_NAME], [JOB_GROUP] ) REFERENCES [dbo].[QRTZ_JOB_DETAILS] ( [SCHED_NAME], [JOB_NAME], [JOB_GROUP] ) GO CREATE NONCLUSTERED INDEX [IX_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS] ON [dbo].[QRTZ_CRON_TRIGGERS] ( [SCHED_NAME] ASC, [TRIGGER_NAME] ASC, [TRIGGER_GROUP] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY] GO CREATE NONCLUSTERED INDEX [IX_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS] ON [dbo].[QRTZ_SIMPLE_TRIGGERS] ( [SCHED_NAME] ASC, [TRIGGER_NAME] ASC, [TRIGGER_GROUP] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY] GO CREATE NONCLUSTERED INDEX [IX_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS] ON [dbo].[QRTZ_SIMPROP_TRIGGERS] ( [SCHED_NAME] ASC, [TRIGGER_NAME] ASC, [TRIGGER_GROUP] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY] GO CREATE NONCLUSTERED INDEX [IX_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS] ON [dbo].[QRTZ_TRIGGERS] ( [SCHED_NAME] ASC, [TRIGGER_NAME] ASC, [TRIGGER_GROUP] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY] GO ================================================ FILE: quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_sybase.sql ================================================ /*==============================================================================================*/ /* Quartz database tables creation script for Sybase ASE 12.5 */ /* Written by Pertti Laiho (email: pertti.laiho@deio.net), 9th May 2003 */ /* */ /* Compatible with Quartz version 1.1.2 */ /* */ /* Sybase ASE works ok with the SybaseDelegate delegate class. That means in your Quartz properties */ /* file, you'll need to set: */ /* org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.SybaseDelegate */ /*==============================================================================================*/ use your_db_name_here go /*==============================================================================*/ /* Clear all tables: */ /*==============================================================================*/ IF OBJECT_ID('QRTZ_FIRED_TRIGGERS') IS NOT NULL delete from QRTZ_FIRED_TRIGGERS go IF OBJECT_ID('QRTZ_PAUSED_TRIGGER_GRPS') IS NOT NULL delete from QRTZ_PAUSED_TRIGGER_GRPS go IF OBJECT_ID('QRTZ_SCHEDULER_STATE') IS NOT NULL delete from QRTZ_SCHEDULER_STATE go IF OBJECT_ID('QRTZ_LOCKS') IS NOT NULL delete from QRTZ_LOCKS go IF OBJECT_ID('QRTZ_SIMPLE_TRIGGERS') IS NOT NULL delete from QRTZ_SIMPLE_TRIGGERS go IF OBJECT_ID('QRTZ_SIMPROP_TRIGGERS') IS NOT NULL delete from QRTZ_SIMPROP_TRIGGERS go IF OBJECT_ID('QRTZ_CRON_TRIGGERS') IS NOT NULL delete from QRTZ_CRON_TRIGGERS go IF OBJECT_ID('QRTZ_BLOB_TRIGGERS') IS NOT NULL delete from QRTZ_BLOB_TRIGGERS go IF OBJECT_ID('QRTZ_TRIGGERS') IS NOT NULL delete from QRTZ_TRIGGERS go IF OBJECT_ID('QRTZ_JOB_DETAILS') IS NOT NULL delete from QRTZ_JOB_DETAILS go IF OBJECT_ID('QRTZ_CALENDARS') IS NOT NULL delete from QRTZ_CALENDARS go /*==============================================================================*/ /* Drop constraints: */ /*==============================================================================*/ alter table QRTZ_TRIGGERS drop constraint FK_triggers_job_details go alter table QRTZ_CRON_TRIGGERS drop constraint FK_cron_triggers_triggers go alter table QRTZ_SIMPLE_TRIGGERS drop constraint FK_simple_triggers_triggers go alter table QRTZ_SIMPROP_TRIGGERS drop constraint FK_simprop_triggers_triggers go alter table QRTZ_BLOB_TRIGGERS drop constraint FK_blob_triggers_triggers go /*==============================================================================*/ /* Drop tables: */ /*==============================================================================*/ drop table QRTZ_FIRED_TRIGGERS go drop table QRTZ_PAUSED_TRIGGER_GRPS go drop table QRTZ_SCHEDULER_STATE go drop table QRTZ_LOCKS go drop table QRTZ_SIMPLE_TRIGGERS go drop table QRTZ_SIMPROP_TRIGGERS go drop table QRTZ_CRON_TRIGGERS go drop table QRTZ_BLOB_TRIGGERS go drop table QRTZ_TRIGGERS go drop table QRTZ_JOB_DETAILS go drop table QRTZ_CALENDARS go /*==============================================================================*/ /* Create tables: */ /*==============================================================================*/ create table QRTZ_CALENDARS ( SCHED_NAME varchar(120) not null, CALENDAR_NAME varchar(200) not null, CALENDAR image not null ) go create table QRTZ_CRON_TRIGGERS ( SCHED_NAME varchar(120) not null, TRIGGER_NAME varchar(200) not null, TRIGGER_GROUP varchar(200) not null, CRON_EXPRESSION varchar(120) not null, TIME_ZONE_ID varchar(80) null, ) go create table QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME varchar(120) not null, TRIGGER_GROUP varchar(200) not null, ) go create table QRTZ_FIRED_TRIGGERS( SCHED_NAME varchar(120) not null, ENTRY_ID varchar(95) not null, TRIGGER_NAME varchar(200) not null, TRIGGER_GROUP varchar(200) not null, INSTANCE_NAME varchar(200) not null, FIRED_TIME numeric(13,0) not null, SCHED_TIME numeric(13,0) not null, PRIORITY int not null, STATE varchar(16) not null, JOB_NAME varchar(200) null, JOB_GROUP varchar(200) null, IS_NONCONCURRENT bit not null, REQUESTS_RECOVERY bit not null, ) go create table QRTZ_SCHEDULER_STATE ( SCHED_NAME varchar(120) not null, INSTANCE_NAME varchar(200) not null, LAST_CHECKIN_TIME numeric(13,0) not null, CHECKIN_INTERVAL numeric(13,0) not null, ) go create table QRTZ_LOCKS ( SCHED_NAME varchar(120) not null, LOCK_NAME varchar(40) not null, ) go create table QRTZ_JOB_DETAILS ( SCHED_NAME varchar(120) not null, JOB_NAME varchar(200) not null, JOB_GROUP varchar(200) not null, DESCRIPTION varchar(250) null, JOB_CLASS_NAME varchar(250) not null, IS_DURABLE bit not null, IS_NONCONCURRENT bit not null, IS_UPDATE_DATA bit not null, REQUESTS_RECOVERY bit not null, JOB_DATA image null ) go create table QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME varchar(120) not null, TRIGGER_NAME varchar(200) not null, TRIGGER_GROUP varchar(200) not null, REPEAT_COUNT numeric(13,0) not null, REPEAT_INTERVAL numeric(13,0) not null, TIMES_TRIGGERED numeric(13,0) not null ) go CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 NUMERIC(13,0) NULL, LONG_PROP_2 NUMERIC(13,0) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 bit NOT NULL, BOOL_PROP_2 bit NOT NULL ) go create table QRTZ_BLOB_TRIGGERS ( SCHED_NAME varchar(120) not null, TRIGGER_NAME varchar(200) not null, TRIGGER_GROUP varchar(200) not null, BLOB_DATA image null ) go create table QRTZ_TRIGGERS ( SCHED_NAME varchar(120) not null, TRIGGER_NAME varchar(200) not null, TRIGGER_GROUP varchar(200) not null, JOB_NAME varchar(200) not null, JOB_GROUP varchar(200) not null, DESCRIPTION varchar(250) null, NEXT_FIRE_TIME numeric(13,0) null, PREV_FIRE_TIME numeric(13,0) null, PRIORITY int null, TRIGGER_STATE varchar(16) not null, TRIGGER_TYPE varchar(8) not null, START_TIME numeric(13,0) not null, END_TIME numeric(13,0) null, CALENDAR_NAME varchar(200) null, MISFIRE_INSTR smallint null, JOB_DATA image null ) go /*==============================================================================*/ /* Create primary key constraints: */ /*==============================================================================*/ alter table QRTZ_CALENDARS add constraint PK_qrtz_calendars primary key clustered (SCHED_NAME,CALENDAR_NAME) go alter table QRTZ_CRON_TRIGGERS add constraint PK_qrtz_cron_triggers primary key clustered (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) go alter table QRTZ_FIRED_TRIGGERS add constraint PK_qrtz_fired_triggers primary key clustered (SCHED_NAME,ENTRY_ID) go alter table QRTZ_PAUSED_TRIGGER_GRPS add constraint PK_qrtz_paused_trigger_grps primary key clustered (SCHED_NAME,TRIGGER_GROUP) go alter table QRTZ_SCHEDULER_STATE add constraint PK_qrtz_scheduler_state primary key clustered (SCHED_NAME,INSTANCE_NAME) go alter table QRTZ_LOCKS add constraint PK_qrtz_locks primary key clustered (SCHED_NAME,LOCK_NAME) go alter table QRTZ_JOB_DETAILS add constraint PK_qrtz_job_details primary key clustered (SCHED_NAME,JOB_NAME, JOB_GROUP) go alter table QRTZ_SIMPLE_TRIGGERS add constraint PK_qrtz_simple_triggers primary key clustered (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) go alter table QRTZ_SIMPROP_TRIGGERS add constraint PK_qrtz_simprop_triggers primary key clustered (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) go alter table QRTZ_TRIGGERS add constraint PK_qrtz_triggers primary key clustered (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) go alter table QRTZ_BLOB_TRIGGERS add constraint PK_qrtz_blob_triggers primary key clustered (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP) go /*==============================================================================*/ /* Create foreign key constraints: */ /*==============================================================================*/ alter table QRTZ_CRON_TRIGGERS add constraint FK_cron_triggers_triggers foreign key (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) references QRTZ_TRIGGERS (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) go alter table QRTZ_SIMPLE_TRIGGERS add constraint FK_simple_triggers_triggers foreign key (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) references QRTZ_TRIGGERS (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) go alter table QRTZ_SIMPROP_TRIGGERS add constraint FK_simprop_triggers_triggers foreign key (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) references QRTZ_TRIGGERS (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) go alter table QRTZ_TRIGGERS add constraint FK_triggers_job_details foreign key (SCHED_NAME,JOB_NAME,JOB_GROUP) references QRTZ_JOB_DETAILS (SCHED_NAME,JOB_NAME,JOB_GROUP) go alter table QRTZ_BLOB_TRIGGERS add constraint FK_blob_triggers_triggers foreign key (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) references QRTZ_TRIGGERS (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) go /*==============================================================================*/ /* End of script. */ /*==============================================================================*/ ================================================ FILE: quartz/src/main/resources/org/quartz/quartz.properties ================================================ # Default Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance, if a different # properties file is not explicitly specified. # org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore ================================================ FILE: quartz/src/main/resources/org/quartz/xml/job_scheduling_data_1_8.xsd ================================================ Root level node Commands to be executed before scheduling the jobs and triggers in this file. Directives to be followed while scheduling the jobs and triggers in this file. Version of the XML Schema instance Delete all jobs, if any, in the identified group. "*" can be used to identify all groups. Will also result in deleting all triggers related to the jobs. Delete all triggers, if any, in the identified group. "*" can be used to identify all groups. Will also result in deletion of related jobs that are non-durable. Delete the identified job if it exists (will also result in deleting all triggers related to it). Delete the identified trigger if it exists (will also result in deletion of related jobs that are non-durable). Whether the existing scheduling data (with same identifiers) will be overwritten. If false, and ignore-duplicates is not false, and jobs or triggers with the same names already exist as those in the file, an error will occur. If true (and overwrite-existing-data is false) then any job/triggers encountered in this file that have names that already exist in the scheduler will be ignored, and no error will be produced. Define a JobDetail Define a JobDataMap Define a JobDataMap entry Define a Trigger Common Trigger definitions Define a SimpleTrigger Define a CronTrigger Define a DateIntervalTrigger Cron expression (see JavaDoc for examples) Special thanks to Chris Thatcher (thatcher@butterfly.net) for the regular expression! Regular expressions are not my strong point but I believe this is complete, with the caveat that order for expressions like 3-0 is not legal but will pass, and month and day names must be capitalized. If you want to examine the correctness look for the [\s] to denote the separation of individual regular expressions. This is how I break them up visually to examine them: SECONDS: ( ((([0-9]|[0-5][0-9]),)*([0-9]|[0-5][0-9])) | (([\*]|[0-9]|[0-5][0-9])(/|-)([0-9]|[0-5][0-9])) | ([\?]) | ([\*]) ) [\s] MINUTES: ( ((([0-9]|[0-5][0-9]),)*([0-9]|[0-5][0-9])) | (([\*]|[0-9]|[0-5][0-9])(/|-)([0-9]|[0-5][0-9])) | ([\?]) | ([\*]) ) [\s] HOURS: ( ((([0-9]|[0-1][0-9]|[2][0-3]),)*([0-9]|[0-1][0-9]|[2][0-3])) | (([\*]|[0-9]|[0-1][0-9]|[2][0-3])(/|-)([0-9]|[0-1][0-9]|[2][0-3])) | ([\?]) | ([\*]) ) [\s] DAY OF MONTH: ( ((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]),)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?) | (([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(/|-)([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?) | (L) | (LW) | ([1-9]W) | ([1-3][0-9]W) | ([\?]) | ([\*]) )[\s] MONTH: ( ((([1-9]|0[1-9]|1[0-2]),)*([1-9]|0[1-9]|1[0-2])) | (([1-9]|0[1-9]|1[0-2])(/|-)([1-9]|0[1-9]|1[0-2])) | (((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC),)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)) | ((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-|/)(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)) | ([\?]) | ([\*]) )[\s] DAY OF WEEK: ( (([1-7],)*([1-7])) | ([1-7](/|-)([1-7])) | (((MON|TUE|WED|THU|FRI|SAT|SUN),)*(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?) | ((MON|TUE|WED|THU|FRI|SAT|SUN)(-|/)(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?) | (([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))(L|LW)?) | (([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?) | ([\?]) | ([\*]) ) YEAR (OPTIONAL): ( [\s]? ([\*])? | ((19[7-9][0-9])|(20[0-9][0-9]))? | (((19[7-9][0-9])|(20[0-9][0-9]))(-|/)((19[7-9][0-9])|(20[0-9][0-9])))? | ((((19[7-9][0-9])|(20[0-9][0-9])),)*((19[7-9][0-9])|(20[0-9][0-9])))? ) Number of times to repeat the Trigger (-1 for indefinite) Simple Trigger Misfire Instructions Simple Trigger Misfire Instructions Date Interval Trigger Misfire Instructions Interval Units ================================================ FILE: quartz/src/main/resources/org/quartz/xml/job_scheduling_data_2_0.xsd ================================================ Root level node Commands to be executed before scheduling the jobs and triggers in this file. Directives to be followed while scheduling the jobs and triggers in this file. Version of the XML Schema instance Delete all jobs, if any, in the identified group. "*" can be used to identify all groups. Will also result in deleting all triggers related to the jobs. Delete all triggers, if any, in the identified group. "*" can be used to identify all groups. Will also result in deletion of related jobs that are non-durable. Delete the identified job if it exists (will also result in deleting all triggers related to it). Delete the identified trigger if it exists (will also result in deletion of related jobs that are non-durable). Whether the existing scheduling data (with same identifiers) will be overwritten. If false, and ignore-duplicates is not false, and jobs or triggers with the same names already exist as those in the file, an error will occur. If true (and overwrite-existing-data is false) then any job/triggers encountered in this file that have names that already exist in the scheduler will be ignored, and no error will be produced. Define a JobDetail Define a JobDataMap Define a JobDataMap entry Define a Trigger Common Trigger definitions Define a SimpleTrigger Define a CronTrigger Define a DateIntervalTrigger Cron expression (see JavaDoc for examples) Special thanks to Chris Thatcher (thatcher@butterfly.net) for the regular expression! Regular expressions are not my strong point but I believe this is complete, with the caveat that order for expressions like 3-0 is not legal but will pass, and month and day names must be capitalized. If you want to examine the correctness look for the [\s] to denote the separation of individual regular expressions. This is how I break them up visually to examine them: SECONDS: ( ((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?) | (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9])) | ([\?]) | ([\*]) ) [\s] MINUTES: ( ((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?) | (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9])) | ([\?]) | ([\*]) ) [\s] HOURS: ( ((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?) | (([\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3])) | ([\?]) | ([\*]) ) [\s] DAY OF MONTH: ( ((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?) | (([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?) | (L(-[0-9])?) | (L(-[1-2][0-9])?) | (L(-[3][0-1])?) | (LW) | ([1-9]W) | ([1-3][0-9]W) | ([\?]) | ([\*]) )[\s] MONTH: ( ((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?) | (([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2])) | (((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?) | ((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)) | ([\?]) | ([\*]) )[\s] DAY OF WEEK: ( (([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?) | ([1-7]/([1-7])) | (((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?) | ((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?) | (([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))(L|LW)?) | (([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?) | ([\?]) | ([\*]) ) YEAR (OPTIONAL): ( [\s]? ([\*])? | ((19[7-9][0-9])|(20[0-9][0-9]))? | (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))? | ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)? ) Number of times to repeat the Trigger (-1 for indefinite) Simple Trigger Misfire Instructions Cron Trigger Misfire Instructions Date Interval Trigger Misfire Instructions Interval Units ================================================ FILE: quartz/src/test/java/org/quartz/AbstractJobStoreTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.quartz.DailyTimeIntervalScheduleBuilder.MONDAY_THROUGH_FRIDAY; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.quartz.Trigger.TriggerState; import org.quartz.impl.JobDetailImpl; import org.quartz.impl.jdbcjobstore.JobStoreSupport; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.simpl.CascadingClassLoadHelper; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.JobStore; import org.quartz.spi.OperableTrigger; import org.quartz.spi.SchedulerSignaler; import org.quartz.spi.TriggerFiredResult; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; /** * Unit test for JobStores. These tests were submitted by Johannes Zillmann * as part of issue QUARTZ-306. */ public abstract class AbstractJobStoreTest { private JobStore fJobStore; private JobDetailImpl fJobDetail; private SampleSignaler fSignaler; private TestInfo testInfo; @SuppressWarnings("deprecation") @BeforeEach protected void setUp(TestInfo testInfo) throws Exception { this.testInfo = testInfo; this.fSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); this.fJobStore = createJobStore("AbstractJobStoreTest"); this.fJobStore.initialize(loadHelper, this.fSignaler); this.fJobStore.schedulerStarted(); this.fJobDetail = new JobDetailImpl("job1", "jobGroup1", MyJob.class); this.fJobDetail.setDurability(true); this.fJobStore.storeJob(this.fJobDetail, false); } @AfterEach protected void tearDown() { if (fJobStore instanceof JobStoreSupport) { //default to false ((JobStoreSupport) fJobStore).setUseEnhancedStatements(false); } destroyJobStore("AbstractJobStoreTest"); } /** * Creates a jobstore using the method name * @return the jobstore */ protected JobStore createJobStore() { return createJobStore(testInfo.getTestMethod().map(Method::getName) .orElseGet(testInfo::getDisplayName).replace(" ", "_")); } protected abstract JobStore createJobStore(String name); protected abstract void destroyJobStore(String name); protected abstract Map stores(); @SuppressWarnings("deprecation") @Test void testAcquireNextTrigger() throws Exception { Date baseFireTimeDate = DateBuilder.evenMinuteDateAfterNow(); long baseFireTime = baseFireTimeDate.getTime(); OperableTrigger trigger1 = new SimpleTriggerImpl("trigger1", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 200000), new Date(baseFireTime + 200000), 2, 2000); OperableTrigger trigger2 = new SimpleTriggerImpl("trigger2", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 50000), new Date(baseFireTime + 200000), 2, 2000); OperableTrigger trigger3 = new SimpleTriggerImpl("trigger1", "triggerGroup2", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 100000), new Date(baseFireTime + 200000), 2, 2000); trigger1.computeFirstFireTime(null); trigger2.computeFirstFireTime(null); trigger3.computeFirstFireTime(null); this.fJobStore.storeTrigger(trigger1, false); this.fJobStore.storeTrigger(trigger2, false); this.fJobStore.storeTrigger(trigger3, false); long firstFireTime = new Date(trigger1.getNextFireTime().getTime()).getTime(); assertTrue(this.fJobStore.acquireNextTriggers(10, 1, 0L).isEmpty()); assertEquals( trigger2.getKey(), this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 1, 0L).get(0).getKey()); assertEquals( trigger3.getKey(), this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 1, 0L).get(0).getKey()); assertEquals( trigger1.getKey(), this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 1, 0L).get(0).getKey()); assertTrue( this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 1, 0L).isEmpty()); // release trigger3 this.fJobStore.releaseAcquiredTrigger(trigger3); assertEquals( trigger3, this.fJobStore.acquireNextTriggers(new Date(trigger1.getNextFireTime().getTime()).getTime() + 10000, 1, 1L).get(0)); } @SuppressWarnings("deprecation") @Test void testAcquireNextTriggerBatch() throws Exception { long baseFireTime = System.currentTimeMillis() - 1000; OperableTrigger early = new SimpleTriggerImpl("early", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime), new Date(baseFireTime + 5), 2, 2000); OperableTrigger trigger1 = new SimpleTriggerImpl("trigger1", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 200000), new Date(baseFireTime + 200005), 2, 2000); OperableTrigger trigger2 = new SimpleTriggerImpl("trigger2", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 210000), new Date(baseFireTime + 210005), 2, 2000); OperableTrigger trigger3 = new SimpleTriggerImpl("trigger3", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 220000), new Date(baseFireTime + 220005), 2, 2000); OperableTrigger trigger4 = new SimpleTriggerImpl("trigger4", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 230000), new Date(baseFireTime + 230005), 2, 2000); OperableTrigger trigger10 = new SimpleTriggerImpl("trigger10", "triggerGroup2", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 500000), new Date(baseFireTime + 700000), 2, 2000); early.computeFirstFireTime(null); early.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); trigger1.computeFirstFireTime(null); trigger2.computeFirstFireTime(null); trigger3.computeFirstFireTime(null); trigger4.computeFirstFireTime(null); trigger10.computeFirstFireTime(null); this.fJobStore.storeTrigger(early, false); this.fJobStore.storeTrigger(trigger1, false); this.fJobStore.storeTrigger(trigger2, false); this.fJobStore.storeTrigger(trigger3, false); this.fJobStore.storeTrigger(trigger4, false); this.fJobStore.storeTrigger(trigger10, false); long firstFireTime = new Date(trigger1.getNextFireTime().getTime()).getTime(); List acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 4, 1000L); assertEquals(1, acquiredTriggers.size()); assertEquals(early.getKey(), acquiredTriggers.get(0).getKey()); this.fJobStore.releaseAcquiredTrigger(early); acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 4, 205000); assertEquals(2, acquiredTriggers.size()); assertEquals(early.getKey(), acquiredTriggers.get(0).getKey()); assertEquals(trigger1.getKey(), acquiredTriggers.get(1).getKey()); this.fJobStore.releaseAcquiredTrigger(early); this.fJobStore.releaseAcquiredTrigger(trigger1); this.fJobStore.removeTrigger(early.getKey()); acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 5, 100000L); assertEquals(4, acquiredTriggers.size()); assertEquals(trigger1.getKey(), acquiredTriggers.get(0).getKey()); assertEquals(trigger2.getKey(), acquiredTriggers.get(1).getKey()); assertEquals(trigger3.getKey(), acquiredTriggers.get(2).getKey()); assertEquals(trigger4.getKey(), acquiredTriggers.get(3).getKey()); this.fJobStore.releaseAcquiredTrigger(trigger1); this.fJobStore.releaseAcquiredTrigger(trigger2); this.fJobStore.releaseAcquiredTrigger(trigger3); this.fJobStore.releaseAcquiredTrigger(trigger4); acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 10000, 6, 100000L); assertEquals(4, acquiredTriggers.size()); assertEquals(trigger1.getKey(), acquiredTriggers.get(0).getKey()); assertEquals(trigger2.getKey(), acquiredTriggers.get(1).getKey()); assertEquals(trigger3.getKey(), acquiredTriggers.get(2).getKey()); assertEquals(trigger4.getKey(), acquiredTriggers.get(3).getKey()); this.fJobStore.releaseAcquiredTrigger(trigger1); this.fJobStore.releaseAcquiredTrigger(trigger2); this.fJobStore.releaseAcquiredTrigger(trigger3); this.fJobStore.releaseAcquiredTrigger(trigger4); acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 1, 5, 0L); assertEquals(1, acquiredTriggers.size()); assertEquals(trigger1.getKey(), acquiredTriggers.get(0).getKey()); this.fJobStore.releaseAcquiredTrigger(trigger1); acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 250, 5, 19999L); assertEquals(2, acquiredTriggers.size()); assertEquals(trigger1.getKey(), acquiredTriggers.get(0).getKey()); assertEquals(trigger2.getKey(), acquiredTriggers.get(1).getKey()); this.fJobStore.releaseAcquiredTrigger(trigger1); this.fJobStore.releaseAcquiredTrigger(trigger2); this.fJobStore.releaseAcquiredTrigger(trigger3); acquiredTriggers = this.fJobStore.acquireNextTriggers(firstFireTime + 150, 5, 5000L); assertEquals(1, acquiredTriggers.size()); assertEquals(trigger1.getKey(), acquiredTriggers.get(0).getKey()); this.fJobStore.releaseAcquiredTrigger(trigger1); } @SuppressWarnings("deprecation") @Test void testTriggerStates() throws Exception { OperableTrigger trigger = new SimpleTriggerImpl("trigger1", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(System.currentTimeMillis() + 100000), new Date(System.currentTimeMillis() + 200000), 2, 2000); trigger.computeFirstFireTime(null); assertEquals(TriggerState.NONE, this.fJobStore.getTriggerState(trigger.getKey())); this.fJobStore.storeTrigger(trigger, false); assertEquals(TriggerState.NORMAL, this.fJobStore.getTriggerState(trigger.getKey())); this.fJobStore.pauseTrigger(trigger.getKey()); assertEquals(TriggerState.PAUSED, this.fJobStore.getTriggerState(trigger.getKey())); this.fJobStore.resumeTrigger(trigger.getKey()); assertEquals(TriggerState.NORMAL, this.fJobStore.getTriggerState(trigger.getKey())); trigger = this.fJobStore.acquireNextTriggers( new Date(trigger.getNextFireTime().getTime()).getTime() + 10000, 1, 1L).get(0); assertNotNull(trigger); this.fJobStore.releaseAcquiredTrigger(trigger); trigger=this.fJobStore.acquireNextTriggers( new Date(trigger.getNextFireTime().getTime()).getTime() + 10000, 1, 1L).get(0); assertNotNull(trigger); assertTrue(this.fJobStore.acquireNextTriggers( new Date(trigger.getNextFireTime().getTime()).getTime() + 10000, 1, 1L).isEmpty()); } // See: http://jira.opensymphony.com/browse/QUARTZ-606 @SuppressWarnings("deprecation") @Disabled @Test void testStoreTriggerReplacesTrigger() throws Exception { String jobName = "StoreTriggerReplacesTrigger"; String jobGroup = "StoreTriggerReplacesTriggerGroup"; JobDetailImpl detail = new JobDetailImpl(jobName, jobGroup, MyJob.class); fJobStore.storeJob(detail, false); String trName = "StoreTriggerReplacesTrigger"; String trGroup = "StoreTriggerReplacesTriggerGroup"; OperableTrigger tr = new SimpleTriggerImpl(trName ,trGroup, new Date()); tr.setJobKey(new JobKey(jobName, jobGroup)); tr.setCalendarName(null); fJobStore.storeTrigger(tr, false); assertEquals(tr,fJobStore.retrieveTrigger(tr.getKey())); try { fJobStore.storeTrigger(tr, false); fail("an attempt to store duplicate trigger succeeded"); } catch(ObjectAlreadyExistsException oaee) { // expected } tr.setCalendarName("QQ"); fJobStore.storeTrigger(tr, true); //fails here assertEquals(tr, fJobStore.retrieveTrigger(tr.getKey())); assertEquals( "StoreJob doesn't replace triggers", "QQ", fJobStore.retrieveTrigger(tr.getKey()).getCalendarName()); } @SuppressWarnings("deprecation") @Test void testPauseJobGroupPausesNewJob() throws Exception { // Pausing job groups in JDBCJobStore is broken, see QTZ-208 if (fJobStore instanceof JobStoreSupport) return; final String jobName1 = "PauseJobGroupPausesNewJob"; final String jobName2 = "PauseJobGroupPausesNewJob2"; final String jobGroup = "PauseJobGroupPausesNewJobGroup"; JobDetailImpl detail = new JobDetailImpl(jobName1, jobGroup, MyJob.class); detail.setDurability(true); fJobStore.storeJob(detail, false); fJobStore.pauseJobs(GroupMatcher.jobGroupEquals(jobGroup)); detail = new JobDetailImpl(jobName2, jobGroup, MyJob.class); detail.setDurability(true); fJobStore.storeJob(detail, false); String trName = "PauseJobGroupPausesNewJobTrigger"; String trGroup = "PauseJobGroupPausesNewJobTriggerGroup"; OperableTrigger tr = new SimpleTriggerImpl(trName, trGroup, new Date()); tr.setJobKey(new JobKey(jobName2, jobGroup)); fJobStore.storeTrigger(tr, false); assertEquals(TriggerState.PAUSED, fJobStore.getTriggerState(tr.getKey())); } @Test void testStoreAndRetrieveJobs() throws Exception { SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore("testStoreAndRetrieveJobs"); store.initialize(loadHelper, schedSignaler); // Store jobs. for (int i=0; i < 10; i++) { String group = i < 5 ? "a" : "b"; JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job" + i, group).build(); store.storeJob(job, false); } // Retrieve jobs. for (int i=0; i < 10; i++) { String group = i < 5 ? "a" : "b"; JobKey jobKey = JobKey.jobKey("job" + i, group); JobDetail storedJob = store.retrieveJob(jobKey); assertEquals(jobKey, storedJob.getKey()); } // Retrieve by group assertEquals(5, store.getJobKeys(GroupMatcher.jobGroupEquals("a")).size(), "Wrong number of jobs in group 'a'"); assertEquals(5, store.getJobKeys(GroupMatcher.jobGroupEquals("b")).size(), "Wrong number of jobs in group 'b'"); } @Test void testStoreAndRetrieveJobsGroups() throws Exception { SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore("testStoreAndRetrieveJobsGroups"); store.initialize(loadHelper, schedSignaler); List expectedJobs = new ArrayList<>(10); // these will NOT be in order because of hashmap // Store jobs. for (int i=0; i < 10; i++) { String group = i < 5 ? "a" : "b"; JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job" + i, group).build(); store.storeJob(job, false); expectedJobs.add(job); } List expectedKey = expectedJobs.stream().map(JobDetail::getKey).collect(Collectors.toList()); List actual = store.getJobDetails(GroupMatcher.anyGroup()); Set jobKeys = actual.stream().map(JobDetail::getKey).collect(Collectors.toSet()); // Retrieve jobs. for (int i=0; i < 10; i++) { assertTrue(jobKeys.contains(actual.get(i).getKey())); //assertEquals(expectedJobs.get(i).getKey(), actual.get(i).getKey(), "Job does not matche expected"); } List listA = store.getJobDetails(GroupMatcher.jobGroupEquals("a")); jobKeys = listA.stream().map(JobDetail::getKey).collect(Collectors.toSet()); assertEquals(5, listA.size(), "Wrong number of jobs in group 'a'"); for (int i=0; i < 5; i++) { assertTrue(jobKeys.contains(expectedKey.get(i))); } List listB = store.getJobDetails(GroupMatcher.jobGroupEquals("b")); jobKeys = listB.stream().map(JobDetail::getKey).collect(Collectors.toSet()); // Retrieve by group assertEquals(5, listB.size(), "Wrong number of jobs in group 'b'"); for (int i=5; i < 10; i++) { assertTrue(jobKeys.contains(expectedKey.get(i))); } } private JobStore createVariedTriggers() throws JobPersistenceException, SchedulerConfigException{ SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore(); store.initialize(loadHelper, schedSignaler); // Store jobs and triggers. for (int i=0; i < 10; i++) { String group = i < 5 ? "a" : "b"; JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job" + i, group).build(); store.storeJob(job, true); Trigger trigger; if (i == 0 || i == 5) { trigger = TriggerBuilder.newTrigger() .withIdentity("job" + i, group) .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInDays(1)) .forJob(job) .build(); } else if (i == 1 || i == 6) { trigger = TriggerBuilder.newTrigger() .withIdentity("job" + i, group) .withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")) .forJob(job) .build(); } else if (i == 2 || i == 7) { trigger = TriggerBuilder.newTrigger() .withIdentity("job" + i, group) .withSchedule(DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(17, 0)) .onDaysOfTheWeek(MONDAY_THROUGH_FRIDAY) .withIntervalInHours(1)) .forJob(job) .build(); } else if (i == 3 || i == 8){ trigger = TriggerBuilder.newTrigger() .withIdentity("job" + i, group) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(15).repeatForever()) .forJob(job) .build(); } else { continue; } store.storeTrigger((OperableTrigger)trigger, true); } return store; } @Test void testStoreAndRetrieveTriggers() throws Exception { SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore(); store.initialize(loadHelper, schedSignaler); // Store jobs and triggers. for (int i=0; i < 10; i++) { String group = i < 5 ? "a" : "b"; JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job" + i, group).build(); store.storeJob(job, true); SimpleScheduleBuilder schedule = SimpleScheduleBuilder.simpleSchedule(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("job" + i, group).withSchedule(schedule).forJob(job).build(); store.storeTrigger((OperableTrigger)trigger, true); } // Retrieve job and trigger. for (int i=0; i < 10; i++) { String group = i < 5 ? "a" : "b"; JobKey jobKey = JobKey.jobKey("job" + i, group); JobDetail storedJob = store.retrieveJob(jobKey); assertEquals(jobKey, storedJob.getKey()); TriggerKey triggerKey = TriggerKey.triggerKey("job" + i, group); Trigger storedTrigger = store.retrieveTrigger(triggerKey); assertEquals(triggerKey, storedTrigger.getKey()); } // Retrieve by group assertEquals(5, store.getJobKeys(GroupMatcher.jobGroupEquals("a")).size(), "Wrong number of jobs in group 'a'"); assertEquals(5, store.getJobKeys(GroupMatcher.jobGroupEquals("b")).size(), "Wrong number of jobs in group 'b'"); } @TestFactory Stream testEnhancedTriggerAndJobMatchers() throws Exception { final JobStore store = createVariedTriggers(); final String A_GROUP = "a"; final String B_GROUP = "b"; return Stream.of( dynamicTest("Get Group Jobs Enhanced", () -> { assertEquals(5, store.getJobKeys(GroupMatcher.jobGroupEquals(A_GROUP)).size(), "Wrong number of jobs in group: " + A_GROUP); assertEquals(5, store.getJobKeys(GroupMatcher.jobGroupEquals(B_GROUP)).size(), "Wrong number of jobs in group: " + B_GROUP); }), dynamicTest("Enhanced Trigger and JobMatcher test", () -> { for (int i=0; i < 10; i++) { String group = i < 5 ? A_GROUP : B_GROUP; JobKey jobKey = JobKey.jobKey("job" + i, group); JobDetail storedJob = store.retrieveJob(jobKey); assertEquals(jobKey, storedJob.getKey()); TriggerKey triggerKey = TriggerKey.triggerKey("job" + i, group); Trigger storedTrigger = store.retrieveTrigger(triggerKey); if (i != 4 && i != 9) { assertEquals(triggerKey, storedTrigger.getKey()); } else { assertNull(storedTrigger); } } }), dynamicTest("testStoreAndRetrieveTriggersJustTriggerGroupMatchers", () -> { //TODO: get by matcher List aTriggers = store.getTriggersByTriggerGroup(GroupMatcher.triggerGroupEquals(A_GROUP)); List bTriggers = store.getTriggersByTriggerGroup(GroupMatcher.triggerGroupEquals(B_GROUP)); assertEquals(4, aTriggers.size(), "Wrong number of jobs in group: " + A_GROUP); assertEquals(4, bTriggers.size(), "Wrong number of jobs in group: " + B_GROUP); }), dynamicTest("Match exact - only Job Group", () -> { //TODO: get by matcher List aTriggers = store.getTriggersByJobGroup(GroupMatcher.jobGroupEquals(A_GROUP)); List bTriggers = store.getTriggersByJobGroup(GroupMatcher.jobGroupEquals(B_GROUP)); assertEquals(4, aTriggers.size(), "Wrong number of jobs in group: " + A_GROUP); assertEquals(4, bTriggers.size(), "Wrong number of jobs in group: " + B_GROUP); }), dynamicTest("Match exact both", () -> { List aTriggers = store.getTriggersByJobAndTriggerGroup(GroupMatcher.jobGroupEquals(A_GROUP), GroupMatcher.triggerGroupEquals(A_GROUP)); List bTriggers = store.getTriggersByJobAndTriggerGroup(GroupMatcher.jobGroupEquals(B_GROUP), GroupMatcher.triggerGroupEquals(B_GROUP)); assertEquals(4, aTriggers.size(), "Wrong number of jobs in group: " + A_GROUP); assertEquals(4, bTriggers.size(), "Wrong number of jobs in group: " + B_GROUP); }) ); } @Test void testMatchers() throws Exception { SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore("testMatchers"); store.initialize(loadHelper, schedSignaler); JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "aaabbbccc").build(); store.storeJob(job, true); SimpleScheduleBuilder schedule = SimpleScheduleBuilder.simpleSchedule(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trig1", "aaabbbccc").withSchedule(schedule).forJob(job).build(); store.storeTrigger((OperableTrigger) trigger, true); job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "xxxyyyzzz").build(); store.storeJob(job, true); schedule = SimpleScheduleBuilder.simpleSchedule(); trigger = TriggerBuilder.newTrigger().withIdentity("trig1", "xxxyyyzzz").withSchedule(schedule).forJob(job).build(); store.storeTrigger((OperableTrigger) trigger, true); job = JobBuilder.newJob(MyJob.class).withIdentity("job2", "xxxyyyzzz").build(); store.storeJob(job, true); schedule = SimpleScheduleBuilder.simpleSchedule(); trigger = TriggerBuilder.newTrigger().withIdentity("trig2", "xxxyyyzzz").withSchedule(schedule).forJob(job).build(); store.storeTrigger((OperableTrigger) trigger, true); Set jkeys = store.getJobKeys(GroupMatcher.anyJobGroup()); assertEquals(3, jkeys.size(), "Wrong number of jobs found by anything matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupEquals("xxxyyyzzz")); assertEquals(2, jkeys.size(), "Wrong number of jobs found by equals matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupEquals("aaabbbccc")); assertEquals(1, jkeys.size(), "Wrong number of jobs found by equals matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupStartsWith("aa")); assertEquals(1, jkeys.size(), "Wrong number of jobs found by starts with matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupStartsWith("xx")); assertEquals(2, jkeys.size(), "Wrong number of jobs found by starts with matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupEndsWith("cc")); assertEquals(1, jkeys.size(), "Wrong number of jobs found by ends with matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupEndsWith("zzz")); assertEquals(2, jkeys.size(), "Wrong number of jobs found by ends with matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupContains("bc")); assertEquals(1, jkeys.size(), "Wrong number of jobs found by contains with matcher"); jkeys = store.getJobKeys(GroupMatcher.jobGroupContains("yz")); assertEquals(2, jkeys.size(), "Wrong number of jobs found by contains with matcher"); Set tkeys = store.getTriggerKeys(GroupMatcher.anyTriggerGroup()); assertEquals(3, tkeys.size(), "Wrong number of triggers found by anything matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupEquals("xxxyyyzzz")); assertEquals(2, tkeys.size(), "Wrong number of triggers found by equals matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupEquals("aaabbbccc")); assertEquals(1, tkeys.size(), "Wrong number of triggers found by equals matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupStartsWith("aa")); assertEquals(1, tkeys.size(), "Wrong number of triggers found by starts with matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupStartsWith("xx")); assertEquals(2, tkeys.size(), "Wrong number of triggers found by starts with matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupEndsWith("cc")); assertEquals(1, tkeys.size(), "Wrong number of triggers found by ends with matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupEndsWith("zzz")); assertEquals(2, tkeys.size(), "Wrong number of triggers found by ends with matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupContains("bc")); assertEquals(1, tkeys.size(), "Wrong number of triggers found by contains with matcher"); tkeys = store.getTriggerKeys(GroupMatcher.triggerGroupContains("yz")); assertEquals(2, tkeys.size(), "Wrong number of triggers found by contains with matcher"); } @Test void testAcquireTriggers() throws Exception { SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore("testAcquireTriggers"); store.initialize(loadHelper, schedSignaler); // Setup: Store jobs and triggers. long MIN = 60 * 1000L; Date startTime0 = new Date(System.currentTimeMillis() + MIN); // a min from now. for (int i=0; i < 10; i++) { Date startTime = new Date(startTime0.getTime() + i * MIN); // a min apart JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job" + i).build(); SimpleScheduleBuilder schedule = SimpleScheduleBuilder.repeatMinutelyForever(2); OperableTrigger trigger = (OperableTrigger)TriggerBuilder.newTrigger().withIdentity("job" + i).withSchedule(schedule).forJob(job).startAt(startTime).build(); // Manually trigger the first fire time computation that scheduler would do. Otherwise // the store.acquireNextTriggers() will not work properly. Date fireTime = trigger.computeFirstFireTime(null); assertNotNull(fireTime); store.storeJobAndTrigger(job, trigger); } // Test acquire one trigger at a time for (int i=0; i < 10; i++) { long noLaterThan = (startTime0.getTime() + i * MIN); int maxCount = 1; long timeWindow = 0; List triggers = store.acquireNextTriggers(noLaterThan, maxCount, timeWindow); assertEquals(1, triggers.size()); assertEquals("job" + i, triggers.get(0).getKey().getName()); // Let's remove the trigger now. store.removeJob(triggers.get(0).getJobKey()); } } @Test void testAcquireTriggersInBatch() throws Exception { SchedulerSignaler schedSignaler = new SampleSignaler(); ClassLoadHelper loadHelper = new CascadingClassLoadHelper(); loadHelper.initialize(); JobStore store = createJobStore("testAcquireTriggersInBatch"); store.initialize(loadHelper, schedSignaler); // Setup: Store jobs and triggers. long MIN = 60 * 1000L; Date startTime0 = new Date(System.currentTimeMillis() + MIN); // a min from now. for (int i=0; i < 10; i++) { Date startTime = new Date(startTime0.getTime() + i * MIN); // a min apart JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job" + i).build(); SimpleScheduleBuilder schedule = SimpleScheduleBuilder.repeatMinutelyForever(2); OperableTrigger trigger = (OperableTrigger)TriggerBuilder.newTrigger().withIdentity("job" + i).withSchedule(schedule).forJob(job).startAt(startTime).build(); // Manually trigger the first fire time computation that scheduler would do. Otherwise // the store.acquireNextTriggers() will not work properly. Date fireTime = trigger.computeFirstFireTime(null); assertNotNull(fireTime); store.storeJobAndTrigger(job, trigger); } // Test acquire batch of triggers at a time long noLaterThan = startTime0.getTime() + 10 * MIN; int maxCount = 7; // time window needs to be big to be able to pick up multiple triggers when they are a minute apart long timeWindow = 8 * MIN; List triggers = store.acquireNextTriggers(noLaterThan, maxCount, timeWindow); assertEquals(7, triggers.size()); for (int i=0; i < 7; i++) { assertEquals("job" + i, triggers.get(i).getKey().getName()); } } @Test void testResetErrorTrigger() throws Exception { Date baseFireTimeDate = DateBuilder.evenMinuteDateAfterNow(); long baseFireTime = baseFireTimeDate.getTime(); // create and store a trigger OperableTrigger trigger1 = new SimpleTriggerImpl("trigger1", "triggerGroup1", this.fJobDetail.getName(), this.fJobDetail.getGroup(), new Date(baseFireTime + 200000), new Date(baseFireTime + 200000), 2, 2000); trigger1.computeFirstFireTime(null); this.fJobStore.storeTrigger(trigger1, false); long firstFireTime = new Date(trigger1.getNextFireTime().getTime()).getTime(); // pretend to fire it List aqTs = this.fJobStore.acquireNextTriggers( firstFireTime + 10000, 1, 0L); assertEquals(trigger1.getKey(), aqTs.get(0).getKey()); List fTs = this.fJobStore.triggersFired(aqTs); TriggerFiredResult ft = fTs.get(0); // get the trigger into error state this.fJobStore.triggeredJobComplete(ft.getTriggerFiredBundle().getTrigger(), ft.getTriggerFiredBundle().getJobDetail(), Trigger.CompletedExecutionInstruction.SET_TRIGGER_ERROR); TriggerState state = this.fJobStore.getTriggerState(trigger1.getKey()); assertEquals(TriggerState.ERROR, state); // test reset this.fJobStore.resetTriggerFromErrorState(trigger1.getKey()); state = this.fJobStore.getTriggerState(trigger1.getKey()); assertEquals(TriggerState.NORMAL, state); } @Test void testStoreJobReplacesJob() throws Exception { JobDetailImpl testJob = new JobDetailImpl("testDupeJob", "testDupeGroup", MyJob.class); testJob.setDescription("123abc"); testJob.setDurability(true); assertDoesNotThrow(() -> { fJobStore.storeJob(testJob, false); }); JobDetail origJob = fJobStore.retrieveJob(testJob.getKey()); assertNotNull(origJob); assertEquals(testJob.getKey(), origJob.getKey()); assertEquals(testJob.getDescription(), origJob.getDescription()); JobDetailImpl dupeJob = new JobDetailImpl("testDupeJob", "testDupeGroup", MyJob.class); dupeJob.setDescription("123abc"); dupeJob.setDurability(true); assertThrows(SchedulerException.class, () -> { fJobStore.storeJob(dupeJob, false); }, "storing a duplicate job should not succeed with replaceExisting=false"); assertDoesNotThrow(() -> { fJobStore.storeJob(dupeJob, true); }, "storing a duplicate job should succeed with replaceExisting=true"); JobDetail reloadedJob = fJobStore.retrieveJob(testJob.getKey()); assertNotNull(reloadedJob); assertEquals(testJob.getKey(), reloadedJob.getKey()); assertEquals(testJob.getDescription(), reloadedJob.getDescription()); } public static class SampleSignaler implements SchedulerSignaler { volatile int fMisfireCount = 0; public void notifyTriggerListenersMisfired(Trigger trigger) { System.out.println("Trigger misfired: " + trigger.getKey() + ", fire time: " + trigger.getNextFireTime()); fMisfireCount++; } public void signalSchedulingChange(long candidateNewNextFireTime) { } public void notifySchedulerListenersFinalized(Trigger trigger) { } public void notifySchedulerListenersJobDeleted(JobKey jobKey) { } public void notifySchedulerListenersError(String string, SchedulerException jpe) { } } /** An empty job for testing purpose. */ public static class MyJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { // } } } ================================================ FILE: quartz/src/test/java/org/quartz/AbstractSchedulerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.*; import static org.quartz.JobBuilder.newJob; import static org.quartz.JobKey.jobKey; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import static org.quartz.TriggerKey.triggerKey; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; import org.quartz.Trigger.TriggerState; import org.quartz.impl.matchers.GroupMatcher; /** * Test High Level Scheduler functionality (implicitly tests the underlying jobstore (RAMJobStore)) */ public abstract class AbstractSchedulerTest { private static final String BARRIER = "BARRIER"; private static final String DATE_STAMPS = "DATE_STAMPS"; private static final String JOB_THREAD = "JOB_THREAD"; @SuppressWarnings("deprecation") public static class TestStatefulJob implements StatefulJob { public void execute(JobExecutionContext context) throws JobExecutionException { } } public static class TestJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { } } public static final long TEST_TIMEOUT_SECONDS = 50; public static class TestJobWithSync implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { try { @SuppressWarnings("unchecked") List jobExecTimestamps = (List)context.getScheduler().getContext().get(DATE_STAMPS); CyclicBarrier barrier = (CyclicBarrier)context.getScheduler().getContext().get(BARRIER); jobExecTimestamps.add(System.currentTimeMillis()); barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (Throwable e) { e.printStackTrace(); throw new AssertionError("Await on barrier was interrupted: " + e.toString()); } } } @DisallowConcurrentExecution @PersistJobDataAfterExecution public static class TestAnnotatedJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { } } protected abstract Scheduler createScheduler(String name, int threadPoolSize) throws SchedulerException; @Test void testBasicStorageFunctions() throws Exception { Scheduler sched = createScheduler("testBasicStorageFunctions", 2); // test basic storage functions of scheduler... JobDetail job = newJob() .ofType(TestJob.class) .withIdentity("j1") .storeDurably() .build(); assertFalse(sched.checkExists(jobKey("j1")), "Unexpected existence of job named 'j1'."); sched.addJob(job, false); assertTrue(sched.checkExists(jobKey("j1")), "Expected existence of job named 'j1' but checkExists return false."); job = sched.getJobDetail(jobKey("j1")); assertNotNull(job,"Stored job not found!"); sched.deleteJob(jobKey("j1")); Trigger trigger = newTrigger() .withIdentity("t1") .forJob(job) .startNow() .withSchedule(simpleSchedule() .repeatForever() .withIntervalInSeconds(5)) .build(); assertFalse(sched.checkExists(triggerKey("t1")), "Unexpected existence of trigger named '11'."); sched.scheduleJob(job, trigger); assertTrue(sched.checkExists(triggerKey("t1")), "Expected existence of trigger named 't1' but checkExists return false."); job = sched.getJobDetail(jobKey("j1")); assertNotNull(job,"Stored job not found!"); trigger = sched.getTrigger(triggerKey("t1")); assertNotNull(trigger,"Stored trigger not found!"); job = newJob() .ofType(TestJob.class) .withIdentity("j2", "g1") .build(); trigger = newTrigger() .withIdentity("t2", "g1") .forJob(job) .startNow() .withSchedule(simpleSchedule() .repeatForever() .withIntervalInSeconds(5)) .build(); sched.scheduleJob(job, trigger); job = newJob() .ofType(TestJob.class) .withIdentity("j3", "g1") .build(); trigger = newTrigger() .withIdentity("t3", "g1") .forJob(job) .startNow() .withSchedule(simpleSchedule() .repeatForever() .withIntervalInSeconds(5)) .build(); sched.scheduleJob(job, trigger); List jobGroups = sched.getJobGroupNames(); List triggerGroups = sched.getTriggerGroupNames(); assertEquals(2, jobGroups.size(), "Job group list size expected to be = 2 "); assertEquals(2, triggerGroups.size(), "Trigger group list size expected to be = 2 "); Set jobKeys = sched.getJobKeys(GroupMatcher.jobGroupEquals(JobKey.DEFAULT_GROUP)); Set triggerKeys = sched.getTriggerKeys(GroupMatcher.triggerGroupEquals(TriggerKey.DEFAULT_GROUP)); assertEquals(1, jobKeys.size(), "Number of jobs expected in default group was 1 "); assertEquals(1, triggerKeys.size(), "Number of triggers expected in default group was 1 "); jobKeys = sched.getJobKeys(GroupMatcher.jobGroupEquals("g1")); triggerKeys = sched.getTriggerKeys(GroupMatcher.triggerGroupEquals("g1")); assertEquals(2, jobKeys.size(), "Number of jobs expected in 'g1' group was 2 "); assertEquals(2, triggerKeys.size(), "Number of triggers expected in 'g1' group was 2 "); TriggerState s = sched.getTriggerState(triggerKey("t2", "g1")); assertEquals(TriggerState.NORMAL, s, "State of trigger t2 expected to be NORMAL "); sched.pauseTrigger(triggerKey("t2", "g1")); s = sched.getTriggerState(triggerKey("t2", "g1")); assertEquals(TriggerState.PAUSED, s, "State of trigger t2 expected to be PAUSED "); sched.resumeTrigger(triggerKey("t2", "g1")); s = sched.getTriggerState(triggerKey("t2", "g1")); assertEquals(TriggerState.NORMAL, s, "State of trigger t2 expected to be NORMAL "); Set pausedGroups = sched.getPausedTriggerGroups(); assertTrue(pausedGroups.isEmpty(), "Size of paused trigger groups list expected to be 0 "); sched.pauseTriggers(GroupMatcher.triggerGroupEquals("g1")); // test that adding a trigger to a paused group causes the new trigger to be paused also... job = newJob() .ofType(TestJob.class) .withIdentity("j4", "g1") .build(); trigger = newTrigger() .withIdentity("t4", "g1") .forJob(job) .startNow() .withSchedule(simpleSchedule() .repeatForever() .withIntervalInSeconds(5)) .build(); sched.scheduleJob(job, trigger); pausedGroups = sched.getPausedTriggerGroups(); assertEquals(1, pausedGroups.size(), "Size of paused trigger groups list expected to be 1 "); s = sched.getTriggerState(triggerKey("t2", "g1")); assertEquals(TriggerState.PAUSED, s, "State of trigger t2 expected to be PAUSED "); s = sched.getTriggerState(triggerKey("t4", "g1")); assertEquals(TriggerState.PAUSED, s, "State of trigger t4 expected to be PAUSED "); sched.resumeTriggers(GroupMatcher.triggerGroupEquals("g1")); s = sched.getTriggerState(triggerKey("t2", "g1")); assertEquals(TriggerState.NORMAL, s, "State of trigger t2 expected to be NORMAL "); s = sched.getTriggerState(triggerKey("t4", "g1")); assertEquals(TriggerState.NORMAL, s, "State of trigger t4 expected to be NORMAL "); pausedGroups = sched.getPausedTriggerGroups(); assertEquals(0, pausedGroups.size(), "Size of paused trigger groups list expected to be 0 "); assertFalse(sched.unscheduleJob(triggerKey("foasldfksajdflk")), "Scheduler should have returned 'false' from attempt to unschedule non-existing trigger. "); assertTrue(sched.unscheduleJob(triggerKey("t3", "g1")), "Scheduler should have returned 'true' from attempt to unschedule existing trigger. "); jobKeys = sched.getJobKeys(GroupMatcher.jobGroupEquals("g1")); triggerKeys = sched.getTriggerKeys(GroupMatcher.triggerGroupEquals("g1")); assertEquals(2, jobKeys.size(), "Number of jobs expected in 'g1' group was 1 "); // job should have been deleted also, because it is non-durable assertEquals(2, triggerKeys.size(), "Number of triggers expected in 'g1' group was 1 "); assertTrue(sched.unscheduleJob(triggerKey("t1")), "Scheduler should have returned 'true' from attempt to unschedule existing trigger. "); jobKeys = sched.getJobKeys(GroupMatcher.jobGroupEquals(JobKey.DEFAULT_GROUP)); triggerKeys = sched.getTriggerKeys(GroupMatcher.triggerGroupEquals(TriggerKey.DEFAULT_GROUP)); assertEquals(1, jobKeys.size(), "Number of jobs expected in default group was 1 "); // job should have been left in place, because it is non-durable assertEquals(0, triggerKeys.size(), "Number of triggers expected in default group was 0 "); sched.shutdown(true); } @Test void testDurableStorageFunctions() throws Exception { Scheduler sched = createScheduler("testDurableStorageFunctions", 2); try { // test basic storage functions of scheduler... JobDetail job = newJob() .ofType(TestJob.class) .withIdentity("j1") .storeDurably() .build(); assertFalse(sched.checkExists(jobKey("j1")), "Unexpected existence of job named 'j1'."); sched.addJob(job, false); assertTrue(sched.checkExists(jobKey("j1")), "Unexpected non-existence of job named 'j1'."); JobDetail nonDurableJob = newJob() .ofType(TestJob.class) .withIdentity("j2") .build(); try { sched.addJob(nonDurableJob, false); fail("Storage of non-durable job should not have succeeded."); } catch(SchedulerException expected) { assertFalse(sched.checkExists(jobKey("j2")), "Unexpected existence of job named 'j2'."); } sched.addJob(nonDurableJob, false, true); assertTrue(sched.checkExists(jobKey("j2")), "Unexpected non-existence of job named 'j2'."); } finally { sched.shutdown(true); } } @Test void testShutdownWithSleepReturnsAfterAllThreadsAreStopped() throws Exception { Map allThreadsStart = Thread.getAllStackTraces(); int threadPoolSize = 5; Scheduler scheduler = createScheduler("testShutdownWithSleepReturnsAfterAllThreadsAreStopped", threadPoolSize); Thread.sleep(500L); Map allThreadsRunning = Thread.getAllStackTraces(); scheduler.shutdown( true ); Thread.sleep(200L); Map allThreadsEnd = Thread.getAllStackTraces(); Set endingThreads = new HashSet(allThreadsEnd.keySet()); // remove all preexisting threads from the set for(Thread t: allThreadsStart.keySet()) { allThreadsEnd.remove(t); } // remove threads that are known artifacts of the test for(Thread t: endingThreads) { if(t.getName().contains("derby") && t.getThreadGroup().getName().contains("derby")) { allThreadsEnd.remove(t); } if(t.getThreadGroup() != null && t.getThreadGroup().getName().equals("system")) { allThreadsEnd.remove(t); } if(t.getThreadGroup() != null && t.getThreadGroup().getName().equals("main")) { allThreadsEnd.remove(t); } } if(!allThreadsEnd.isEmpty()) { // log the additional threads for(Thread t: allThreadsEnd.keySet()) { System.out.println("*** Found additional thread: " + t.getName() + " (of type " + t.getClass().getName() +") in group: " + t.getThreadGroup().getName() + " with parent group: " + (t.getThreadGroup().getParent() == null ? "-none-" : t.getThreadGroup().getParent().getName())); } // log all threads that were running before shutdown for(Thread t: allThreadsRunning.keySet()) { System.out.println("- Test runtime thread: " + t.getName() + " (of type " + t.getClass().getName() +") in group: " + (t.getThreadGroup() == null ? "-none-" : (t.getThreadGroup().getName() + " with parent group: " + (t.getThreadGroup().getParent() == null ? "-none-" : t.getThreadGroup().getParent().getName())))); } } assertEquals(0, allThreadsEnd.size(), "Found unexpected new threads (see console output for listing)"); } @Test void testAbilityToFireImmediatelyWhenStartedBefore() throws Exception { List jobExecTimestamps = Collections.synchronizedList(new ArrayList()); CyclicBarrier barrier = new CyclicBarrier(2); Scheduler sched = createScheduler("testAbilityToFireImmediatelyWhenStartedBefore", 5); sched.getContext().put(BARRIER, barrier); sched.getContext().put(DATE_STAMPS, jobExecTimestamps); sched.start(); Thread.yield(); JobDetail job1 = JobBuilder.newJob(TestJobWithSync.class).withIdentity("job1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().forJob(job1).build(); long sTime = System.currentTimeMillis(); sched.scheduleJob(job1, trigger1); barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); sched.shutdown(true); long fTime = jobExecTimestamps.get(0); assertTrue((fTime - sTime < 7000L), "Immediate trigger did not fire within a reasonable amount of time."); // This is dangerously subjective! but what else to do? } @Test void testAbilityToFireImmediatelyWhenStartedBeforeWithTriggerJob() throws Exception { List jobExecTimestamps = Collections.synchronizedList(new ArrayList()); CyclicBarrier barrier = new CyclicBarrier(2); Scheduler sched = createScheduler("testAbilityToFireImmediatelyWhenStartedBeforeWithTriggerJob", 5); sched.getContext().put(BARRIER, barrier); sched.getContext().put(DATE_STAMPS, jobExecTimestamps); sched.start(); Thread.yield(); JobDetail job1 = JobBuilder.newJob(TestJobWithSync.class).withIdentity("job1").storeDurably().build(); sched.addJob(job1, false); long sTime = System.currentTimeMillis(); sched.triggerJob(job1.getKey()); barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); sched.shutdown(true); long fTime = jobExecTimestamps.get(0); assertTrue((fTime - sTime < 7000L), "Immediate trigger did not fire within a reasonable amount of time."); // This is dangerously subjective! but what else to do? } @Test void testAbilityToFireImmediatelyWhenStartedAfter() throws Exception { List jobExecTimestamps = Collections.synchronizedList(new ArrayList()); CyclicBarrier barrier = new CyclicBarrier(2); Scheduler sched = createScheduler("testAbilityToFireImmediatelyWhenStartedAfter", 5); sched.getContext().put(BARRIER, barrier); sched.getContext().put(DATE_STAMPS, jobExecTimestamps); JobDetail job1 = JobBuilder.newJob(TestJobWithSync.class).withIdentity("job1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().forJob(job1).build(); long sTime = System.currentTimeMillis(); sched.scheduleJob(job1, trigger1); sched.start(); barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); sched.shutdown(true); long fTime = jobExecTimestamps.get(0); assertTrue((fTime - sTime < 7000L), "Immediate trigger did not fire within a reasonable amount of time."); // This is dangerously subjective! but what else to do? } @Test void testScheduleMultipleTriggersForAJob() throws SchedulerException { JobDetail job = newJob(TestJob.class).withIdentity("job1", "group1").build(); Trigger trigger1 = newTrigger() .withIdentity("trigger1", "group1") .startNow() .withSchedule( SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1) .repeatForever()) .build(); Trigger trigger2 = newTrigger() .withIdentity("trigger2", "group1") .startNow() .withSchedule( SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1) .repeatForever()) .build(); Set triggersForJob = new HashSet(); triggersForJob.add(trigger1); triggersForJob.add(trigger2); Scheduler sched = createScheduler("testScheduleMultipleTriggersForAJob", 5); sched.scheduleJob(job,triggersForJob, true); List triggersOfJob = sched.getTriggersOfJob(job.getKey()); assertEquals(2,triggersOfJob.size()); assertTrue(triggersOfJob.contains(trigger1)); assertTrue(triggersOfJob.contains(trigger2)); sched.shutdown(true); } @Test void testShutdownWithoutWaitIsUnclean() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); Scheduler scheduler = createScheduler("testShutdownWithoutWaitIsUnclean", 8); try { scheduler.getContext().put(BARRIER, barrier); scheduler.start(); scheduler.addJob(newJob().ofType(UncleanShutdownJob.class).withIdentity("job").storeDurably().build(), false); scheduler.scheduleJob(newTrigger().forJob("job").startNow().build()); while (scheduler.getCurrentlyExecutingJobs().isEmpty()) { Thread.sleep(50); } } finally { scheduler.shutdown(false); } barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); Thread jobThread = (Thread) scheduler.getContext().get(JOB_THREAD); jobThread.join(TimeUnit.SECONDS.toMillis(TEST_TIMEOUT_SECONDS)); } public static class UncleanShutdownJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { try { SchedulerContext schedulerContext = context.getScheduler().getContext(); schedulerContext.put(JOB_THREAD, Thread.currentThread()); CyclicBarrier barrier = (CyclicBarrier) schedulerContext.get(BARRIER); barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (Throwable e) { e.printStackTrace(); throw new AssertionError("Await on barrier was interrupted: " + e.toString()); } } } @Test void testShutdownWithWaitIsClean() throws Exception { final AtomicBoolean shutdown = new AtomicBoolean(false); List jobExecTimestamps = Collections.synchronizedList(new ArrayList()); CyclicBarrier barrier = new CyclicBarrier(2); final Scheduler scheduler = createScheduler("testShutdownWithWaitIsClean", 8); try { scheduler.getContext().put(BARRIER, barrier); scheduler.getContext().put(DATE_STAMPS, jobExecTimestamps); scheduler.start(); scheduler.addJob(newJob().ofType(TestJobWithSync.class).withIdentity("job").storeDurably().build(), false); scheduler.scheduleJob(newTrigger().forJob("job").startNow().build()); while (scheduler.getCurrentlyExecutingJobs().isEmpty()) { Thread.sleep(50); } } finally { Thread t = new Thread() { @Override public void run() { try { scheduler.shutdown(true); shutdown.set(true); } catch (SchedulerException ex) { throw new RuntimeException(ex); } } }; t.start(); Thread.sleep(1000); assertFalse(shutdown.get()); barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); t.join(); } } } ================================================ FILE: quartz/src/test/java/org/quartz/AnnualCalendarTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; import org.junit.jupiter.api.Test; import org.quartz.impl.calendar.AnnualCalendar; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for AnnualCalendar serialization backwards compatibility. */ public class AnnualCalendarTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"1.5.1"}; private static final TimeZone EST_TIME_ZONE = TimeZone.getTimeZone("America/New_York"); /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ @Override protected Object getTargetObject() { AnnualCalendar c = new AnnualCalendar(); c.setDescription("description"); Calendar cal = Calendar.getInstance(EST_TIME_ZONE, Locale.US); cal.clear(); cal.set(2005, Calendar.JANUARY, 20, 10, 5, 15); c.setDayExcluded(cal, true); return c; } /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ @Override protected String[] getVersions() { return VERSIONS; } /** * Verify that the target object and the object we just deserialized * match. */ @Override protected void verifyMatch(Object target, Object deserialized) { AnnualCalendar targetCalendar = (AnnualCalendar)target; AnnualCalendar deserializedCalendar = (AnnualCalendar)deserialized; assertNotNull(deserializedCalendar); assertEquals(targetCalendar.getDescription(), deserializedCalendar.getDescription()); assertEquals(targetCalendar.getDaysExcluded(), deserializedCalendar.getDaysExcluded()); assertNull(deserializedCalendar.getTimeZone()); } /** * Tests if method setDaysExcluded protects the property daysExcluded against nulling. * See: QUARTZ-590 */ @Test void testDaysExcluded() { AnnualCalendar annualCalendar = new AnnualCalendar(); annualCalendar.setDaysExcluded(null); assertNotNull(annualCalendar.getDaysExcluded(),"Annual calendar daysExcluded property should have been set to empty ArrayList, not null."); } /** * Tests the parameter exclude in a method setDaysExcluded * of class org.quartz.impl.calendar.AnnualCalendar */ @Test void testExclude() { AnnualCalendar annualCalendar = new AnnualCalendar(); Calendar day = Calendar.getInstance(); day.set(Calendar.MONTH, 9); day.set(Calendar.DAY_OF_MONTH, 15); annualCalendar.setDayExcluded(day, false); assertFalse(annualCalendar.isDayExcluded(day), "The day 15 October is not expected to be excluded but it is"); day.set(Calendar.MONTH, 9); day.set(Calendar.DAY_OF_MONTH, 15); annualCalendar.setDayExcluded((Calendar) day.clone(), true); day.set(Calendar.MONTH, 10); day.set(Calendar.DAY_OF_MONTH, 12); annualCalendar.setDayExcluded((Calendar) day.clone(), true); day.set(Calendar.MONTH, 8); day.set(Calendar.DAY_OF_MONTH, 1); annualCalendar.setDayExcluded((Calendar) day.clone(), true); assertTrue(annualCalendar.isDayExcluded(day), "The day 15 October is expected to be excluded but it is not"); day.set(Calendar.MONTH, 9); day.set(Calendar.DAY_OF_MONTH, 15); annualCalendar.setDayExcluded((Calendar) day.clone(), false); assertFalse(annualCalendar.isDayExcluded(day), "The day 15 October is not expected to be excluded but it is"); } /** * QUARTZ-679 Test if the annualCalendar works over years */ @Test void testDaysExcludedOverTime() { AnnualCalendar annualCalendar = new AnnualCalendar(); Calendar day = Calendar.getInstance(); day.set(Calendar.MONTH, Calendar.JUNE); day.set(Calendar.YEAR, 2005); day.set(Calendar.DAY_OF_MONTH, 23); annualCalendar.setDayExcluded((Calendar) day.clone(), true); day.set(Calendar.YEAR, 2008); day.set(Calendar.MONTH, Calendar.FEBRUARY); day.set(Calendar.DAY_OF_MONTH, 1); annualCalendar.setDayExcluded((Calendar) day.clone(), true); assertTrue(annualCalendar.isDayExcluded(day), "The day 1 February is expected to be excluded but it is not"); } /** * Part 2 of the tests of QUARTZ-679 */ @Test void testRemoveInTheFuture() { AnnualCalendar annualCalendar = new AnnualCalendar(); Calendar day = Calendar.getInstance(); day.set(Calendar.MONTH, Calendar.JUNE); day.set(Calendar.YEAR, 2005); day.set(Calendar.DAY_OF_MONTH, 23); annualCalendar.setDayExcluded((Calendar) day.clone(), true); // Trying to remove the 23th of June day.set(Calendar.MONTH, Calendar.JUNE); day.set(Calendar.YEAR, 2008); day.set(Calendar.DAY_OF_MONTH, 23); annualCalendar.setDayExcluded((Calendar) day.clone(), false); assertFalse(annualCalendar.isDayExcluded(day), "The day 23 June is not expected to be excluded but it is"); } } ================================================ FILE: quartz/src/test/java/org/quartz/CalendarIntervalTriggerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.TimeZone; import org.junit.jupiter.api.Test; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.calendar.BaseCalendar; import org.quartz.impl.triggers.CalendarIntervalTriggerImpl; /** * Unit tests for DateIntervalTrigger. */ public class CalendarIntervalTriggerTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"2.0"}; @Test void testQTZ331FireTimeAfterBoundary() { Calendar start = Calendar.getInstance(); start.clear(); start.set(2013, Calendar.FEBRUARY, 15); Date startTime = start.getTime(); start.add(Calendar.DAY_OF_MONTH, 1); Date triggerTime = start.getTime(); CalendarIntervalTriggerImpl trigger = new CalendarIntervalTriggerImpl("test", startTime, null, IntervalUnit.DAY, 1); assertThat(trigger.getFireTimeAfter(startTime), equalTo(triggerTime)); Date after = new Date(start.getTimeInMillis() - 500); assertThat(trigger.getFireTimeAfter(after), equalTo(triggerTime)); } void testQTZ330DaylightSavingsCornerCase() { TimeZone edt = TimeZone.getTimeZone("America/New_York"); Calendar start = Calendar.getInstance(); start.clear(); start.setTimeZone(edt); start.set(2012, Calendar.MARCH, 16, 2, 30, 0); Calendar after = Calendar.getInstance(); after.clear(); after.setTimeZone(edt); after.set(2013, Calendar.APRIL, 19, 2, 30, 0); BaseCalendar baseCalendar = new BaseCalendar(edt); CalendarIntervalTriggerImpl intervalTrigger = new CalendarIntervalTriggerImpl("QTZ-330", start.getTime(), null, DateBuilder.IntervalUnit.DAY, 1); intervalTrigger.setTimeZone(edt); intervalTrigger.setPreserveHourOfDayAcrossDaylightSavings(true); intervalTrigger.computeFirstFireTime(baseCalendar); Date fireTime = intervalTrigger.getFireTimeAfter(after.getTime()); assertThat(fireTime.after(after.getTime()), is(true)); } @Test void testYearlyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl yearlyTrigger = new CalendarIntervalTriggerImpl(); yearlyTrigger.setStartTime(startCalendar.getTime()); yearlyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.YEAR); yearlyTrigger.setRepeatInterval(2); // every two years; Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2009, Calendar.JUNE, 1, 9, 30, 17); // jump 4 years (2 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(yearlyTrigger, null, 4); Date secondTime = fireTimes.get(2); // get the third fire time assertEquals(targetCalendar.getTime(), secondTime, "Year increment result not as expected."); } @Test void testMonthlyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl yearlyTrigger = new CalendarIntervalTriggerImpl(); yearlyTrigger.setStartTime(startCalendar.getTime()); yearlyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MONTH); yearlyTrigger.setRepeatInterval(5); // every five months Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); targetCalendar.setLenient(true); targetCalendar.add(Calendar.MONTH, 25); // jump 25 five months (5 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(yearlyTrigger, null, 6); Date fifthTime = fireTimes.get(5); // get the sixth fire time assertEquals(targetCalendar.getTime(), fifthTime, "Month increment result not as expected."); } @Test void testWeeklyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl yearlyTrigger = new CalendarIntervalTriggerImpl(); yearlyTrigger.setStartTime(startCalendar.getTime()); yearlyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.WEEK); yearlyTrigger.setRepeatInterval(6); // every six weeks Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 7 * 6 * 4); // jump 24 weeks (4 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(yearlyTrigger, null, 7); Date fifthTime = fireTimes.get(4); // get the fifth fire time assertEquals(targetCalendar.getTime(), fifthTime, "Week increment result not as expected."); } @Test void testDailyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(90); // every ninety days Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 360); // jump 360 days (4 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); Date fifthTime = fireTimes.get(4); // get the fifth fire time assertEquals(targetCalendar.getTime(), fifthTime, "Day increment result not as expected."); } @Test void testHourlyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl yearlyTrigger = new CalendarIntervalTriggerImpl(); yearlyTrigger.setStartTime(startCalendar.getTime()); yearlyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.HOUR); yearlyTrigger.setRepeatInterval(100); // every 100 hours Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); targetCalendar.setLenient(true); targetCalendar.add(Calendar.HOUR, 400); // jump 400 hours (4 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(yearlyTrigger, null, 6); Date fifthTime = fireTimes.get(4); // get the fifth fire time assertEquals(targetCalendar.getTime(), fifthTime, "Hour increment result not as expected."); } @Test void testMinutelyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl yearlyTrigger = new CalendarIntervalTriggerImpl(); yearlyTrigger.setStartTime(startCalendar.getTime()); yearlyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); yearlyTrigger.setRepeatInterval(100); // every 100 minutes Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); targetCalendar.setLenient(true); targetCalendar.add(Calendar.MINUTE, 400); // jump 400 minutes (4 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(yearlyTrigger, null, 6); Date fifthTime = fireTimes.get(4); // get the fifth fire time assertEquals(targetCalendar.getTime(), fifthTime, "Minutes increment result not as expected."); } @Test void testSecondlyIntervalGetFireTimeAfter() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl yearlyTrigger = new CalendarIntervalTriggerImpl(); yearlyTrigger.setStartTime(startCalendar.getTime()); yearlyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.SECOND); yearlyTrigger.setRepeatInterval(100); // every 100 seconds Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(2005, Calendar.JUNE, 1, 9, 30, 17); targetCalendar.setLenient(true); targetCalendar.add(Calendar.SECOND, 400); // jump 400 seconds (4 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(yearlyTrigger, null, 6); Date fifthTime = fireTimes.get(4); // get the third fire time assertEquals(targetCalendar.getTime(), fifthTime, "Seconds increment result not as expected."); } @Test void testDaylightSavingsTransitions() { // Pick a day before a spring daylight savings transition... Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2010, Calendar.MARCH, 12, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(5); // every 5 days Calendar targetCalendar = Calendar.getInstance(); targetCalendar.setTime(startCalendar.getTime()); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 10); // jump 10 days (2 intervals) targetCalendar.clear(Calendar.MILLISECOND); List fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); Date testTime = fireTimes.get(2); // get the third fire time assertEquals(targetCalendar.getTime(), testTime, "Day increment result not as expected over spring 2010 daylight savings transition."); // And again, Pick a day before a spring daylight savings transition... (QTZ-240) startCalendar = Calendar.getInstance(); startCalendar.set(2011, Calendar.MARCH, 12, 1, 0, 0); startCalendar.clear(Calendar.MILLISECOND); dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(1); // every day targetCalendar = Calendar.getInstance(); targetCalendar.setTime(startCalendar.getTime()); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 2); // jump 2 days (2 intervals) targetCalendar.clear(Calendar.MILLISECOND); fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); testTime = fireTimes.get(2); // get the third fire time assertEquals(targetCalendar.getTime(), testTime, "Day increment result not as expected over spring 2011 daylight savings transition."); // And again, Pick a day before a spring daylight savings transition... (QTZ-240) - and prove time of day is not preserved without setPreserveHourOfDayAcrossDaylightSavings(true) startCalendar = Calendar.getInstance(); startCalendar.setTimeZone(TimeZone.getTimeZone("CET")); startCalendar.set(2011, Calendar.MARCH, 26, 4, 0, 0); startCalendar.clear(Calendar.MILLISECOND); dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(1); // every day dailyTrigger.setTimeZone(TimeZone.getTimeZone("EST")); targetCalendar = Calendar.getInstance(); targetCalendar.setTimeZone(TimeZone.getTimeZone("CET")); targetCalendar.setTime(startCalendar.getTime()); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 2); // jump 2 days (2 intervals) targetCalendar.clear(Calendar.MILLISECOND); fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); testTime = fireTimes.get(2); // get the third fire time Calendar testCal = Calendar.getInstance(TimeZone.getTimeZone("CET")); testCal.setTimeInMillis(testTime.getTime()); assertNotEquals(targetCalendar.get(Calendar.HOUR_OF_DAY), testCal.get(Calendar.HOUR_OF_DAY), "Day increment time-of-day result not as expected over spring 2011 daylight savings transition."); // And again, Pick a day before a spring daylight savings transition... (QTZ-240) - and prove time of day is preserved with setPreserveHourOfDayAcrossDaylightSavings(true) startCalendar = Calendar.getInstance(); startCalendar.setTimeZone(TimeZone.getTimeZone("CET")); startCalendar.set(2011, Calendar.MARCH, 26, 4, 0, 0); startCalendar.clear(Calendar.MILLISECOND); dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(1); // every day dailyTrigger.setTimeZone(TimeZone.getTimeZone("CET")); dailyTrigger.setPreserveHourOfDayAcrossDaylightSavings(true); targetCalendar = Calendar.getInstance(); targetCalendar.setTimeZone(TimeZone.getTimeZone("CET")); targetCalendar.setTime(startCalendar.getTime()); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 2); // jump 2 days (2 intervals) targetCalendar.clear(Calendar.MILLISECOND); fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); testTime = fireTimes.get(2); // get the third fire time testCal = Calendar.getInstance(TimeZone.getTimeZone("CET")); testCal.setTimeInMillis(testTime.getTime()); assertEquals(targetCalendar.get(Calendar.HOUR_OF_DAY), testCal.get(Calendar.HOUR_OF_DAY), "Day increment time-of-day result not as expected over spring 2011 daylight savings transition."); // Pick a day before a fall daylight savings transition... startCalendar = Calendar.getInstance(); startCalendar.set(2010, Calendar.OCTOBER, 31, 9, 30, 17); startCalendar.clear(Calendar.MILLISECOND); dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(5); // every 5 days targetCalendar = Calendar.getInstance(); targetCalendar.setTime(startCalendar.getTime()); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 15); // jump 15 days (3 intervals) targetCalendar.clear(Calendar.MILLISECOND); fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); testTime = (Date) fireTimes.get(3); // get the fourth fire time assertEquals(targetCalendar.getTime(), testTime, "Day increment result not as expected over fall 2010 daylight savings transition."); // And again, Pick a day before a fall daylight savings transition... (QTZ-240) startCalendar = Calendar.getInstance(); startCalendar.setTimeZone(TimeZone.getTimeZone("CEST")); startCalendar.set(2011, Calendar.OCTOBER, 29, 1, 30, 00); startCalendar.clear(Calendar.MILLISECOND); dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(1); // every day dailyTrigger.setTimeZone(TimeZone.getTimeZone("EST")); targetCalendar = Calendar.getInstance(); targetCalendar.setTimeZone(TimeZone.getTimeZone("CEST")); targetCalendar.setTime(startCalendar.getTime()); targetCalendar.setLenient(true); targetCalendar.add(Calendar.DAY_OF_YEAR, 3); // jump 3 days (3 intervals) targetCalendar.clear(Calendar.MILLISECOND); fireTimes = TriggerUtils.computeFireTimes(dailyTrigger, null, 6); testTime = (Date) fireTimes.get(3); // get the fourth fire time assertEquals(targetCalendar.getTime(), testTime, "Day increment result not as expected over fall 2011 daylight savings transition."); } @Test void testFinalFireTimes() { Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2010, Calendar.MARCH, 12, 9, 0, 0); startCalendar.clear(Calendar.MILLISECOND); CalendarIntervalTriggerImpl dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.DAY); dailyTrigger.setRepeatInterval(5); // every 5 days Calendar endCalendar = Calendar.getInstance(); endCalendar.setTime(startCalendar.getTime()); endCalendar.setLenient(true); endCalendar.add(Calendar.DAY_OF_YEAR, 10); // jump 10 days (2 intervals) endCalendar.clear(Calendar.MILLISECOND); dailyTrigger.setEndTime(endCalendar.getTime()); Date testTime = dailyTrigger.getFinalFireTime(); assertEquals(endCalendar.getTime(), testTime, "Final fire time not computed correctly for day interval."); startCalendar = Calendar.getInstance(); startCalendar.set(2010, Calendar.MARCH, 12, 9, 0, 0); startCalendar.clear(Calendar.MILLISECOND); dailyTrigger = new CalendarIntervalTriggerImpl(); dailyTrigger.setStartTime(startCalendar.getTime()); dailyTrigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); dailyTrigger.setRepeatInterval(5); // every 5 minutes endCalendar = Calendar.getInstance(); endCalendar.setTime(startCalendar.getTime()); endCalendar.setLenient(true); endCalendar.add(Calendar.DAY_OF_YEAR, 15); // jump 15 days endCalendar.add(Calendar.MINUTE,-2); // back up two minutes endCalendar.clear(Calendar.MILLISECOND); dailyTrigger.setEndTime(endCalendar.getTime()); testTime = dailyTrigger.getFinalFireTime(); assertTrue((endCalendar.getTime().after(testTime)), "Final fire time not computed correctly for minutely interval."); endCalendar.add(Calendar.MINUTE,-3); // back up three more minutes assertEquals(endCalendar.getTime(), testTime, "Final fire time not computed correctly for minutely interval."); } @Test void testMisfireInstructionValidity() throws ParseException { CalendarIntervalTriggerImpl trigger = new CalendarIntervalTriggerImpl(); try { trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY); trigger.setMisfireInstruction(CalendarIntervalTriggerImpl.MISFIRE_INSTRUCTION_DO_NOTHING); trigger.setMisfireInstruction(CalendarIntervalTriggerImpl.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); } catch(Exception e) { fail("Unexpected exception while setting misfire instruction."); } try { trigger.setMisfireInstruction(CalendarIntervalTriggerImpl.MISFIRE_INSTRUCTION_DO_NOTHING + 1); fail("Expected exception while setting invalid misfire instruction but did not get it."); } catch(Exception e) { } } @Override protected Object getTargetObject() throws Exception { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("A", "B"); CalendarIntervalTriggerImpl t = new CalendarIntervalTriggerImpl(); t.setName("test"); t.setGroup("testGroup"); t.setCalendarName("MyCalendar"); t.setDescription("CronTriggerDesc"); t.setJobDataMap(jobDataMap); t.setRepeatInterval(5); t.setRepeatIntervalUnit(IntervalUnit.DAY); return t; } @Override protected String[] getVersions() { return VERSIONS; } @Override protected void verifyMatch(Object target, Object deserialized) { CalendarIntervalTriggerImpl targetCalTrigger = (CalendarIntervalTriggerImpl)target; CalendarIntervalTriggerImpl deserializedCalTrigger = (CalendarIntervalTriggerImpl)deserialized; assertNotNull(deserializedCalTrigger); assertEquals(targetCalTrigger.getName(), deserializedCalTrigger.getName()); assertEquals(targetCalTrigger.getGroup(), deserializedCalTrigger.getGroup()); assertEquals(targetCalTrigger.getJobName(), deserializedCalTrigger.getJobName()); assertEquals(targetCalTrigger.getJobGroup(), deserializedCalTrigger.getJobGroup()); // assertEquals(targetCronTrigger.getStartTime(), deserializedCronTrigger.getStartTime()); assertEquals(targetCalTrigger.getEndTime(), deserializedCalTrigger.getEndTime()); assertEquals(targetCalTrigger.getCalendarName(), deserializedCalTrigger.getCalendarName()); assertEquals(targetCalTrigger.getDescription(), deserializedCalTrigger.getDescription()); assertEquals(targetCalTrigger.getJobDataMap(), deserializedCalTrigger.getJobDataMap()); assertEquals(targetCalTrigger.getRepeatInterval(), deserializedCalTrigger.getRepeatInterval()); assertEquals(targetCalTrigger.getRepeatIntervalUnit(), deserializedCalTrigger.getRepeatIntervalUnit()); } // execute with version number to generate a new version's serialized form public static void main(String[] args) throws Exception { new CalendarIntervalTriggerTest().writeJobDataFile("2.0"); } } ================================================ FILE: quartz/src/test/java/org/quartz/CronExpressionTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.junit.jupiter.api.Test; import java.io.*; import java.text.ParseException; import java.util.*; import java.util.Calendar; import static org.junit.jupiter.api.Assertions.*; public class CronExpressionTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"1.5.2"}; private static final TimeZone EST_TIME_ZONE = TimeZone.getTimeZone("US/Eastern"); /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ @Override protected Object getTargetObject() throws ParseException { CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005"); cronExpression.setTimeZone(EST_TIME_ZONE); return cronExpression; } /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ @Override protected String[] getVersions() { return VERSIONS; } /** * Verify that the target object and the object we just deserialized * match. */ @Override protected void verifyMatch(Object target, Object deserialized) { CronExpression targetCronExpression = (CronExpression)target; CronExpression deserializedCronExpression = (CronExpression)deserialized; assertNotNull(deserializedCronExpression); assertEquals(targetCronExpression.getCronExpression(), deserializedCronExpression.getCronExpression()); assertEquals(targetCronExpression.getTimeZone(), deserializedCronExpression.getTimeZone()); } void testTooManyTokens() throws Exception { try { new CronExpression("0 15 10 * * ? 2005 *"); // too many tokens/terms in expression fail("Expected ParseException did not occur for invalid expression"); } catch(ParseException pe) { assertTrue(pe.getMessage().contains("too many"), "Incorrect ParseException thrown"); } } /* * Test method for 'org.quartz.CronExpression.isSatisfiedBy(Date)'. */ @Test void testIsSatisfiedBy() throws Exception { CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005"); Calendar cal = Calendar.getInstance(); cal.set(2005, Calendar.JUNE, 1, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(Calendar.DAY_OF_MONTH, 30); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(Calendar.YEAR, 2006); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.JUNE, 1, 10, 16, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.JUNE, 1, 10, 14, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test for March cal = Calendar.getInstance(); cal.set(2005, Calendar.MARCH, 1, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(Calendar.DAY_OF_MONTH, 31); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.MARCH, 1, 10, 16, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.MARCH, 1, 10, 14, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test for February cal = Calendar.getInstance(); cal.set(2005, Calendar.FEBRUARY, 1, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(Calendar.DAY_OF_MONTH, 28); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.FEBRUARY, 1, 10, 16, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.FEBRUARY, 1, 10, 14, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test specific day of month cronExpression = new CronExpression("0 15 10 12 * ? 2005"); cal.set(2005, Calendar.DECEMBER, 12, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.DECEMBER, 11, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.DECEMBER, 13, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); } @Test void testIsSatisfiedByLastDayOfMonth() throws Exception { Calendar cal = Calendar.getInstance(); // Test months with 31 days CronExpression cronExpression = new CronExpression("0 15 10 L * ? 2005"); cal.set(2005, Calendar.DECEMBER, 31, 10, 15, 0); // December has 31 days assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.DECEMBER, 30, 10, 15, 0); // Not the last day assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test months with 30 days cronExpression = new CronExpression("0 15 10 L * ? 2005"); cal.set(2005, Calendar.SEPTEMBER, 30, 10, 15, 0); // September has 30 days assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.SEPTEMBER, 29, 10, 15, 0); // Not the last day assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test February (non-leap year) cronExpression = new CronExpression("0 15 10 L 2 ? 2005"); cal.set(2005, Calendar.FEBRUARY, 28, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.FEBRUARY, 27, 10, 15, 0); // Not the last day assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test February (leap year) cronExpression = new CronExpression("0 15 10 L 2 ? 2004"); cal.set(2004, Calendar.FEBRUARY, 29, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2004, Calendar.FEBRUARY, 28, 10, 15, 0); // Not the last day assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); } @Test void testIsSatisfiedByLastDayOfMonthWithOffset() throws Exception { Calendar cal = Calendar.getInstance(); // Test months with 31 days CronExpression cronExpression = new CronExpression("0 15 10 L-2 * ? 2005"); cal.set(2005, Calendar.DECEMBER, 29, 10, 15, 0); // December has 31 days, L-2 = 29th assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.DECEMBER, 28, 10, 15, 0); // Not L-2 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.DECEMBER, 30, 10, 15, 0); // Not L-2 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.DECEMBER, 31, 10, 15, 0); // Not L-2 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test months with 30 days cronExpression = new CronExpression("0 15 10 L-1 * ? 2005"); cal.set(2005, Calendar.SEPTEMBER, 29, 10, 15, 0); // September has 30 days, L-1 = 29th assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.SEPTEMBER, 27, 10, 15, 0); // Not L-1 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.SEPTEMBER, 28, 10, 15, 0); // Not L-1 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.SEPTEMBER, 30, 10, 15, 0); // Not L-1 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test February (non-leap year) cronExpression = new CronExpression("0 15 10 L-3 2 ? 2005"); cal.set(2005, Calendar.FEBRUARY, 25, 10, 15, 0); // February has 28 days in 2005, L-3 = 25th assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.FEBRUARY, 24, 10, 15, 0); // Not L-3 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.FEBRUARY, 26, 10, 15, 0); // Not L-3 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2005, Calendar.FEBRUARY, 28, 10, 15, 0); // Not L-3 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); // Test February (leap year) cronExpression = new CronExpression("0 15 10 L-3 2 ? 2000"); cal.set(2000, Calendar.FEBRUARY, 26, 10, 15, 0); // February has 29 days in 2000, L-3 = 26th assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2000, Calendar.FEBRUARY, 25, 10, 15, 0); // Not L-3 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2000, Calendar.FEBRUARY, 27, 10, 15, 0); // Not L-3 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2000, Calendar.FEBRUARY, 29, 10, 15, 0); // Not L-3 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); } @Test void testLastDayOffset() throws Exception { CronExpression cronExpression = new CronExpression("0 15 10 L-2 * ? 2010"); Calendar cal = Calendar.getInstance(); cal.set(2010, Calendar.OCTOBER, 29, 10, 15, 0); // last day - 2 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 28, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 26, 10, 15, 0); // last day - 2 for February assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 25, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.JUNE, 28, 10, 15, 0); // last day - 2 for June assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.JUNE, 27, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-5W * ? 2010"); cal.set(2010, Calendar.OCTOBER, 26, 10, 15, 0); // last day - 5 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 25, 10, 15, 0); // not last day - 5 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 27, 10, 15, 0); // not last day - 5 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.SEPTEMBER, 24, 10, 15, 0); // last day - 5 (September has 30 days) assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.SEPTEMBER, 23, 10, 15, 0); // not last day - 5 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.SEPTEMBER, 25, 10, 15, 0); // not last day - 5 assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-1 * ? 2010"); cal.set(2010, Calendar.OCTOBER, 30, 10, 15, 0); // last day - 1 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-1W * ? 2010"); cal.set(2010, Calendar.OCTOBER, 29, 10, 15, 0); // nearest weekday to last day - 1 (29th is a friday in 2010) assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 1,L * ? 2010"); cal.set(2010, Calendar.OCTOBER, 1, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 31, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 30, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 1, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 28, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 27, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-1W,L-1 * ? 2010"); cal.set(2010, Calendar.OCTOBER, 29, 10, 15, 0); // nearest weekday to last day - 1 (29th is a friday in 2010) assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 30, 10, 15, 0); // last day - 1 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 26, 10, 15, 0); // nearest weekday to last day - 1 (26th is a friday in 2010) assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.FEBRUARY, 27, 10, 15, 0); // last day - 1 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 2W,16 * ? 2010"); cal.set(2010, Calendar.OCTOBER, 1, 10, 15, 0); // nearest weekday to the 2nd of the month (1st is a friday in 2010) assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 2, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 16, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.NOVEMBER, 2, 10, 15, 0); // 2nd is a Tuesday in November 2010 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.NOVEMBER, 16, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); } /* * QUARTZ-571: Showing that expressions with months correctly serialize. */ @Test void testQuartz571() throws Exception { CronExpression cronExpression = new CronExpression("19 15 10 4 Apr ? "); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(cronExpression); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); CronExpression newExpression = (CronExpression) ois.readObject(); assertEquals(newExpression.getCronExpression(), cronExpression.getCronExpression()); // if broken, this will throw an exception newExpression.getNextValidTimeAfter(new Date()); } /** * QTZ-259 : last day offset causes repeating fire time * */ @Test void testQtz259() throws Exception { CronScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0 0 0 L-2 * ? *"); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("test").withSchedule(schedBuilder).build(); int i = 0; Date previousDate = trigger.getFireTimeAfter(new Date()); while (++i < 26) { Date date = trigger.getFireTimeAfter(previousDate); System.out.println("fireTime: " + date + ", previousFireTime: " + previousDate); assertNotEquals(previousDate, date, "Next fire time is the same as previous fire time!"); previousDate = date; } } /** * QTZ-259 : last day offset causes repeating fire time * */ @Test void testQtz259LW() throws Exception { CronScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0 0 0 LW * ? *"); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("test").withSchedule(schedBuilder).build(); int i = 0; Date pdate = trigger.getFireTimeAfter(new Date()); while (++i < 26) { Date date = trigger.getFireTimeAfter(pdate); System.out.println("fireTime: " + date + ", previousFireTime: " + pdate); assertNotEquals(pdate, date, "Next fire time is the same as previous fire time!"); pdate = date; } } /* * QUARTZ-574: Showing that storeExpressionVals correctly calculates the month number */ @Test void testQuartz574() { try { new CronExpression("* * * * Foo ? "); fail("Expected ParseException did not fire for nonexistent month"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Invalid Month value:"), "Incorrect ParseException thrown"); } try { new CronExpression("* * * * Jan-Foo ? "); fail("Expected ParseException did not fire for nonexistent month"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Invalid Month value:"), "Incorrect ParseException thrown"); } } @Test void testQuartz621() { try { new CronExpression("0 0 * * * *"); fail("Expected ParseException did not fire for wildcard day-of-month and day-of-week"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."), "Incorrect ParseException thrown"); } try { new CronExpression("0 0 * 4 * *"); fail("Expected ParseException did not fire for specified day-of-month and wildcard day-of-week"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."), "Incorrect ParseException thrown"); } try { new CronExpression("0 0 * * * 4"); fail("Expected ParseException did not fire for wildcard day-of-month and specified day-of-week"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."), "Incorrect ParseException thrown"); } } @Test void testQuartz640() throws ParseException { try { new CronExpression("0 43 9 ? * SAT,SUN,L"); fail("Expected ParseException did not fire for L combined with other days of the week"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Support for specifying 'L' with other days of the week is not implemented"), "Incorrect ParseException thrown"); } try { new CronExpression("0 43 9 ? * 6,7,L"); fail("Expected ParseException did not fire for L combined with other days of the week"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("Support for specifying 'L' with other days of the week is not implemented"), "Incorrect ParseException thrown"); } try { new CronExpression("0 43 9 ? * 5L"); } catch(ParseException pe) { fail("Unexpected ParseException thrown for supported '5L' expression."); } } @Test void testQtz96() throws ParseException { try { new CronExpression("0/5 * * 32W 1 ?"); fail("Expected ParseException did not fire for W with value larger than 31"); } catch(ParseException pe) { assertTrue(pe.getMessage().startsWith("The 'W' option does not make sense with values larger than"), "Incorrect ParseException thrown"); } } void testQtz395_CopyConstructorMustPreserveTimeZone () throws ParseException { TimeZone nonDefault = TimeZone.getTimeZone("Europe/Brussels"); if (nonDefault.equals(TimeZone.getDefault())) { nonDefault = EST_TIME_ZONE; } CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005"); cronExpression.setTimeZone(nonDefault); CronExpression copyCronExpression = new CronExpression(cronExpression); assertEquals(nonDefault, copyCronExpression.getTimeZone()); } // Issue #58 @Test void testSecRangeIntervalAfterSlash() throws Exception { // Test case 1 try { new CronExpression("/120 0 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 60 : 120", e.getMessage()); } // Test case 2 try { new CronExpression("0/120 0 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in in '0/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 60 : 120", e.getMessage()); } // Test case 3 try { new CronExpression("/ 0 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 4 try { new CronExpression("0/ 0 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '0/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 5 try { new CronExpression("/60 0 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 60 : 60", e.getMessage()); } } // Issue #58 @Test void testMinRangeIntervalAfterSlash() throws Exception { // Test case 1 try { new CronExpression("0 /120 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 60 : 120", e.getMessage()); } // Test case 2 try { new CronExpression("0 0/120 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in in '0/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 60 : 120", e.getMessage()); } // Test case 3 try { new CronExpression("0 / 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 4 try { new CronExpression("0 0/ 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '0/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 5 try { new CronExpression("0 /60 8-18 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 60 : 60", e.getMessage()); } } // Issue #58 @Test void testHourRangeIntervalAfterSlash() throws Exception { // Test case 1 try { new CronExpression("0 0 /120 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 24 : 120", e.getMessage()); } // Test case 2 try { new CronExpression("0 0 0/120 ? * 2-6"); fail("Cron did not validate bad range interval in in '0/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 24 : 120", e.getMessage()); } // Test case 3 try { new CronExpression("0 0 / ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 4 try { new CronExpression("0 0 0/ ? * 2-6"); fail("Cron did not validate bad range interval in '0/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 5 try { new CronExpression("0 0 /24 ? * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 24 : 24", e.getMessage()); } } // Issue #58 @Test void testDayOfMonthRangeIntervalAfterSlash() throws Exception { // Test case 1 try { new CronExpression("0 0 0 /120 * 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 31 : 120", e.getMessage()); } // Test case 2 try { new CronExpression("0 0 0 0/120 * 2-6"); fail("Cron did not validate bad range interval in in '0/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 31 : 120", e.getMessage()); } // Test case 3 try { new CronExpression("0 0 0 / * 2-6"); fail("Cron did not validate bad range interval in '_blank/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 4 try { new CronExpression("0 0 0 0/ * 2-6"); fail("Cron did not validate bad range interval in '0/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } } // Issue #58 @Test void testMonthRangeIntervalAfterSlash() throws Exception { // Test case 1 try { new CronExpression("0 0 0 ? /120 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 12 : 120", e.getMessage()); } // Test case 2 try { new CronExpression("0 0 0 ? 0/120 2-6"); fail("Cron did not validate bad range interval in in '0/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 12 : 120", e.getMessage()); } // Test case 3 try { new CronExpression("0 0 0 ? / 2-6"); fail("Cron did not validate bad range interval in '_blank/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 4 try { new CronExpression("0 0 0 ? 0/ 2-6"); fail("Cron did not validate bad range interval in '0/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 5 try { new CronExpression("0 0 0 ? /13 2-6"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 12 : 13", e.getMessage()); } } // Issue #58 @Test void testDayOfWeekRangeIntervalAfterSlash() throws Exception { // Test case 1 try { new CronExpression("0 0 0 ? * /120"); fail("Cron did not validate bad range interval in '_blank/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 7 : 120", e.getMessage()); } // Test case 2 try { new CronExpression("0 0 0 ? * 0/120"); fail("Cron did not validate bad range interval in in '0/xxx' form"); } catch (ParseException e) { assertEquals("Increment >= 7 : 120", e.getMessage()); } // Test case 3 try { new CronExpression("0 0 0 ? * /"); fail("Cron did not validate bad range interval in '_blank/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } // Test case 4 try { new CronExpression("0 0 0 ? * 0/"); fail("Cron did not validate bad range interval in '0/_blank'"); } catch (ParseException e) { assertEquals("'/' must be followed by an integer.", e.getMessage()); } } @Test public void testGetTimeBefore() throws ParseException { long now = System.currentTimeMillis(); Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(now); int year = cal.get(Calendar.YEAR); Object[][] tests = { { "* * * * * ? *", 1000L }, { "0 * * * * ? *", 60 * 1000L }, { "0/15 * * * * ? *", 15 * 1000L }, { "0 0 5 * * ? *", 24 * 60 * 60 * 1000L }, { "0 0 0 * * ? *", 24 * 60 * 60 * 1000L }, { "0/30 1 2 * * ? *", 24 * 60 * 60 * 1000L - 30000L, 30000L }, { "* * * * * ? " + (year + 2) }, { "* * * * * ? " + (year - 2), 24 * 60 * 60 * 1000L - 30000L, 30000L }, }; for (Object[] test : tests) { String expression = (String)test[0]; long interval1 = test.length > 1 ? (long)test[1] : -1; long interval2 = test.length > 2 ? (long)test[2] : interval1; CronExpression exp = new CronExpression(expression); Date after = exp.getTimeAfter(new Date(now)); if (after == null) { // matches only in the past Date before = exp.getTimeBefore(new Date(now)); assertNotNull(before, "expression " + expression); } else if (interval1 < 0) { // matches only in the future Date before = exp.getTimeBefore(after); assertNull(before, "expression " + expression); } else { // matches at fixed intervals Date before = exp.getTimeBefore(after); Date after2 = exp.getTimeAfter(after); assertEquals(interval1, after.getTime() - before.getTime(), "expression " + expression); assertEquals(interval2, after2.getTime() - after.getTime(), "expression " + expression); } } } @Test public void testIsValidExpression() throws Exception { assertTrue(CronExpression.isValidExpression("* * * * * ?")); assertTrue(CronExpression.isValidExpression("0 * 5 * * ?")); assertTrue(CronExpression.isValidExpression("0 15 10 L-1W,L-1 * ? 2010")); assertFalse(CronExpression.isValidExpression("Ralf 30 * * * ?")); assertFalse(CronExpression.isValidExpression("0 30 Ralf * * ?")); assertFalse(CronExpression.isValidExpression("kilroy was here")); assertFalse(CronExpression.isValidExpression("L 30 * * * ?")); } @Test public void test1420() throws Exception { // seconds minute hours day-of-month month day-of-week year CronExpression expFirstWeekdayOfTheMonth = new CronExpression("0 0 0 1W * ?"); CronExpression expLastDayOfMonth = new CronExpression("59 59 23 L * ?"); assertTrue(CronExpression.isValidExpression(expFirstWeekdayOfTheMonth.getCronExpression())); assertTrue(CronExpression.isValidExpression(expLastDayOfMonth.getCronExpression())); List correctFireTimes = new ArrayList<>(); Calendar cal = Calendar.getInstance(); cal.set(Calendar.MILLISECOND, 0); cal.set(2025, Calendar.DECEMBER, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.JANUARY, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.FEBRUARY, 2, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.MARCH, 2, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.APRIL, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.MAY, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.JUNE, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.JULY, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.AUGUST, 3, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.SEPTEMBER, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.OCTOBER, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.NOVEMBER, 2, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.DECEMBER, 1, 0, 0, 0); correctFireTimes.add(cal.getTime()); cal.set(2025, Calendar.NOVEMBER, 18, 10, 15, 0); Date lTime = cal.getTime(); // test correct computed dates for first weekdays days of month for(int i=0; i < 13; i++) { Date nTime = expFirstWeekdayOfTheMonth.getTimeAfter(lTime); assertEquals(correctFireTimes.get(i), nTime); lTime = nTime; } correctFireTimes.clear(); cal.set(2025, Calendar.NOVEMBER, 30, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2025, Calendar.DECEMBER, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.JANUARY, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.FEBRUARY, 28, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.MARCH, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.APRIL, 30, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.MAY, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.JUNE, 30, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.JULY, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.AUGUST, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.SEPTEMBER, 30, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.OCTOBER, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.NOVEMBER, 30, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2026, Calendar.DECEMBER, 31, 23, 59, 59); correctFireTimes.add(cal.getTime()); cal.set(2025, Calendar.NOVEMBER, 18, 10, 15, 0); lTime = cal.getTime(); // test correct computed dates for last days of month for(int i=0; i < 14; i++) { Date nTime = expLastDayOfMonth.getTimeAfter(lTime); assertEquals(correctFireTimes.get(i), nTime); lTime = nTime; } } // execute with version number to generate a new version's serialized form public static void main(String[] args) throws Exception { new CronExpressionTest().writeJobDataFile("1.5.2"); } } ================================================ FILE: quartz/src/test/java/org/quartz/CronScheduleBuilderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.quartz.CronScheduleBuilder.atHourAndMinuteOnGivenDaysOfWeek; import static org.quartz.TriggerBuilder.newTrigger; /** * Unit test for CronScheduleBuilder. * * @author jhouse * */ public class CronScheduleBuilderTest { @Test void testAtHourAndMinuteOnGivenDaysOfWeek() { CronTrigger trigger = newTrigger().withIdentity("test") .withSchedule( atHourAndMinuteOnGivenDaysOfWeek(10, 0, DateBuilder.MONDAY, DateBuilder.THURSDAY, DateBuilder.FRIDAY)) .build(); assertEquals("0 0 10 ? * 2,5,6", trigger.getCronExpression()); trigger = newTrigger().withIdentity("test") .withSchedule( atHourAndMinuteOnGivenDaysOfWeek(10, 0, DateBuilder.WEDNESDAY)) .build(); assertEquals("0 0 10 ? * 4", trigger.getCronExpression()); } } ================================================ FILE: quartz/src/test/java/org/quartz/CronTriggerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.text.ParseException; import org.quartz.impl.triggers.CronTriggerImpl; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for CronTrigger. */ public class CronTriggerTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"2.0"}; /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ @Override protected String[] getVersions() { return VERSIONS; } /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ @Override protected Object getTargetObject() throws Exception { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("A", "B"); CronTriggerImpl t = new CronTriggerImpl(); t.setName("test"); t.setGroup("testGroup"); t.setCronExpression("0 0 12 * * ?"); t.setCalendarName("MyCalendar"); t.setDescription("CronTriggerDesc"); t.setJobDataMap(jobDataMap); return t; } /** * Verify that the target object and the object we just deserialized * match. */ @Override protected void verifyMatch(Object target, Object deserialized) { CronTriggerImpl targetCronTrigger = (CronTriggerImpl)target; CronTriggerImpl deserializedCronTrigger = (CronTriggerImpl)deserialized; assertNotNull(deserializedCronTrigger); assertEquals(targetCronTrigger.getName(), deserializedCronTrigger.getName()); assertEquals(targetCronTrigger.getGroup(), deserializedCronTrigger.getGroup()); assertEquals(targetCronTrigger.getJobName(), deserializedCronTrigger.getJobName()); assertEquals(targetCronTrigger.getJobGroup(), deserializedCronTrigger.getJobGroup()); // assertEquals(targetCronTrigger.getStartTime(), deserializedCronTrigger.getStartTime()); assertEquals(targetCronTrigger.getEndTime(), deserializedCronTrigger.getEndTime()); assertEquals(targetCronTrigger.getCalendarName(), deserializedCronTrigger.getCalendarName()); assertEquals(targetCronTrigger.getDescription(), deserializedCronTrigger.getDescription()); assertEquals(targetCronTrigger.getJobDataMap(), deserializedCronTrigger.getJobDataMap()); assertEquals(targetCronTrigger.getCronExpression(), deserializedCronTrigger.getCronExpression()); } void testClone() throws ParseException { CronTriggerImpl trigger = new CronTriggerImpl(); trigger.setName("test"); trigger.setGroup("testGroup"); trigger.setCronExpression("0 0 12 * * ?"); CronTrigger trigger2 = (CronTrigger) trigger.clone(); assertEquals(trigger, trigger2, "Cloning failed"); // equals() doesn't test the cron expression assertEquals( "Cloning failed for the cron expression", "0 0 12 * * ?", trigger2.getCronExpression() ); } // http://jira.opensymphony.com/browse/QUARTZ-558 void testQuartz558() throws ParseException { CronTriggerImpl trigger = new CronTriggerImpl(); trigger.setName("test"); trigger.setGroup("testGroup"); CronTrigger trigger2 = (CronTrigger) trigger.clone(); assertEquals(trigger, trigger2, "Cloning failed"); } void testMisfireInstructionValidity() throws ParseException { CronTriggerImpl trigger = new CronTriggerImpl(); try { trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY); trigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); trigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); } catch(Exception e) { fail("Unexpected exception while setting misfire instruction."); } try { trigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING + 1); fail("Expected exception while setting invalid misfire instruction but did not get it."); } catch(Exception e) { } } void testMisfireInstructionInDerivedBuilder() throws ParseException { for (int policy : asList( Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) ) { CronTriggerImpl trigger = new CronTriggerImpl(); trigger.setCronExpression("0 0 12 * * ?"); trigger.setMisfireInstruction(policy); assertThat(trigger.getMisfireInstruction(), is(policy)); CronTrigger copy = trigger.getTriggerBuilder().build(); assertThat(copy.getMisfireInstruction(), is(policy)); } } void testUndefinedMisfireInstructionInDerivedBuilder() throws ParseException { CronTriggerImpl trigger = new CronTriggerImpl() { @Override public int getMisfireInstruction() { return 12345; } }; trigger.setCronExpression("0 0 12 * * ?"); try { trigger.setMisfireInstruction(12345); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), is("The misfire instruction code is invalid for this type of trigger.")); } CronTrigger copy = trigger.getTriggerBuilder().build(); assertThat(copy.getMisfireInstruction(), is(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY)); } // execute with version number to generate a new version's serialized form public static void main(String[] args) throws Exception { new CronTriggerTest().writeJobDataFile("2.0"); } } ================================================ FILE: quartz/src/test/java/org/quartz/DailyTimeIntervalScheduleBuilderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.*; import static org.quartz.DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule; import static org.quartz.DateBuilder.dateOf; import static org.quartz.JobBuilder.newJob; import static org.quartz.TimeOfDay.hourMinuteAndSecondOfDay; import static org.quartz.TriggerBuilder.newTrigger; import java.util.Date; import java.util.List; import org.junit.jupiter.api.Test; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.impl.StdSchedulerFactory; import org.quartz.spi.OperableTrigger; /** * Unit test for DailyTimeIntervalScheduleBuilder. * * @author Zemian Deng * */ public class DailyTimeIntervalScheduleBuilderTest { @Test void testScheduleActualTrigger() throws Exception { Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); JobDetail job = newJob(MyJob.class).build(); DailyTimeIntervalTrigger trigger = newTrigger().withIdentity("test") .withSchedule(dailyTimeIntervalSchedule() .withIntervalInSeconds(3)) .build(); scheduler.scheduleJob(job, trigger); //We are not verify anything other than just run through the scheduler. scheduler.shutdown(); } void testScheduleInMiddleOfDailyInterval() throws Exception { java.util.Calendar currTime = java.util.Calendar.getInstance(); int currHour = currTime.get(java.util.Calendar.HOUR); // this test won't work out well in the early hours, where 'backing up' would give previous day, // or where daylight savings transitions could occur and confuse the assertions... if(currHour < 3) return; Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); JobDetail job = newJob(MyJob.class).build(); Trigger trigger = newTrigger().withIdentity("test") .withSchedule(dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(2, 15)) .withIntervalInMinutes(5)) .startAt(currTime.getTime()) .build(); scheduler.scheduleJob(job, trigger); trigger = scheduler.getTrigger(trigger.getKey()); System.out.println("testScheduleInMiddleOfDailyInterval: currTime = " + currTime.getTime()); System.out.println("testScheduleInMiddleOfDailyInterval: computed first fire time = " + trigger.getNextFireTime()); assertTrue(trigger.getNextFireTime().after(currTime.getTime()), "First fire time is not after now!"); Date startTime = DateBuilder.todayAt(2, 15, 0); job = newJob(MyJob.class).build(); trigger = newTrigger().withIdentity("test2") .withSchedule(dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(2, 15)) .withIntervalInMinutes(5)) .startAt(startTime) .build(); scheduler.scheduleJob(job, trigger); trigger = scheduler.getTrigger(trigger.getKey()); System.out.println("testScheduleInMiddleOfDailyInterval: startTime = " + startTime); System.out.println("testScheduleInMiddleOfDailyInterval: computed first fire time = " + trigger.getNextFireTime()); assertEquals(trigger.getNextFireTime(), startTime, "First fire time is not after now!"); scheduler.shutdown(); } @Test void testHourlyTrigger() { DailyTimeIntervalTrigger trigger = newTrigger().withIdentity("test") .withSchedule(dailyTimeIntervalSchedule() .withIntervalInHours(1)) .build(); assertEquals("test", trigger.getKey().getName()); assertEquals("DEFAULT", trigger.getKey().getGroup()); assertEquals(IntervalUnit.HOUR, trigger.getRepeatIntervalUnit()); assertEquals(1, trigger.getRepeatInterval()); List fireTimes = TriggerUtils.computeFireTimes((OperableTrigger)trigger, null, 48); assertEquals(48, fireTimes.size()); } @Test void testMinutelyTriggerWithTimeOfDay() { DailyTimeIntervalTrigger trigger = newTrigger().withIdentity("test", "group") .withSchedule(dailyTimeIntervalSchedule() .withIntervalInMinutes(72) .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(8, 0)) .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(17, 0)) .onMondayThroughFriday()) .build(); assertEquals("test", trigger.getKey().getName()); assertEquals("group", trigger.getKey().getGroup()); assertTrue(new Date().getTime() >= trigger.getStartTime().getTime()); assertNull(trigger.getEndTime()); assertEquals(IntervalUnit.MINUTE, trigger.getRepeatIntervalUnit()); assertEquals(72, trigger.getRepeatInterval()); assertEquals(new TimeOfDay(8, 0), trigger.getStartTimeOfDay()); assertEquals(new TimeOfDay(17, 0), trigger.getEndTimeOfDay()); List fireTimes = TriggerUtils.computeFireTimes((OperableTrigger)trigger, null, 48); assertEquals(48, fireTimes.size()); } @Test void testSecondlyTriggerWithStartAndEndTime() { Date startTime = DateBuilder.dateOf(0, 0, 0, 1, 1, 2011); Date endTime = DateBuilder.dateOf(0, 0, 0, 2, 1, 2011); DailyTimeIntervalTrigger trigger = newTrigger().withIdentity("test", "test") .withSchedule(dailyTimeIntervalSchedule() .withIntervalInSeconds(121) .startingDailyAt(hourMinuteAndSecondOfDay(10, 0, 0)) .endingDailyAt(hourMinuteAndSecondOfDay(23, 59, 59)) .onSaturdayAndSunday()) .startAt(startTime) .endAt(endTime) .build(); assertEquals("test", trigger.getKey().getName()); assertEquals("test", trigger.getKey().getGroup()); assertEquals(startTime.getTime(), trigger.getStartTime().getTime()); assertEquals(endTime.getTime(), trigger.getEndTime().getTime()); assertEquals(IntervalUnit.SECOND, trigger.getRepeatIntervalUnit()); assertEquals(121, trigger.getRepeatInterval()); assertEquals(new TimeOfDay(10, 0, 0), trigger.getStartTimeOfDay()); assertEquals(new TimeOfDay(23, 59, 59), trigger.getEndTimeOfDay()); List fireTimes = TriggerUtils.computeFireTimes((OperableTrigger)trigger, null, 48); assertEquals(48, fireTimes.size()); } @Test void testRepeatCountTrigger() { DailyTimeIntervalTrigger trigger = newTrigger() .withIdentity("test") .withSchedule( dailyTimeIntervalSchedule() .withIntervalInHours(1) .withRepeatCount(9)) .build(); assertEquals("test", trigger.getKey().getName()); assertEquals("DEFAULT", trigger.getKey().getGroup()); assertEquals(IntervalUnit.HOUR, trigger.getRepeatIntervalUnit()); assertEquals(1, trigger.getRepeatInterval()); List fireTimes = TriggerUtils.computeFireTimes((OperableTrigger)trigger, null, 48); assertEquals(10, fireTimes.size()); } @Test void testEndingAtAfterCount() { Date startTime = DateBuilder.dateOf(0, 0, 0, 1, 1, 2011); DailyTimeIntervalTrigger trigger = newTrigger() .withIdentity("test") .withSchedule( dailyTimeIntervalSchedule() .withIntervalInMinutes(15) .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(8, 0)) .endingDailyAfterCount(12)) .startAt(startTime) .build(); assertEquals("test", trigger.getKey().getName()); assertEquals("DEFAULT", trigger.getKey().getGroup()); assertEquals(IntervalUnit.MINUTE, trigger.getRepeatIntervalUnit()); List fireTimes = TriggerUtils.computeFireTimes((OperableTrigger)trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(10, 45, 0, 4, 1, 2011), fireTimes.get(47)); assertEquals(new TimeOfDay(10, 45), trigger.getEndTimeOfDay()); } @Test void testEndingAtAfterCountOf1() { Date startTime = DateBuilder.dateOf(0, 0, 0, 1, 1, 2011); DailyTimeIntervalTrigger trigger = newTrigger() .withIdentity("test") .withSchedule( dailyTimeIntervalSchedule() .withIntervalInMinutes(15) .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(8, 0)) .endingDailyAfterCount(1)) .startAt(startTime) .forJob("testJob", "testJobGroup") .build(); assertEquals("test", trigger.getKey().getName()); assertEquals("DEFAULT", trigger.getKey().getGroup()); assertEquals(IntervalUnit.MINUTE, trigger.getRepeatIntervalUnit()); validateTrigger(trigger); List fireTimes = TriggerUtils.computeFireTimes((OperableTrigger)trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(8, 0, 0, 17, 2, 2011), fireTimes.get(47)); assertEquals(new TimeOfDay(8, 0), trigger.getEndTimeOfDay()); } private void validateTrigger(DailyTimeIntervalTrigger trigger) { try { ((OperableTrigger) trigger).validate(); } catch (SchedulerException e) { throw new RuntimeException("Trigger " + trigger.getKey() + " failed to validate", e); } } @Test void testEndingAtAfterCountOf0() { try { Date startTime = DateBuilder.dateOf(0, 0, 0, 1, 1, 2011); newTrigger() .withIdentity("test") .withSchedule( dailyTimeIntervalSchedule() .withIntervalInMinutes(15) .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(8, 0)) .endingDailyAfterCount(0)) .startAt(startTime) .build(); fail("We should not accept endingDailyAfterCount(0)"); } catch (IllegalArgumentException e) { // Expected. } try { Date startTime = DateBuilder.dateOf(0, 0, 0, 1, 1, 2011); newTrigger() .withIdentity("test") .withSchedule( dailyTimeIntervalSchedule() .withIntervalInMinutes(15) .endingDailyAfterCount(1)) .startAt(startTime) .build(); fail("We should not accept endingDailyAfterCount(x) without first setting startingDailyAt."); } catch (IllegalArgumentException e) { // Expected. } } /** An empty job for testing purpose. */ public static class MyJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { // } } } ================================================ FILE: quartz/src/test/java/org/quartz/DateBuilderTest.java ================================================ package org.quartz; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.quartz.DateBuilder.JULY; import static org.quartz.DateBuilder.JUNE; import static org.quartz.DateBuilder.dateOf; import static org.quartz.DateBuilder.futureDate; import static org.quartz.DateBuilder.newDate; import static org.quartz.DateBuilder.newDateInLocale; import static org.quartz.DateBuilder.newDateInTimeZoneAndLocale; import static org.quartz.DateBuilder.newDateInTimezone; import static org.quartz.DateBuilder.todayAt; import static org.quartz.DateBuilder.translateTime; import java.time.Clock; import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.quartz.DateBuilder.IntervalUnit; class DateBuilderTest { @Test void testJustInstantiatedBuilderShouldReturnSameInstantTruncatedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T10:15:30.999Z"), ZoneOffset.UTC); var builder = DateBuilder.newDate(); builder.setClock(clock); assertEquals(Date.from(Instant.parse("2007-12-03T10:15:30.000Z")), builder.build()); } @Test void testBuilder() { var expected = ZonedDateTime.of(2013, 07, 1, 10, 30, 0, 0, ZoneId.systemDefault()).toInstant(); var bd1 = newDate().inYear(2013).inMonth(JULY).onDay(1).atHourOfDay(10).atMinute(30).atSecond(0).build(); var bd2 = newDate().inYear(2013).inMonthOnDay(JULY, 1).atHourMinuteAndSecond(10, 30, 0).build(); assertAll( "dateBuilderDefaultTimeZone", () -> assertEquals(expected, bd1.toInstant()), () -> assertEquals(expected, bd2.toInstant())); } @Test void testBuilderWithTimeZone() { TimeZone tz = TimeZone.getTimeZone("GMT-4:00"); var expected = Instant.parse("2013-06-01T14:33:12.000Z"); Locale lz = Locale.TAIWAN; var bd1 = newDate().inYear(2013) .inMonth(JUNE) .onDay(1) .atHourOfDay(10) .atMinute(33) .atSecond(12) .inTimeZone(tz) .inLocale(lz) .build(); var bd2 = newDateInLocale(lz).inYear(2013) .inMonth(JUNE) .onDay(1) .atHourOfDay(10) .atMinute(33) .atSecond(12) .inTimeZone(tz) .build(); var bd3 = newDateInTimezone(tz).inYear(2013) .inMonth(JUNE) .onDay(1) .atHourOfDay(10) .atMinute(33) .atSecond(12) .inLocale(lz) .build(); var bd4 = newDateInTimeZoneAndLocale(tz, lz).inYear(2013) .inMonth(JUNE) .onDay(1) .atHourOfDay(10) .atMinute(33) .atSecond(12) .build(); assertAll( "dateBuilderWithCustomTimeZone", () -> assertEquals(expected, bd1.toInstant()), () -> assertEquals(expected, bd2.toInstant()), () -> assertEquals(expected, bd3.toInstant()), () -> assertEquals(expected, bd4.toInstant())); } /* * futureDate tests */ private static Stream futureDateTestData() { return Stream.of( Arguments.of(1, IntervalUnit.MILLISECOND, "2007-12-03T10:15:30.001Z"), Arguments.of(1, IntervalUnit.SECOND, "2007-12-03T10:15:31.000Z"), Arguments.of(1, IntervalUnit.MINUTE, "2007-12-03T10:16:30.000Z"), Arguments.of(1, IntervalUnit.HOUR, "2007-12-03T11:15:30.000Z"), Arguments.of(1, IntervalUnit.DAY, "2007-12-04T10:15:30.000Z"), Arguments.of(1, IntervalUnit.WEEK, "2007-12-10T10:15:30.000Z"), Arguments.of(1, IntervalUnit.MONTH, "2008-01-03T10:15:30.000Z"), Arguments.of(1, IntervalUnit.YEAR, "2008-12-03T10:15:30.000Z")); } @ParameterizedTest @MethodSource("futureDateTestData") void testFutureDate(int interval, IntervalUnit unit, String expected) { var clock = Clock.fixed(Instant.parse("2007-12-03T10:15:30.000Z"), ZoneId.systemDefault()); assertEquals(Date.from(Instant.parse(expected)), futureDate(interval, unit, clock)); } @ParameterizedTest @MethodSource("futureDateTestData") void testFutureDateWithNonDefaultZone(int interval, IntervalUnit unit, String expected) { var clock = Clock.fixed(Instant.parse("2007-12-03T10:15:30.000Z"), ZoneOffset.ofHoursMinutes(3, 15)); assertEquals(Date.from(Instant.parse(expected)), futureDate(interval, unit, clock)); } /* * tomorrowAt tests */ private static Stream tomorrowAtTestData() { return Stream.of( Arguments.of("2024-10-25T23:59:30.999Z", ZoneOffset.UTC, "2024-10-26T02:30:20.000Z"), Arguments.of( "2024-03-30T23:59:59.999+01:00", ZoneId.of("Europe/Vienna"), "2024-03-31T03:30:20.000+02:00"), // DST change skipped one hour Arguments.of( "2024-10-26T23:59:59.999+01:00", ZoneId.of("Europe/Vienna"), "2024-10-27T02:30:20.000+01:00")); // DST change added one hour } @ParameterizedTest @MethodSource("tomorrowAtTestData") void testTomorrowAt(String input, ZoneId zoneId, String expected) { var instant = ZonedDateTime.parse(input).toInstant(); var clock = Clock.fixed(instant, zoneId); var tomorrow = DateBuilder.tomorrowAt(2, 30, 20, clock); assertEquals(Date.from(ZonedDateTime.parse(expected).toInstant()), tomorrow); } /* * todayAt tests */ @ParameterizedTest @MethodSource("testDateOf3PWithInvalidParametersTestData") void testTodayAtInvalidValues(int hour, int minute, int second) { assertThrows(DateTimeException.class, () -> todayAt(hour, minute, second)); } @Test void testTodayAt() { var clock1 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); var date1 = DateBuilder.todayAt(1, 1, 1, clock1); var clock2 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneId.of("Europe/Vienna")); var date2 = DateBuilder.todayAt(1, 1, 1, clock2); var clock3 = Clock.fixed(Instant.parse("2024-03-31T08:13:54.341Z"), ZoneId.of("Europe/Vienna")); var dstSkippedHour = DateBuilder.todayAt(2, 30, 17, clock3); // DST change => skipped hour assertAll( "dateOf3P", () -> assertNotEquals(date1, date2), () -> assertEquals(Instant.parse("2007-12-03T01:01:01.000Z"), date1.toInstant()), () -> assertEquals(Instant.parse("2007-12-03T00:01:01.000Z"), date2.toInstant()), () -> assertEquals( ZonedDateTime.parse("2024-03-31T03:30:17.000+02:00") .toInstant(), dstSkippedHour.toInstant())); } /* * dateOf tests */ private static Stream testDateOf3PWithInvalidParametersTestData() { return Stream.of( Arguments.of(-1, 0, 0), Arguments.of(24, 0, 0), Arguments.of(0, -1, 0), Arguments.of(0, 60, 0), Arguments.of(0, 0, -1), Arguments.of(0, 0, 60)); } @ParameterizedTest @MethodSource("testDateOf3PWithInvalidParametersTestData") void testDateOf3PWithInvalidParameters(int hour, int minute, int second) { assertThrows(DateTimeException.class, () -> dateOf(hour, minute, second)); } @Test void testDateOf3P() { var clock1 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); var clock2 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneId.of("Europe/Vienna")); var date1 = DateBuilder.dateOf(1, 1, 1, clock1); var date2 = DateBuilder.dateOf(1, 1, 1, clock2); assertAll( "dateOf3P", () -> assertNotEquals(date1, date2), () -> assertEquals(Instant.parse("2007-12-03T01:01:01.000Z"), date1.toInstant()), () -> assertEquals(Instant.parse("2007-12-03T00:01:01.000Z"), date2.toInstant())); } private static Stream testDateOf5PWithInvalidParametersTestData() { return Stream.of( Arguments.of(-1, 0, 0, 0, 0), Arguments.of(24, 0, 0, 0, 0), Arguments.of(0, -1, 0, 0, 0), Arguments.of(0, 60, 0, 0, 0), Arguments.of(0, 0, -1, 0, 0), Arguments.of(0, 0, 60, 0, 0), Arguments.of(0, 0, 0, 0, 0), Arguments.of(0, 0, 0, 32, 0), Arguments.of(0, 0, 0, 0, 0), Arguments.of(0, 0, 0, 0, 13), Arguments.of(0, 0, 0, 0, 0), Arguments.of(0, 0, 0, 0, 0)); } @ParameterizedTest @MethodSource("testDateOf5PWithInvalidParametersTestData") void testDateOf5PWithInvalidParameters(int hour, int minute, int second, int dayOfMonth, int month) { assertThrows(DateTimeException.class, () -> dateOf(hour, minute, second, dayOfMonth, month)); } @Test void testDateOf5P() { var clock1 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); var clock2 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneId.of("Europe/Vienna")); var date1 = DateBuilder.dateOf(1, 1, 1, 1, 1, clock1); var date2 = DateBuilder.dateOf(1, 1, 1, 1, 1, clock2); assertAll( "dateOf6P", () -> assertNotEquals(date1, date2), () -> assertEquals(Instant.parse("2007-01-01T01:01:01.000Z"), date1.toInstant()), () -> assertEquals(Instant.parse("2007-01-01T00:01:01.000Z"), date2.toInstant())); } private static Stream testDateOf6PWithInvalidParametersTestData() { return Stream.of( Arguments.of(-1, 0, 0, 0, 0, 0), Arguments.of(24, 0, 0, 0, 0, 0), Arguments.of(0, -1, 0, 0, 0, 0), Arguments.of(0, 60, 0, 0, 0, 0), Arguments.of(0, 0, -1, 0, 0, 0), Arguments.of(0, 0, 60, 0, 0, 0), Arguments.of(0, 0, 0, 0, 0, 0), Arguments.of(0, 0, 0, 32, 0, 0), Arguments.of(0, 0, 0, 0, 0, 0), Arguments.of(0, 0, 0, 0, 13, 0), Arguments.of(0, 0, 0, 0, 0, -1_000_000_000), Arguments.of(0, 0, 0, 0, 0, 1_000_000_000)); } @ParameterizedTest @MethodSource("testDateOf6PWithInvalidParametersTestData") void testDateOf6PWithInvalidParameters(int hour, int minute, int second, int dayOfMonth, int month, int year) { assertThrows(DateTimeException.class, () -> dateOf(hour, minute, second, dayOfMonth, month, year)); } @Test void testDateOf6P() { var clock1 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); var clock2 = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneId.of("Europe/Vienna")); var date1 = DateBuilder.dateOf(1, 1, 1, 1, 1, 2024, clock1); var date2 = DateBuilder.dateOf(1, 1, 1, 1, 1, 2024, clock2); assertAll( "dateOf6P", () -> assertNotEquals(date1, date2), () -> assertEquals(Instant.parse("2024-01-01T01:01:01.000Z"), date1.toInstant()), () -> assertEquals(Instant.parse("2024-01-01T00:01:01.000Z"), date2.toInstant())); } /* * evenHourDateAfterNow tests */ @Test void testEvenHourDateAfterNow() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T09:00:00.000Z"), DateBuilder.evenHourDateAfterNow(clock) .toInstant()); } /* * evenHourDate tests */ @Test void testEvenHourDate() { assertEquals( Instant.parse("2007-12-03T09:00:00.000Z"), DateBuilder.evenHourDate(Date.from(Instant.parse("2007-12-03T08:13:54.341Z"))).toInstant()); } @Test void testEvenHourDateWithoutProvidedDateCurrentDateRoundedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T09:00:00.000Z"), DateBuilder.evenHourDate(null, clock) .toInstant()); } /* * evenHourDateBefore tests */ @Test void testEvenHourDateBefore() { assertEquals( Instant.parse("2007-12-03T08:00:00.000Z"), DateBuilder.evenHourDateBefore(Date.from(Instant.parse("2007-12-03T08:13:54.341Z"))).toInstant()); } @Test void testEvenHourDateBeforeWithoutProvidedDateCurrentDateRoundedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:00:00.000Z"), DateBuilder.evenHourDateBefore(null, clock) .toInstant()); } /* * evenMinuteDateAfterNow tests */ @Test void testEvenMinuteDateAfterNow() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:14:00.000Z"), DateBuilder.evenMinuteDateAfterNow(clock) .toInstant()); } /* * evenMinuteDate tests */ @Test void testEvenMinuteDate() { assertEquals( Instant.parse("2007-12-03T08:14:00.000Z"), DateBuilder.evenMinuteDate(Date.from(Instant.parse("2007-12-03T08:13:54.341Z"))).toInstant()); } @Test void testEvenMinuteDateWithoutProvidedDateCurrentDateRoundedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:14:00.000Z"), DateBuilder.evenMinuteDate(null, clock) .toInstant()); } /* * evenMinuteDateBefore tests */ @Test void testEvenMinuteDateBefore() { assertEquals( Instant.parse("2007-12-03T08:13:00.000Z"), DateBuilder.evenMinuteDateBefore(Date.from(Instant.parse("2007-12-03T08:13:54.341Z"))).toInstant()); } @Test void testEvenMinuteDateBeforeWithoutProvidedDateCurrentDateRoundedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:13:00.000Z"), DateBuilder.evenMinuteDateBefore(null, clock) .toInstant()); } /* * evenSecondDateAfterNow tests */ @Test void testEvenSecondDateAfterNow() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:13:55.000Z"), DateBuilder.evenSecondDateAfterNow(clock) .toInstant()); } @Test void testEvenSecondDateAfterNowDSTForward() { var clock = Clock.fixed( ZonedDateTime.parse("2024-03-31T01:59:59.999+01:00") .toInstant(), ZoneId.of("Europe/Vienna")); var before = clock.instant(); var result = DateBuilder.evenSecondDateAfterNow(clock).toInstant(); assertAll("DSTChange+1H", () -> assertEquals(ZonedDateTime.parse("2024-03-31T01:59:59.999+01:00").toInstant(), before), () -> assertEquals(ZonedDateTime.parse("2024-03-31T01:00:00.000Z").toInstant(), result), () -> assertEquals(ZonedDateTime.parse("2024-03-31T03:00:00.000+02:00").toInstant(), result)); } @Test void testEvenSecondDateAfterNowDSTBackward() { var clock = Clock.fixed( ZonedDateTime.parse("2024-10-27T02:59:59.999+02:00") .toInstant(), ZoneId.of("Europe/Vienna")); var before = clock.instant(); var result = DateBuilder.evenSecondDateAfterNow(clock).toInstant(); assertAll("DSTChange-1H", () -> assertEquals(ZonedDateTime.parse("2024-10-27T02:59:59.999+02:00").toInstant(), before), () -> assertEquals(ZonedDateTime.parse("2024-10-27T01:00:00.000Z").toInstant(), result), () -> assertEquals(ZonedDateTime.parse("2024-10-27T02:00:00.000+01:00").toInstant(), result)); } /* * evenSecondDate tests */ @Test void testEvenSecondDate() { assertEquals( Instant.parse("2007-12-03T08:13:55.000Z"), DateBuilder.evenSecondDate(Date.from(Instant.parse("2007-12-03T08:13:54.341Z"))).toInstant()); } @Test void testEvenSecondDateWithoutProvidedDateCurrentDateRoundedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:13:55.000Z"), DateBuilder.evenSecondDate(null, clock) .toInstant()); } /* * evenSecondDateBefore tests */ @Test void testEvenSecondDateBefore() { assertEquals( Instant.parse("2007-12-03T08:13:54.000Z"), DateBuilder.evenSecondDateBefore(Date.from(Instant.parse("2007-12-03T08:13:54.341Z"))).toInstant()); } @Test void testEvenSecondDateBeforeWithoutProvidedDateCurrentDateRoundedToSeconds() { var clock = Clock.fixed(Instant.parse("2007-12-03T08:13:54.341Z"), ZoneOffset.UTC); assertEquals( Instant.parse("2007-12-03T08:13:54.000Z"), DateBuilder.evenSecondDateBefore(null, clock) .toInstant()); } /* * nextGivenMinuteDate tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 60, Integer.MAX_VALUE }) void testNextGivenMinuteDateWithInvalidSecondBaseShouldThrow(int minuteBase) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.nextGivenMinuteDate(null, minuteBase)); } private static Stream nextGivenMinuteDateTestData() { return Stream.of( Arguments.of("2007-12-03T11:16:41.123Z", 20, "2007-12-03T11:20:00.000Z"), Arguments.of("2007-12-03T11:36:41.123Z", 20, "2007-12-03T11:40:00.000Z"), Arguments.of("2007-12-03T11:46:41.123Z", 20, "2007-12-03T12:00:00.000Z"), Arguments.of("2007-12-03T11:26:41.123Z", 30, "2007-12-03T11:30:00.000Z"), Arguments.of("2007-12-03T11:36:41.123Z", 30, "2007-12-03T12:00:00.000Z"), Arguments.of("2007-12-03T11:16:41.123Z", 17, "2007-12-03T11:17:00.000Z"), Arguments.of("2007-12-03T11:17:41.123Z", 17, "2007-12-03T11:34:00.000Z"), Arguments.of("2007-12-03T11:52:41.123Z", 17, "2007-12-03T12:00:00.000Z"), Arguments.of("2007-12-03T11:52:41.123Z", 5, "2007-12-03T11:55:00.000Z"), Arguments.of("2007-12-03T11:57:41.123Z", 5, "2007-12-03T12:00:00.000Z"), Arguments.of("2007-12-03T11:17:41.123Z", 0, "2007-12-03T12:00:00.000Z"), Arguments.of("2007-12-03T11:17:41.123Z", 1, "2007-12-03T11:18:00.000Z")); } @ParameterizedTest @MethodSource("nextGivenMinuteDateTestData") void testNextGivenMinuteDate(String input, int minuteBase, String expected) { var instant = Instant.parse(input); var result = DateBuilder.nextGivenMinuteDate(instant != null ? Date.from(instant) : null, minuteBase); assertEquals(Instant.parse(expected), result.toInstant()); } @Test void testNextGivenMinuteDateWithNullDateShouldReturnCurrentDateRoundedToBaseMinute() { var clock = Clock.fixed(Instant.parse("2007-12-03T10:15:30.123Z"), ZoneOffset.UTC); var result = DateBuilder.nextGivenMinuteDate(null, 0, clock); assertEquals(Instant.parse("2007-12-03T11:00:00.000Z"), result.toInstant()); } /* * nextGivenSecondDate tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 60, Integer.MAX_VALUE }) void testNextGivenSecondDateWithInvalidSecondBaseShouldThrow(int secondBase) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.nextGivenSecondDate(null, secondBase)); } private static Stream nextGivenSecondDateTestData() { return Stream.of( Arguments.of("2007-12-03T10:15:30.123Z", 0, "2007-12-03T10:16:00.000Z"), Arguments.of("2007-12-03T10:15:30.123Z", 1, "2007-12-03T10:15:31.000Z"), Arguments.of("2007-12-03T10:15:54.256Z", 13, "2007-12-03T10:16:00.000Z"), Arguments.of("2007-12-03T10:15:30.256Z", 13, "2007-12-03T10:15:39.000Z"), Arguments.of("2007-12-03T10:15:30.000Z", 59, "2007-12-03T10:15:59.000Z")); } @ParameterizedTest @MethodSource("nextGivenSecondDateTestData") void testNextGivenSecondDate(String input, int secondBase, String expected) { var instant = Instant.parse(input); var result = DateBuilder.nextGivenSecondDate(instant != null ? Date.from(instant) : null, secondBase); assertEquals(Instant.parse(expected), result.toInstant()); } @Test void testNextGivenSecondDateWithNullDateShouldReturnCurrentDateRoundedToBaseSecond() { var clock = Clock.fixed(Instant.parse("2007-12-03T10:15:30.123Z"), ZoneOffset.UTC); var result = DateBuilder.nextGivenSecondDate(null, 0, clock); assertEquals(Instant.parse("2007-12-03T10:16:00.000Z"), result.toInstant()); } /* * translateTime tests */ @Test void testTranslate() { TimeZone tz1 = TimeZone.getTimeZone("GMT-2:00"); TimeZone tz2 = TimeZone.getTimeZone("GMT-4:00"); Calendar vc = Calendar.getInstance(tz1); vc.set(Calendar.YEAR, 2013); vc.set(Calendar.MONTH, Calendar.JUNE); vc.set(Calendar.DAY_OF_MONTH, 1); vc.set(Calendar.HOUR_OF_DAY, 10); vc.set(Calendar.MINUTE, 33); vc.set(Calendar.SECOND, 12); vc.set(Calendar.MILLISECOND, 0); vc.setTime(translateTime(vc.getTime(), tz1, tz2)); assertEquals(12, vc.get(Calendar.HOUR_OF_DAY)); vc = Calendar.getInstance(tz2); vc.set(Calendar.YEAR, 2013); vc.set(Calendar.MONTH, Calendar.JUNE); vc.set(Calendar.DAY_OF_MONTH, 1); vc.set(Calendar.HOUR_OF_DAY, 10); vc.set(Calendar.MINUTE, 33); vc.set(Calendar.SECOND, 12); vc.set(Calendar.MILLISECOND, 0); vc.setTime(translateTime(vc.getTime(), tz2, tz1)); assertEquals(8, vc.get(Calendar.HOUR_OF_DAY)); } private static Stream translateTimeTestData() { return Stream.of( Arguments.of( "2013-06-01T10:33:12-02:00", TimeZone.getTimeZone("GMT-2:00"), TimeZone.getTimeZone("GMT-4:00"), "2013-06-01T12:33:12-02:00"), Arguments.of( "2013-06-01T10:33:12-04:00", TimeZone.getTimeZone("GMT-4:00"), TimeZone.getTimeZone("GMT-2:00"), "2013-06-01T08:33:12-04:00")); } @ParameterizedTest @MethodSource("translateTimeTestData") void testTranslateTime(String input, TimeZone tzFrom, TimeZone tzTo, String expected) { var zdt = ZonedDateTime.parse(input).withZoneSameInstant(tzFrom.toZoneId()); var result1 = translateTime(Date.from(zdt.toInstant()), tzFrom, tzTo); assertEquals(ZonedDateTime.parse(expected).toInstant(), result1.toInstant()); } /* * validateDayOfWeek tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 0, 8, Integer.MAX_VALUE }) void testValidateDayOfWeekWithInvalidValueShouldThrow(int dayOfWeek) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateDayOfWeek(dayOfWeek)); } private static Stream validateDayOfWeekTestData() { return Stream.iterate(1, i -> i + 1) .limit(7) .map(i -> Arguments.of(i)); } @ParameterizedTest @MethodSource("validateDayOfWeekTestData") void testValidateDayOfWeek(int second) { assertDoesNotThrow(() -> DateBuilder.validateDayOfWeek(second)); } /* * validateHour tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 24, Integer.MAX_VALUE }) void testValidateHourWithInvalidValueShouldThrow(int minute) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateHour(minute)); } private static Stream validateHourTestData() { return Stream.iterate(0, i -> i + 1) .limit(24) .map(i -> Arguments.of(i)); } @ParameterizedTest @MethodSource("validateHourTestData") void testValidateHour(int hour) { assertDoesNotThrow(() -> DateBuilder.validateHour(hour)); } /* * validateMinutes tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 60, Integer.MAX_VALUE }) void testValidateMinuteWithInvalidValueShouldThrow(int minute) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateMinute(minute)); } private static Stream validateMinuteTestData() { return Stream.iterate(0, i -> i + 1) .limit(60) .map(i -> Arguments.of(i)); } @ParameterizedTest @MethodSource("validateMinuteTestData") void testValidateMinute(int minute) { assertDoesNotThrow(() -> DateBuilder.validateMinute(minute)); } /* * validateSeconds tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 60, Integer.MAX_VALUE }) void testValidateSecondWithInvalidValueShouldThrow(int second) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateSecond(second)); } private static Stream validateSecondsTestData() { return Stream.iterate(0, i -> i + 1) .limit(60) .map(i -> Arguments.of(i)); } @ParameterizedTest @MethodSource("validateSecondsTestData") void testValidateSeconds(int second) { assertDoesNotThrow(() -> DateBuilder.validateSecond(second)); } /* * validateDayOfMonth tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 0, 32, Integer.MAX_VALUE }) void testValidateDayOfMonthWithInvalidValueShouldThrow(int dayOfMonth) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateDayOfMonth(dayOfMonth)); } private static Stream validateDayOfMonthTestData() { return Stream.iterate(1, i -> i + 1) .limit(31) .map(i -> Arguments.of(i)); } @ParameterizedTest @MethodSource("validateDayOfMonthTestData") void testValidateDayOfMonth(int dayOfMonth) { assertDoesNotThrow(() -> DateBuilder.validateDayOfMonth(dayOfMonth)); } /* * validateMonth tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -1, 0, 13, Integer.MAX_VALUE }) void testValidateMonthWithInvalidValueShouldThrow(int month) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateMonth(month)); } private static Stream validateMonthTestData() { return Stream.iterate(1, i -> i + 1) .limit(12) .map(i -> Arguments.of(i)); } @ParameterizedTest @MethodSource("validateMonthTestData") void testValidateMonth(int month) { assertDoesNotThrow(() -> DateBuilder.validateMonth(month)); } /* * validateYear tests */ @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, 1969, 1_000_000_000, Integer.MAX_VALUE }) void testValidateYearWithInvalidValueShouldThrow(int year) { assertThrows(IllegalArgumentException.class, () -> DateBuilder.validateYear(year)); } @ParameterizedTest @ValueSource(ints = { 1970, 2024, 999_999_999 }) void testValidateYear(int year) { assertDoesNotThrow(() -> DateBuilder.validateYear(year)); } } ================================================ FILE: quartz/src/test/java/org/quartz/DefaultSchedulerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import org.junit.jupiter.api.Test; import org.quartz.impl.JobDetailImpl; import org.quartz.impl.StdSchedulerFactory; /** * DefaultSchedulerTest */ class DefaultSchedulerTest { @Test void testAddJobNoTrigger() throws Exception { Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); JobDetailImpl jobDetail = new JobDetailImpl(); jobDetail.setName("testjob"); try { scheduler.addJob(jobDetail, false); } catch (SchedulerException e) { assertThat(e.getMessage(), containsString("durable")); } jobDetail.setDurability(true); scheduler.addJob(jobDetail, false); } } ================================================ FILE: quartz/src/test/java/org/quartz/DisallowConcurrentExecutionJobTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.quartz.impl.StdSchedulerFactory; import org.quartz.listeners.JobListenerSupport; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; /** * Integration test for using DisallowConcurrentExecution annot. * * @author Zemian Deng */ class DisallowConcurrentExecutionJobTest { private static final long JOB_BLOCK_TIME = 300L; private static final String BARRIER = "BARRIER"; private static final String DATE_STAMPS = "DATE_STAMPS"; @DisallowConcurrentExecution public static class TestJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { try { @SuppressWarnings("unchecked") List jobExecDates = (List)context.getScheduler().getContext().get(DATE_STAMPS); long firedAt = System.currentTimeMillis(); jobExecDates.add(new Date(firedAt)); long sleepTill = firedAt + JOB_BLOCK_TIME; for (long sleepFor = sleepTill - System.currentTimeMillis(); sleepFor > 0; sleepFor = sleepTill - System.currentTimeMillis()) { Thread.sleep(sleepFor); } } catch (InterruptedException e) { throw new JobExecutionException("Failed to pause job for testing."); } catch (SchedulerException e) { throw new JobExecutionException("Failed to lookup datestamp collection."); } } } public static class TestJobListener extends JobListenerSupport { private final AtomicInteger jobExCount = new AtomicInteger(0); private final int jobExecutionCountToSyncAfter; public TestJobListener(int jobExecutionCountToSyncAfter) { this.jobExecutionCountToSyncAfter = jobExecutionCountToSyncAfter; } public String getName() { return "TestJobListener"; } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { if(jobExCount.incrementAndGet() == jobExecutionCountToSyncAfter) { try { CyclicBarrier barrier = (CyclicBarrier)context.getScheduler().getContext().get(BARRIER); barrier.await(125, TimeUnit.SECONDS); } catch (Throwable e) { e.printStackTrace(); throw new AssertionError("Await on barrier was interrupted: " + e.toString()); } } } } @Test void testNoConcurrentExecOnSameJob() throws Exception { List jobExecDates = Collections.synchronizedList(new ArrayList()); CyclicBarrier barrier = new CyclicBarrier(2); Date startTime = new Date(System.currentTimeMillis() + 100); // make the triggers fire at the same time. JobDetail job1 = JobBuilder.newJob(TestJob.class).withIdentity("job1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.simpleSchedule()) .startAt(startTime).build(); Trigger trigger2 = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.simpleSchedule()) .startAt(startTime).forJob(job1.getKey()).build(); Properties props = new Properties(); props.setProperty("org.quartz.scheduler.idleWaitTime", "1500"); props.setProperty("org.quartz.threadPool.threadCount", "2"); Scheduler scheduler = new StdSchedulerFactory(props).getScheduler(); scheduler.getContext().put(BARRIER, barrier); scheduler.getContext().put(DATE_STAMPS, jobExecDates); scheduler.getListenerManager().addJobListener(new TestJobListener(2)); scheduler.scheduleJob(job1, trigger1); scheduler.scheduleJob(trigger2); scheduler.start(); barrier.await(125, TimeUnit.SECONDS); scheduler.shutdown(true); assertThat(jobExecDates, hasSize(2)); long fireTimeTrigger1 = jobExecDates.get(0).getTime(); long fireTimeTrigger2 = jobExecDates.get(1).getTime(); assertThat(fireTimeTrigger2 - fireTimeTrigger1, greaterThanOrEqualTo(JOB_BLOCK_TIME)); } /** QTZ-202 */ @Test void testNoConcurrentExecOnSameJobWithBatching() throws Exception { List jobExecDates = Collections.synchronizedList(new ArrayList()); CyclicBarrier barrier = new CyclicBarrier(2); Date startTime = new Date(System.currentTimeMillis() + 100); // make the triggers fire at the same time. JobDetail job1 = JobBuilder.newJob(TestJob.class).withIdentity("job1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.simpleSchedule()) .startAt(startTime).build(); Trigger trigger2 = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.simpleSchedule()) .startAt(startTime).forJob(job1.getKey()).build(); Properties props = new Properties(); props.setProperty("org.quartz.scheduler.idleWaitTime", "1500"); props.setProperty("org.quartz.scheduler.batchTriggerAcquisitionMaxCount", "2"); props.setProperty("org.quartz.threadPool.threadCount", "2"); Scheduler scheduler = new StdSchedulerFactory(props).getScheduler(); scheduler.getContext().put(BARRIER, barrier); scheduler.getContext().put(DATE_STAMPS, jobExecDates); scheduler.getListenerManager().addJobListener(new TestJobListener(2)); scheduler.scheduleJob(job1, trigger1); scheduler.scheduleJob(trigger2); scheduler.start(); barrier.await(125, TimeUnit.SECONDS); scheduler.shutdown(true); assertThat(jobExecDates, hasSize(2)); long fireTimeTrigger1 = jobExecDates.get(0).getTime(); long fireTimeTrigger2 = jobExecDates.get(1).getTime(); assertThat(fireTimeTrigger2 - fireTimeTrigger1, greaterThanOrEqualTo(JOB_BLOCK_TIME)); } } ================================================ FILE: quartz/src/test/java/org/quartz/FlakyJdbcSchedulerTest.java ================================================ package org.quartz; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JobStoreTX; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.simpl.SimpleThreadPool; import org.quartz.utils.ConnectionProvider; import org.quartz.utils.DBConnectionManager; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.fail; public class FlakyJdbcSchedulerTest extends AbstractSchedulerTest { private Random random = new Random(); private float createFailureProb = 0.0f; private float preCommitFailureProb = 0.0f; private float postCommitFailureProb = 0.0f; @MethodSource("data") public static Stream data() { return Stream.of( Arguments.of(0f, 0f, 0f), Arguments.of(0.2f, 0f, 0f), Arguments.of(0f, 0.2f, 0f), Arguments.of(0f, 0f, 0.2f), Arguments.of(0.2f, 0.2f, 0.2f) ); } @Override protected Scheduler createScheduler(String name, int threadPoolSize) throws SchedulerException { try { DBConnectionManager.getInstance().addConnectionProvider(name, new FlakyConnectionProvider(name)); } catch (SQLException ex) { throw new SchedulerException("Failed to create scheduler", ex); } JobStoreTX jobStore = new JobStoreTX(); jobStore.setDataSource(name); jobStore.setTablePrefix("QRTZ_"); jobStore.setInstanceId("AUTO"); jobStore.setDbRetryInterval(50); DirectSchedulerFactory.getInstance().createScheduler( name + "Scheduler", "AUTO", new SimpleThreadPool(threadPoolSize, Thread.NORM_PRIORITY), jobStore, null, 0, -1, 50 ); return SchedulerRepository.getInstance().lookup(name + "Scheduler"); } @ParameterizedTest @MethodSource("data") void testTriggerFiring(float createFailureProb, float preCommitFailureProb, float postCommitFailureProb) throws Exception { this.createFailureProb = createFailureProb; this.preCommitFailureProb = preCommitFailureProb; this.postCommitFailureProb = postCommitFailureProb; this.random = new Random(); final int jobCount = 100; final int execCount = 5; Scheduler scheduler = createScheduler("testTriggerFiring", 2); try { for (int i = 0; i < jobCount; i++) { String jobName = "myJob" + i; JobDetail jobDetail = JobBuilder.newJob(TestJob.class) .withIdentity(jobName, "myJobGroup") .usingJobData("data", 0) .storeDurably() .requestRecovery() .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("triggerName" + i, "triggerGroup") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).withRepeatCount(execCount - 1)) .build(); if (!scheduler.checkExists(jobDetail.getKey())) { scheduler.scheduleJob(jobDetail, trigger); } } scheduler.start(); for (int i = 0; i < TimeUnit.MINUTES.toSeconds(5); i++) { int doneCount = 0; for (int j = 0; j < jobCount; j++) { JobDetail jobDetail = scheduler.getJobDetail(new JobKey("myJob" + j, "myJobGroup")); if (jobDetail != null && jobDetail.getJobDataMap().getInt("data") >= execCount) { doneCount++; } } if (doneCount == jobCount) { return; } TimeUnit.SECONDS.sleep(1); } fail("Not all jobs completed as expected."); } finally { scheduler.shutdown(true); } } @PersistJobDataAfterExecution @DisallowConcurrentExecution public static class TestJob implements Job { public void execute(JobExecutionContext context) { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); int val = dataMap.getInt("data") + 1; dataMap.put("data", val); } } private void createFailure() throws SQLException { if (random.nextFloat() < createFailureProb) { throw new SQLException("FlakyConnection failed on you on creation."); } } private void preCommitFailure() throws SQLException { if (random.nextFloat() < preCommitFailureProb) { throw new SQLException("FlakyConnection failed on you pre-commit."); } } private void postCommitFailure() throws SQLException { if (random.nextFloat() < postCommitFailureProb) { throw new SQLException("FlakyConnection failed on you post-commit."); } } private class FlakyConnectionProvider implements ConnectionProvider { private final Thread safeThread; private final String delegateName; private FlakyConnectionProvider(String name) throws SQLException { this.delegateName = "delegate_" + name; this.safeThread = Thread.currentThread(); JdbcQuartzTestUtilities.createDatabase(delegateName, DatabaseType.DERBY); } @Override public Connection getConnection() throws SQLException { if (Thread.currentThread() == safeThread) { return DBConnectionManager.getInstance().getConnection(delegateName); } else { createFailure(); return (Connection) Proxy.newProxyInstance( Connection.class.getClassLoader(), new Class[]{Connection.class}, new FlakyConnectionInvocationHandler(DBConnectionManager.getInstance().getConnection(delegateName)) ); } } @Override public void shutdown() throws SQLException { DBConnectionManager.getInstance().shutdown(delegateName); JdbcQuartzTestUtilities.destroyDatabase(delegateName, DatabaseType.DERBY); JdbcQuartzTestUtilities.shutdownDatabase(delegateName, DatabaseType.DERBY); } @Override public void initialize() throws SQLException { // No-op } } private class FlakyConnectionInvocationHandler implements InvocationHandler { private final Connection delegate; public FlakyConnectionInvocationHandler(Connection delegate) { this.delegate = delegate; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("commit".equals(method.getName())) { preCommitFailure(); method.invoke(delegate, args); postCommitFailure(); return null; } else { return method.invoke(delegate, args); } } } } ================================================ FILE: quartz/src/test/java/org/quartz/InterruptableJobTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import java.util.List; import java.util.Properties; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; import org.quartz.impl.StdSchedulerFactory; /** * Test job interruption */ public class InterruptableJobTest { static final CyclicBarrier sync = new CyclicBarrier(2); public static class TestInterruptableJob implements InterruptableJob { public static final AtomicBoolean interrupted = new AtomicBoolean(false); public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("TestInterruptableJob is executing."); try { sync.await(); // wait for test thread to notice the job is now running } catch (InterruptedException e1) { } catch (BrokenBarrierException e1) { } for(int i=0; i < 200; i++) { try { Thread.sleep(50); // simulate being busy for a while, then checking interrupted flag... } catch (InterruptedException ignore) { } if(TestInterruptableJob.interrupted.get()) { System.out.println("TestInterruptableJob main loop detected interrupt signal."); break; } } try { System.out.println("TestInterruptableJob exiting with interrupted = " + interrupted); sync.await(); } catch (InterruptedException e) { } catch (BrokenBarrierException e) { } } public void interrupt() throws UnableToInterruptJobException { TestInterruptableJob.interrupted.set(true); System.out.println("TestInterruptableJob.interrupt() called."); } } /* @Override protected void setUp() throws Exception { }*/ @Test void testJobInterruption() throws Exception { // create a simple scheduler Properties config = new Properties(); config.setProperty("org.quartz.scheduler.instanceName", "InterruptableJobTest_Scheduler"); config.setProperty("org.quartz.scheduler.instanceId", "AUTO"); config.setProperty("org.quartz.threadPool.threadCount", "2"); config.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); Scheduler sched = new StdSchedulerFactory(config).getScheduler(); sched.start(); // add a job with a trigger that will fire immediately JobDetail job = newJob() .ofType(TestInterruptableJob.class) .withIdentity("j1") .build(); Trigger trigger = newTrigger() .withIdentity("t1") .forJob(job) .startNow() .build(); sched.scheduleJob(job, trigger); sync.await(); // make sure the job starts running... List executingJobs = sched.getCurrentlyExecutingJobs(); assertEquals(1, executingJobs.size(), "Number of executing jobs should be 1 "); JobExecutionContext jec = executingJobs.get(0); boolean interruptResult = sched.interrupt(jec.getFireInstanceId()); sync.await(); // wait for the job to terminate assertTrue(interruptResult, "Expected successful result from interruption of job "); assertTrue(TestInterruptableJob.interrupted.get(), "Expected interrupted flag to be set on job class "); sched.clear(); sched.shutdown(); } } ================================================ FILE: quartz/src/test/java/org/quartz/JdbcSchedulerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.sql.SQLException; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JobStoreTX; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.simpl.SimpleThreadPool; public class JdbcSchedulerTest extends AbstractSchedulerTest { protected DatabaseType getDatabaseType() { return DatabaseType.DERBY; } @Override protected Scheduler createScheduler(String name, int threadPoolSize) throws SchedulerException { try { JdbcQuartzTestUtilities.createDatabase(name + getDatabaseType().name(), getDatabaseType()); } catch (SQLException e) { throw new AssertionError(e); } JobStoreTX jobStore = new JobStoreTX(); jobStore.setDataSource(name + getDatabaseType().name()); jobStore.setTablePrefix("QRTZ_"); jobStore.setInstanceId("AUTO"); DirectSchedulerFactory.getInstance().createScheduler(name + "Scheduler", "AUTO", new SimpleThreadPool(threadPoolSize, Thread.NORM_PRIORITY), jobStore); return SchedulerRepository.getInstance().lookup(name + "Scheduler"); } } ================================================ FILE: quartz/src/test/java/org/quartz/JobBuilderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import static org.quartz.JobBuilder.newJob; import static org.quartz.JobKey.jobKey; /** * Test JobBuilder functionality */ public class JobBuilderTest { @SuppressWarnings("deprecation") public static class TestStatefulJob implements StatefulJob { public void execute(JobExecutionContext context) throws JobExecutionException { } } public static class TestJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { } } @DisallowConcurrentExecution @PersistJobDataAfterExecution public static class TestAnnotatedJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { } } /* @Override protected void setUp() throws Exception { }*/ @Test void testJobBuilder() throws Exception { JobDetail job = newJob() .ofType(TestJob.class) .withIdentity("j1") .storeDurably() .build(); assertEquals("j1", job.getKey().getName(), "Unexpected job name: " + job.getKey().getName()); assertEquals(JobKey.DEFAULT_GROUP, job.getKey().getGroup(), "Unexpected job group: " + job.getKey().getGroup()); assertEquals(job.getKey(), jobKey("j1"), "Unexpected job key: " + job.getKey()); assertNull(job.getDescription(), "Unexpected job description: " + job.getDescription()); assertTrue(job.isDurable(), "Expected isDurable == true "); assertFalse(job.requestsRecovery(), "Expected requestsRecovery == false "); assertFalse(job.isConcurrentExecutionDisallowed(), "Expected isConcurrentExecutionDisallowed == false "); assertFalse(job.isPersistJobDataAfterExecution(), "Expected isPersistJobDataAfterExecution == false "); assertEquals(job.getJobClass(), TestJob.class, "Unexpected job class: " + job.getJobClass()); job = newJob() .ofType(TestAnnotatedJob.class) .withIdentity("j1") .withDescription("my description") .storeDurably(true) .requestRecovery() .build(); assertEquals("my description", job.getDescription(), "Unexpected job description: " + job.getDescription()); assertTrue(job.isDurable(), "Expected isDurable == true "); assertTrue(job.requestsRecovery(), "Expected requestsRecovery == true "); assertTrue(job.isConcurrentExecutionDisallowed(), "Expected isConcurrentExecutionDisallowed == true "); assertTrue(job.isPersistJobDataAfterExecution(), "Expected isPersistJobDataAfterExecution == true "); job = newJob() .ofType(TestStatefulJob.class) .withIdentity("j1", "g1") .requestRecovery(false) .build(); assertEquals("g1", job.getKey().getGroup(), "Unexpected job group: " + job.getKey().getName()); assertFalse(job.isDurable(), "Expected isDurable == false "); assertFalse(job.requestsRecovery(), "Expected requestsRecovery == false "); assertTrue(job.isConcurrentExecutionDisallowed(), "Expected isConcurrentExecutionDisallowed == true "); assertTrue(job.isPersistJobDataAfterExecution(), "Expected isPersistJobDataAfterExecution == true "); } } ================================================ FILE: quartz/src/test/java/org/quartz/JobDataMapTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Unit test for JobDataMap serialization backwards compatibility. */ public class JobDataMapTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"1.4.5", "1.5.1", "2.1"}; /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ @Override protected Object getTargetObject() { JobDataMap m = new JobDataMap(); m.put("key", Integer.valueOf(5)); return m; } /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ @Override protected String[] getVersions() { return VERSIONS; } /** * Verify that the target object and the object we just deserialized * match. */ @SuppressWarnings("deprecation") @Override protected void verifyMatch(Object target, Object deserialized) { JobDataMap targetMap = (JobDataMap)target; JobDataMap deserializedMap = (JobDataMap)deserialized; assertNotNull(deserializedMap); assertEquals(targetMap.getWrappedMap(), deserializedMap.getWrappedMap()); assertEquals(targetMap.getAllowsTransientData(), deserializedMap.getAllowsTransientData()); assertEquals(targetMap.isDirty(), deserializedMap.isDirty()); } public static void main(String[] args) throws Exception { new JobDataMapTest().writeJobDataFile("2.1"); } } ================================================ FILE: quartz/src/test/java/org/quartz/JobDetailTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.junit.jupiter.api.Test; import org.quartz.impl.JobDetailImpl; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for JobDetail. */ public class JobDetailTest { @PersistJobDataAfterExecution public class SomePersistentJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { } } public class SomeExtendedPersistentJob extends SomePersistentJob { } @DisallowConcurrentExecution public class SomeNonConcurrentJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { } } public class SomeExtendedNonConcurrentJob extends SomeNonConcurrentJob { } @DisallowConcurrentExecution @PersistJobDataAfterExecution public class SomeNonConcurrentPersistentJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { } } public class SomeExtendedNonConcurrentPersistentJob extends SomeNonConcurrentPersistentJob { } public class SomeStatefulJob implements StatefulJob { @Override public void execute(JobExecutionContext context) throws JobExecutionException { } } public class SomeExtendedStatefulJob extends SomeStatefulJob { } @Test void testClone() { JobDetailImpl jobDetail = new JobDetailImpl(); jobDetail.setName("hi"); JobDetail clonedJobDetail = (JobDetail)jobDetail.clone(); assertEquals(clonedJobDetail, jobDetail); } @Test void testAnnotationDetection() { JobDetailImpl jobDetail = new JobDetailImpl(); jobDetail.setName("hi"); jobDetail.setJobClass(SomePersistentJob.class); assertTrue(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomePersistentJob to be persistent"); assertFalse(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomePersistentJob to not disallow concurrent execution"); jobDetail.setJobClass(SomeNonConcurrentJob.class); assertFalse(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeNonConcurrentJob to not be persistent"); assertTrue(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeNonConcurrentJob to disallow concurrent execution"); jobDetail.setJobClass(SomeNonConcurrentPersistentJob.class); assertTrue(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeNonConcurrentPersistentJob to be persistent"); assertTrue(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeNonConcurrentPersistentJob to disallow concurrent execution"); jobDetail.setJobClass(SomeStatefulJob.class); assertTrue(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeStatefulJob to be persistent"); assertTrue(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeStatefulJob to disallow concurrent execution"); jobDetail.setJobClass(SomeExtendedPersistentJob.class); assertTrue(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeExtendedPersistentJob to be persistent"); assertFalse(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeExtendedPersistentJob to not disallow concurrent execution"); jobDetail.setJobClass(SomeExtendedNonConcurrentJob.class); assertFalse(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeExtendedNonConcurrentJob to not be persistent"); assertTrue(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeExtendedNonConcurrentJob to disallow concurrent execution"); jobDetail.setJobClass(SomeExtendedNonConcurrentPersistentJob.class); assertTrue(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeExtendedNonConcurrentPersistentJob to be persistent"); assertTrue(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeExtendedNonConcurrentPersistentJob to disallow concurrent execution"); jobDetail.setJobClass(SomeExtendedStatefulJob.class); assertTrue(jobDetail.isPersistJobDataAfterExecution(), "Expecting SomeExtendedStatefulJob to be persistent"); assertTrue(jobDetail.isConcurrentExecutionDisallowed(), "Expecting SomeExtendedStatefulJob to disallow concurrent execution"); } } ================================================ FILE: quartz/src/test/java/org/quartz/MonthlyCalendarTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import org.junit.jupiter.api.Test; import org.quartz.impl.calendar.MonthlyCalendar; /** * Unit test for MonthlyCalendar */ public class MonthlyCalendarTest { /** * Tests whether greater than the 7th of the month causes infinite looping. * See: QUARTZ-636 */ @Test void testForInfiniteLoop() { MonthlyCalendar monthlyCalendar = new MonthlyCalendar(); for(int i=1; i<9; i++) { monthlyCalendar.setDayExcluded(i, true); } Calendar c = Calendar.getInstance(); c.set(2007, 11, 8, 12, 0, 0); monthlyCalendar.getNextIncludedTime(c.getTime().getTime()); } } ================================================ FILE: quartz/src/test/java/org/quartz/PriorityTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Calendar; import java.util.Properties; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.quartz.impl.JobDetailImpl; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.MutableTrigger; import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test Trigger priority support. */ class PriorityTest { private static CountDownLatch latch; private static StringBuffer result; @SuppressWarnings("deprecation") public static class TestJob implements StatefulJob { public void execute(JobExecutionContext context) throws JobExecutionException { result.append(context.getTrigger().getKey().getName()); latch.countDown(); } } @BeforeEach protected void setUp() { PriorityTest.latch = new CountDownLatch(2); PriorityTest.result = new StringBuffer(); } @SuppressWarnings("deprecation") @Test void testSameDefaultPriority() throws Exception { Properties config = new Properties(); config.setProperty("org.quartz.threadPool.threadCount", "1"); config.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); Scheduler sched = new StdSchedulerFactory(config).getScheduler(); Calendar cal = Calendar.getInstance(); cal.add(Calendar.SECOND, 1); MutableTrigger trig1 = new SimpleTriggerImpl("T1", null, cal.getTime()); MutableTrigger trig2 = new SimpleTriggerImpl("T2", null, cal.getTime()); JobDetail jobDetail = new JobDetailImpl("JD", null, TestJob.class); sched.scheduleJob(jobDetail, trig1); trig2.setJobKey(new JobKey(jobDetail.getKey().getName())); sched.scheduleJob(trig2); sched.start(); latch.await(); assertEquals("T1T2", result.toString()); sched.shutdown(); } @SuppressWarnings("deprecation") @Test void testDifferentPriority() throws Exception { Properties config = new Properties(); config.setProperty("org.quartz.threadPool.threadCount", "1"); config.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); Scheduler sched = new StdSchedulerFactory(config).getScheduler(); Calendar cal = Calendar.getInstance(); cal.add(Calendar.SECOND, 1); MutableTrigger trig1 = new SimpleTriggerImpl("T1", null, cal.getTime()); trig1.setPriority(5); MutableTrigger trig2 = new SimpleTriggerImpl("T2", null, cal.getTime()); trig2.setPriority(10); JobDetail jobDetail = new JobDetailImpl("JD", null, TestJob.class); sched.scheduleJob(jobDetail, trig1); trig2.setJobKey(new JobKey(jobDetail.getKey().getName(), null)); sched.scheduleJob(trig2); sched.start(); latch.await(); assertEquals("T2T1", result.toString()); sched.shutdown(); } } ================================================ FILE: quartz/src/test/java/org/quartz/Qtz205SchedulerListenerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.util.Properties; import org.junit.jupiter.api.Test; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A unit test to reproduce QTZ-205 bug: * A TriggerListener vetoed job will affect SchedulerListener's triggerFinalized() notification. * * @author Zemian Deng */ class Qtz205SchedulerListenerTest { private static Logger logger = LoggerFactory.getLogger(Qtz205SchedulerListenerTest.class); public static class Qtz205Job implements Job { private static volatile int jobExecutionCount = 0; public void execute(JobExecutionContext context) throws JobExecutionException { jobExecutionCount++; logger.info("Job executed. jobExecutionCount=" + jobExecutionCount); } } public static class Qtz205TriggerListener implements TriggerListener { private volatile int fireCount; public int getFireCount() { return fireCount; } public String getName() { return "Qtz205TriggerListener"; } public void triggerFired(Trigger trigger, JobExecutionContext context) { fireCount++; logger.info("Trigger fired. count " + fireCount); } public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { if (fireCount >= 3) { logger.info("Job execution vetoed."); return true; } else { return false; } } public void triggerMisfired(Trigger trigger) { } public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) { } } public static class Qtz205ScheListener implements SchedulerListener { private int triggerFinalizedCount; public int getTriggerFinalizedCount() { return triggerFinalizedCount; } public void jobScheduled(Trigger trigger) { } public void jobUnscheduled(TriggerKey triggerKey) { } public void triggerFinalized(Trigger trigger) { triggerFinalizedCount ++; logger.info("triggerFinalized " + trigger); } public void triggerPaused(TriggerKey triggerKey) { } public void triggersPaused(String triggerGroup) { } public void triggerResumed(TriggerKey triggerKey) { } public void triggersResumed(String triggerGroup) { } public void jobAdded(JobDetail jobDetail) { } public void jobDeleted(JobKey jobKey) { } public void jobPaused(JobKey jobKey) { } public void jobsPaused(String jobGroup) { } public void jobResumed(JobKey jobKey) { } public void jobsResumed(String jobGroup) { } public void schedulerError(String msg, SchedulerException cause) { } public void schedulerInStandbyMode() { } public void schedulerStarted() { } public void schedulerStarting() { } public void schedulerShutdown() { } public void schedulerShuttingdown() { } public void schedulingDataCleared() { } } /** QTZ-205 */ @Test void testTriggerFinalized() throws Exception { Qtz205TriggerListener triggerListener = new Qtz205TriggerListener(); Qtz205ScheListener schedulerListener = new Qtz205ScheListener(); Properties props = new Properties(); props.setProperty("org.quartz.scheduler.idleWaitTime", "1500"); props.setProperty("org.quartz.threadPool.threadCount", "2"); Scheduler scheduler = new StdSchedulerFactory(props).getScheduler(); scheduler.getListenerManager().addSchedulerListener(schedulerListener); scheduler.getListenerManager().addTriggerListener(triggerListener); scheduler.start(); scheduler.standby(); JobDetail job = newJob(Qtz205Job.class).withIdentity("test").build(); Trigger trigger = newTrigger().withIdentity("test") .withSchedule(simpleSchedule().withIntervalInMilliseconds(250).withRepeatCount(2)) .build(); scheduler.scheduleJob(job, trigger); scheduler.start(); Thread.sleep(5000); scheduler.shutdown(true); assertEquals(2, Qtz205Job.jobExecutionCount); assertEquals(3, triggerListener.getFireCount()); assertEquals(1, schedulerListener.getTriggerFinalizedCount()); } } ================================================ FILE: quartz/src/test/java/org/quartz/Quartz601Test.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import org.junit.jupiter.api.Test; import java.text.ParseException; import java.util.Set; import static org.junit.jupiter.api.Assertions.fail; class Quartz601Test { @Test void testNormal() { for(int i=0; i<6; i++) { assertParsesForField("0 15 10 * * ? 2005", i); } } void testSecond() { assertParsesForField("58-4 5 21 ? * MON-FRI", 0); } void testMinute() { assertParsesForField("0 58-4 21 ? * MON-FRI", 1); } void testHour() { assertParsesForField("0 0/5 21-3 ? * MON-FRI", 2); } void testDayOfWeekNumber() { assertParsesForField("58 5 21 ? * 6-2", 5); } void testDayOfWeek() { assertParsesForField("58 5 21 ? * FRI-TUE", 5); } void testDayOfMonth() { assertParsesForField("58 5 21 28-5 1 ?", 3); } void testMonth() { assertParsesForField("58 5 21 ? 11-2 FRI", 4); } void testAmbiguous() { assertParsesForField("0 0 14-6 ? * FRI-MON", 2); assertParsesForField("0 0 14-6 ? * FRI-MON", 5); assertParsesForField("55-3 56-2 6 ? * FRI", 0); assertParsesForField("55-3 56-2 6 ? * FRI", 1); } private void assertParsesForField(String expression, int constant) { try { CronExpression cronExpression = new CronExpression(expression); Set set = cronExpression.getSet(constant); assert set != null; if(set.isEmpty()) { fail("Empty field ["+constant+"] returned for " + expression); } } catch(ParseException pe) { fail("Exception thrown during parsing: " + pe); } } } ================================================ FILE: quartz/src/test/java/org/quartz/RAMSchedulerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.Properties; import org.quartz.impl.StdSchedulerFactory; public class RAMSchedulerTest extends AbstractSchedulerTest { @Override protected Scheduler createScheduler(String name, int threadPoolSize) throws SchedulerException { Properties config = new Properties(); config.setProperty("org.quartz.scheduler.instanceName", name + "Scheduler"); config.setProperty("org.quartz.scheduler.instanceId", "AUTO"); config.setProperty("org.quartz.threadPool.threadCount", Integer.toString(threadPoolSize)); config.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); return new StdSchedulerFactory(config).getScheduler(); } } ================================================ FILE: quartz/src/test/java/org/quartz/SerializationTestSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * Base class for unit tests that wish to verify backwards compatibility of serialization with earlier versions * of Quartz. * *

The way to properly setup tests for subclass is it needs to generate a .ser * resource file under the same package. This ".ser" file only needs to be generated one time, * using the version of Quartz matching to the VERSION values. Then during test, each of this * file will be deserialized to verify the data.

*/ public abstract class SerializationTestSupport { /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ protected abstract Object getTargetObject() throws Exception; /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ protected abstract String[] getVersions(); /** * Verify that the target object and the object we just deserialized * match. */ protected abstract void verifyMatch(Object target, Object deserialized); /** * Test that we can successfully deserialize our target * class for all of the given Quartz versions. */ void testSerialization() throws Exception { Object targetObject = getTargetObject(); for (int i = 0; i < getVersions().length; i++) { String version = getVersions()[i]; verifyMatch( targetObject, deserialize(version, targetObject.getClass())); } } /** * Deserialize the target object from disk. */ protected Object deserialize(String version, Class clazz) throws Exception { InputStream is = getClass().getResourceAsStream(getSerializedFileName(version, clazz)); ObjectInputStream ois = new ObjectInputStream(is); Object obj = (Object)ois.readObject(); ois.close(); is.close(); return obj; } /** * Use this method in the future to generate other versions of * of the serialized object file. */ public void writeJobDataFile(String version) throws Exception { Object obj = getTargetObject(); FileOutputStream fos = new FileOutputStream(getSerializedFileName(version, obj.getClass())); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(obj); oos.flush(); fos.close(); oos.close(); } /** * Generate the expected name of the serialized object file. */ private String getSerializedFileName(String version, Class clazz) { String className = clazz.getName(); int index = className.lastIndexOf("."); index = (index < 0) ? 0 : index + 1; return className.substring(index) + "-" + version + ".ser"; } } ================================================ FILE: quartz/src/test/java/org/quartz/SimpleTriggerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import org.junit.jupiter.api.Test; import org.quartz.impl.triggers.SimpleTriggerImpl; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for SimpleTrigger serialization backwards compatibility. */ public class SimpleTriggerTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"2.0"}; private static final TimeZone EST_TIME_ZONE = TimeZone.getTimeZone("US/Eastern"); private static final Calendar START_TIME = Calendar.getInstance(); private static final Calendar END_TIME = Calendar.getInstance(); static { START_TIME.clear(); START_TIME.set(2006, Calendar.JUNE, 1, 10, 5, 15); START_TIME.setTimeZone(EST_TIME_ZONE); END_TIME.clear(); END_TIME.set(2008, Calendar.MAY, 2, 20, 15, 30); END_TIME.setTimeZone(EST_TIME_ZONE); } /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ @SuppressWarnings("deprecation") @Override protected Object getTargetObject() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("A", "B"); SimpleTriggerImpl t = new SimpleTriggerImpl("SimpleTrigger", "SimpleGroup", "JobName", "JobGroup", START_TIME.getTime(), END_TIME.getTime(), 5, 1000); t.setCalendarName("MyCalendar"); t.setDescription("SimpleTriggerDesc"); t.setJobDataMap(jobDataMap); t.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT); return t; } /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ @Override protected String[] getVersions() { return VERSIONS; } /** * Verify that the target object and the object we just deserialized * match. */ @Override protected void verifyMatch(Object target, Object deserialized) { SimpleTriggerImpl targetSimpleTrigger = (SimpleTriggerImpl)target; SimpleTriggerImpl deserializedSimpleTrigger = (SimpleTriggerImpl)deserialized; assertNotNull(deserializedSimpleTrigger); assertEquals(targetSimpleTrigger.getName(), deserializedSimpleTrigger.getName()); assertEquals(targetSimpleTrigger.getGroup(), deserializedSimpleTrigger.getGroup()); assertEquals(targetSimpleTrigger.getJobName(), deserializedSimpleTrigger.getJobName()); assertEquals(targetSimpleTrigger.getJobGroup(), deserializedSimpleTrigger.getJobGroup()); assertEquals(targetSimpleTrigger.getStartTime(), deserializedSimpleTrigger.getStartTime()); assertEquals(targetSimpleTrigger.getEndTime(), deserializedSimpleTrigger.getEndTime()); assertEquals(targetSimpleTrigger.getRepeatCount(), deserializedSimpleTrigger.getRepeatCount()); assertEquals(targetSimpleTrigger.getRepeatInterval(), deserializedSimpleTrigger.getRepeatInterval()); assertEquals(targetSimpleTrigger.getCalendarName(), deserializedSimpleTrigger.getCalendarName()); assertEquals(targetSimpleTrigger.getDescription(), deserializedSimpleTrigger.getDescription()); assertEquals(targetSimpleTrigger.getJobDataMap(), deserializedSimpleTrigger.getJobDataMap()); assertEquals(targetSimpleTrigger.getMisfireInstruction(), deserializedSimpleTrigger.getMisfireInstruction()); } @Test void testUpdateAfterMisfire() { Calendar startTime = Calendar.getInstance(); startTime.set(2005, Calendar.JULY, 5, 9, 0, 0); Calendar endTime = Calendar.getInstance(); endTime.set(2005, Calendar.JULY, 5, 10, 0, 0); SimpleTriggerImpl simpleTrigger = new SimpleTriggerImpl(); simpleTrigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT); simpleTrigger.setRepeatCount(5); simpleTrigger.setStartTime(startTime.getTime()); simpleTrigger.setEndTime(endTime.getTime()); simpleTrigger.updateAfterMisfire(null); assertEquals(startTime.getTime(), simpleTrigger.getStartTime()); assertEquals(endTime.getTime(), simpleTrigger.getEndTime()); assertNull(simpleTrigger.getNextFireTime()); } @Test void testGetFireTimeAfter() { SimpleTriggerImpl simpleTrigger = new SimpleTriggerImpl(); simpleTrigger.setStartTime(new Date(0)); simpleTrigger.setRepeatInterval(10); simpleTrigger.setRepeatCount(4); Date fireTimeAfter = simpleTrigger.getFireTimeAfter(new Date(34)); assertEquals(40, fireTimeAfter.getTime()); } @Test void testClone() { SimpleTriggerImpl simpleTrigger = new SimpleTriggerImpl(); // Make sure empty sub-objects are cloned okay Trigger clone = (Trigger)simpleTrigger.clone(); assertEquals(0, clone.getJobDataMap().size()); // Make sure non-empty sub-objects are cloned okay simpleTrigger.getJobDataMap().put("K1", "V1"); simpleTrigger.getJobDataMap().put("K2", "V2"); clone = (Trigger)simpleTrigger.clone(); assertEquals(2, clone.getJobDataMap().size()); assertEquals("V1", clone.getJobDataMap().get("K1")); assertEquals("V2", clone.getJobDataMap().get("K2")); // Make sure sub-object collections have really been cloned by ensuring // their modification does not change the source Trigger clone.getJobDataMap().remove("K1"); assertEquals(1, clone.getJobDataMap().size()); assertEquals(2, simpleTrigger.getJobDataMap().size()); assertEquals("V1", simpleTrigger.getJobDataMap().get("K1")); assertEquals("V2", simpleTrigger.getJobDataMap().get("K2")); } // NPE in equals() @Test void testQuartz665() { new SimpleTriggerImpl().equals(new SimpleTriggerImpl()); } @Test void testMisfireInstructionValidity() { SimpleTriggerImpl trigger = new SimpleTriggerImpl(); try { trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT); } catch(Exception e) { fail("Unexpected exception while setting misfire instruction: " + e.getMessage()); } try { trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT + 1); fail("Expected exception while setting invalid misfire instruction but did not get it."); } catch(Exception e) { } } // execute with version number to generate a new version's serialized form public static void main(String[] args) throws Exception { new SimpleTriggerTest().writeJobDataFile("2.0"); } } ================================================ FILE: quartz/src/test/java/org/quartz/TriggerBuilderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.*; import static org.quartz.DateBuilder.evenSecondDateAfterNow; import static org.quartz.DateBuilder.futureDate; import static org.quartz.TriggerBuilder.newTrigger; import java.time.Instant; import java.util.Date; import org.junit.jupiter.api.Test; import org.quartz.DateBuilder.IntervalUnit; /** * Test TriggerBuilder functionality */ public class TriggerBuilderTest { @SuppressWarnings("deprecation") public static class TestStatefulJob implements StatefulJob { public void execute(JobExecutionContext context) throws JobExecutionException { } } public static class TestJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { } } @DisallowConcurrentExecution @PersistJobDataAfterExecution public static class TestAnnotatedJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { } } /* @Override protected void setUp() throws Exception { }*/ @Test void testTriggerBuilder() { Trigger trigger = newTrigger() .build(); assertNotNull(trigger.getKey().getName(), "Expected non-null trigger name "); assertEquals(JobKey.DEFAULT_GROUP, trigger.getKey().getGroup(), "Unexpected trigger group: " + trigger.getKey().getGroup()); assertNull(trigger.getJobKey(), "Unexpected job key: " + trigger.getJobKey()); assertNull(trigger.getDescription(), "Unexpected job description: " + trigger.getDescription()); assertEquals(Trigger.DEFAULT_PRIORITY, trigger.getPriority(), "Unexpected trigger priority: " + trigger.getPriority()); assertNotNull(trigger.getStartTime(), "Unexpected start-time: " + trigger.getStartTime()); assertNull(trigger.getEndTime(), "Unexpected end-time: " + trigger.getEndTime()); Date stime = evenSecondDateAfterNow(); trigger = newTrigger() .withIdentity("t1") .withDescription("my description") .withPriority(2) .endAt(futureDate(10, IntervalUnit.WEEK)) .startAt(stime) .build(); assertEquals("t1", trigger.getKey().getName(), "Unexpected trigger name " + trigger.getKey().getName()); assertEquals(JobKey.DEFAULT_GROUP, trigger.getKey().getGroup(), "Unexpected trigger group: " + trigger.getKey().getGroup()); assertNull(trigger.getJobKey(), "Unexpected job key: " + trigger.getJobKey()); assertEquals("my description", trigger.getDescription(), "Unexpected job description: " + trigger.getDescription()); assertEquals(2, trigger.getPriority(), "Unexpected trigger priority: " + trigger); assertEquals(trigger.getStartTime(), stime, "Unexpected start-time: " + trigger.getStartTime()); assertNotNull(trigger.getEndTime(), "Unexpected end-time: " + trigger.getEndTime()); } /** QTZ-157 */ @Test void testTriggerBuilderWithEndTimePriorCurrentTime() throws Exception { TriggerBuilder.newTrigger() .withIdentity("some trigger name", "some trigger group") .forJob("some job name", "some job group") .startAt(new Date(System.currentTimeMillis() - 200000000)) .endAt(new Date(System.currentTimeMillis() - 100000000)) .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")) .build(); } @Test void testTriggerBuilderWithInstant() throws InterruptedException { Instant instantTime = Instant.now().plusSeconds(3L); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("triggerTest Instant", "triggerTest Instant group") .forJob("test job Instant", "test job Instant group") .startAt(instantTime) .build(); assertEquals(Date.from(instantTime), trigger.getStartTime()); Thread.sleep(5000); assertEquals(Date.from(instantTime), trigger.getFinalFireTime()); } } ================================================ FILE: quartz/src/test/java/org/quartz/TriggerComparatorTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.quartz.DateBuilder.futureDate; import static org.quartz.DateBuilder.IntervalUnit.MINUTE; import static org.quartz.TriggerBuilder.newTrigger; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.Test; import org.quartz.spi.OperableTrigger; class TriggerComparatorTest { @Test void testTriggerSort() { // build trigger in expected sort order Trigger t1 = newTrigger().withIdentity("a").build(); Trigger t2 = newTrigger().withIdentity("b").build(); Trigger t3 = newTrigger().withIdentity("c").build(); Trigger t4 = newTrigger().withIdentity("a", "a").build(); Trigger t5 = newTrigger().withIdentity("a", "b").build(); Trigger t6 = newTrigger().withIdentity("a", "c").build(); List ts = new LinkedList(); // add triggers to list in somewhat randomized order ts.add(t5); ts.add(t6); ts.add(t4); ts.add(t3); ts.add(t1); ts.add(t2); // sort the list Collections.sort(ts); // check the order of the list assertEquals(t1, ts.get(0)); assertEquals(t2, ts.get(1)); assertEquals(t3, ts.get(2)); assertEquals(t4, ts.get(3)); assertEquals(t5, ts.get(4)); assertEquals(t6, ts.get(5)); } @Test void testTriggerTimeSort() { // build trigger in expected sort order Trigger t1 = newTrigger().withIdentity("a").startAt(futureDate(1, MINUTE)).build(); ((OperableTrigger)t1).computeFirstFireTime(null); Trigger t2 = newTrigger().withIdentity("b").startAt(futureDate(2, MINUTE)).build(); ((OperableTrigger)t2).computeFirstFireTime(null); Trigger t3 = newTrigger().withIdentity("c").startAt(futureDate(3, MINUTE)).build(); ((OperableTrigger)t3).computeFirstFireTime(null); Trigger t4 = newTrigger().withIdentity("d").startAt(futureDate(5, MINUTE)).withPriority(7).build(); ((OperableTrigger)t4).computeFirstFireTime(null); Trigger t5 = newTrigger().withIdentity("e").startAt(futureDate(5, MINUTE)).build(); ((OperableTrigger)t5).computeFirstFireTime(null); Trigger t6 = newTrigger().withIdentity("g").startAt(futureDate(5, MINUTE)).build(); ((OperableTrigger)t6).computeFirstFireTime(null); Trigger t7 = newTrigger().withIdentity("h").startAt(futureDate(5, MINUTE)).withPriority(2).build(); ((OperableTrigger)t7).computeFirstFireTime(null); Trigger t8 = newTrigger().withIdentity("i").startAt(futureDate(6, MINUTE)).build(); ((OperableTrigger)t8).computeFirstFireTime(null); Trigger t9 = newTrigger().withIdentity("j").startAt(futureDate(7, MINUTE)).build(); ((OperableTrigger)t9).computeFirstFireTime(null); List ts = new LinkedList(); // add triggers to list in somewhat randomized order ts.add(t5); ts.add(t9); ts.add(t6); ts.add(t8); ts.add(t4); ts.add(t3); ts.add(t1); ts.add(t7); ts.add(t2); // sort the list Collections.sort(ts); // check the order of the list assertEquals(t1, ts.get(0)); assertEquals(t2, ts.get(1)); assertEquals(t3, ts.get(2)); assertEquals(t4, ts.get(3)); assertEquals(t5, ts.get(4)); assertEquals(t6, ts.get(5)); assertEquals(t7, ts.get(6)); assertEquals(t8, ts.get(7)); assertEquals(t9, ts.get(8)); } } ================================================ FILE: quartz/src/test/java/org/quartz/VersionTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz; import java.util.regex.Pattern; import java.util.regex.Matcher; import org.junit.jupiter.api.Test; import org.quartz.core.QuartzScheduler; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class VersionTest { @SuppressWarnings("unused") private static final String SNAPSHOT_SUFFIX = "-SNAPSHOT"; @SuppressWarnings("unused") private static final String PROTOTYPE_SUFFIX = "-PROTO"; @Test void testVersionParsing() { assertNonNegativeInteger(QuartzScheduler.getVersionMajor()); assertNonNegativeInteger(QuartzScheduler.getVersionMinor()); String iter = QuartzScheduler.getVersionIteration(); assertNotNull(iter); Pattern suffix = Pattern.compile("(\\d+)(-\\w+)?"); Matcher m = suffix.matcher(iter); if (m.matches()) { assertNonNegativeInteger(m.group(1)); } else { throw new RuntimeException(iter + " doesn't match pattern '(\\d+)(-\\w+)?'"); } } private void assertNonNegativeInteger(String s) { assertNotNull(s); boolean parsed = false; int intVal = -1; try { intVal = Integer.parseInt(s); parsed = true; } catch (NumberFormatException e) {} assertTrue(parsed, "Failed parse version segment: " + s); assertTrue(intVal >= 0); } } ================================================ FILE: quartz/src/test/java/org/quartz/core/ListenerManagerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals; import static org.quartz.impl.matchers.GroupMatcher.triggerGroupEquals; import static org.quartz.impl.matchers.NameMatcher.jobNameContains; import java.util.List; import java.util.UUID; import org.junit.jupiter.api.Test; import org.quartz.JobListener; import org.quartz.SchedulerListener; import org.quartz.TriggerKey; import org.quartz.TriggerListener; import org.quartz.impl.matchers.NameMatcher; import org.quartz.listeners.JobListenerSupport; import org.quartz.listeners.SchedulerListenerSupport; import org.quartz.listeners.TriggerListenerSupport; /** * Test ListenerManagerImpl functionality */ class ListenerManagerTest { public static class TestJobListener extends JobListenerSupport { private String name; public TestJobListener(String name) { this.name = name; } public String getName() { return name; } } public static class TestTriggerListener extends TriggerListenerSupport { private String name; public TestTriggerListener(String name) { this.name = name; } public String getName() { return name; } } public static class TestSchedulerListener extends SchedulerListenerSupport { } @Test void testManagementOfJobListeners() throws Exception { JobListener tl1 = new TestJobListener("tl1"); JobListener tl2 = new TestJobListener("tl2"); ListenerManagerImpl manager = new ListenerManagerImpl(); // test adding listener without matcher manager.addJobListener(tl1); assertEquals(1, manager.getJobListeners().size(), "Unexpected size of listener list"); // test adding listener with matcher manager.addJobListener(tl2, jobGroupEquals("foo")); assertEquals(2, manager.getJobListeners().size(), "Unexpected size of listener list"); // test removing a listener manager.removeJobListener("tl1"); assertEquals(1, manager.getJobListeners().size(), "Unexpected size of listener list"); // test adding a matcher manager.addJobListenerMatcher("tl2", jobNameContains("foo")); assertEquals(2, manager.getJobListenerMatchers("tl2").size(), "Unexpected size of listener's matcher list"); // Test ordering of registration is preserved. final int numListenersToTestOrderOf = 15; manager = new ListenerManagerImpl(); JobListener[] listeners = new JobListener[numListenersToTestOrderOf]; for(int i = 0; i < numListenersToTestOrderOf; i++) { // use random name, to help test that order isn't based on naming or coincidental hashing listeners[i] = new TestJobListener(UUID.randomUUID().toString()); manager.addJobListener(listeners[i]); } List mls = manager.getJobListeners(); int i = 0; for(JobListener listener: mls) { assertSame(listeners[i], listener, "Unexpected order of listeners"); i++; } } @Test void testManagementOfTriggerListeners() throws Exception { TriggerListener tl1 = new TestTriggerListener("tl1"); TriggerListener tl2 = new TestTriggerListener("tl2"); ListenerManagerImpl manager = new ListenerManagerImpl(); // test adding listener without matcher manager.addTriggerListener(tl1); assertEquals(1, manager.getTriggerListeners().size(), "Unexpected size of listener list"); // test adding listener with matcher manager.addTriggerListener(tl2, triggerGroupEquals("foo")); assertEquals(2, manager.getTriggerListeners().size(), "Unexpected size of listener list"); // test removing a listener manager.removeTriggerListener("tl1"); assertEquals(1, manager.getTriggerListeners().size(), "Unexpected size of listener list"); // test adding a matcher manager.addTriggerListenerMatcher("tl2", NameMatcher.nameContains("foo")); assertEquals(2, manager.getTriggerListenerMatchers("tl2").size(), "Unexpected size of listener's matcher list"); // Test ordering of registration is preserved. final int numListenersToTestOrderOf = 15; manager = new ListenerManagerImpl(); TriggerListener[] listeners = new TriggerListener[numListenersToTestOrderOf]; for(int i = 0; i < numListenersToTestOrderOf; i++) { // use random name, to help test that order isn't based on naming or coincidental hashing listeners[i] = new TestTriggerListener(UUID.randomUUID().toString()); manager.addTriggerListener(listeners[i]); } List mls = manager.getTriggerListeners(); int i = 0; for(TriggerListener listener: mls) { assertSame(listeners[i], listener, "Unexpected order of listeners"); i++; } } @Test void testManagementOfSchedulerListeners() throws Exception { SchedulerListener tl1 = new TestSchedulerListener(); SchedulerListener tl2 = new TestSchedulerListener(); ListenerManagerImpl manager = new ListenerManagerImpl(); // test adding listener without matcher manager.addSchedulerListener(tl1); assertEquals(1, manager.getSchedulerListeners().size(), "Unexpected size of listener list"); // test adding listener with matcher manager.addSchedulerListener(tl2); assertEquals(2, manager.getSchedulerListeners().size(), "Unexpected size of listener list"); // test removing a listener manager.removeSchedulerListener(tl1); assertEquals(1, manager.getSchedulerListeners().size(), "Unexpected size of listener list"); // Test ordering of registration is preserved. final int numListenersToTestOrderOf = 15; manager = new ListenerManagerImpl(); SchedulerListener[] listeners = new SchedulerListener[numListenersToTestOrderOf]; for (int i = 0; i < numListenersToTestOrderOf; i++) { listeners[i] = new TestSchedulerListener(); manager.addSchedulerListener(listeners[i]); } List mls = manager.getSchedulerListeners(); int i = 0; for (SchedulerListener listener : mls) { assertSame(listeners[i], listener, "Unexpected order of listeners"); i++; } } } ================================================ FILE: quartz/src/test/java/org/quartz/core/QTZ212_SchedulerListener_Test.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SchedulerListener; import org.quartz.impl.StdSchedulerFactory; import org.quartz.listeners.BroadcastSchedulerListener; import org.quartz.listeners.SchedulerListenerSupport; import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test that verifies that schedulerStarting() is called before the schedulerStarted() * * * @author adahanne * */ class QTZ212_SchedulerListener_Test { private static final String SCHEDULER_STARTED = "SCHEDULER_STARTED"; private static final String SCHEDULER_STARTING = "SCHEDULER_STARTING"; private static List methodsCalledInSchedulerListener = new ArrayList(); @Test void stdSchedulerCallsStartingBeforeStartedTest() throws SchedulerException { SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); sched.getListenerManager().addSchedulerListener(new TestSchedulerListener()); sched.start(); assertEquals(SCHEDULER_STARTING,methodsCalledInSchedulerListener.get(0)); assertEquals(SCHEDULER_STARTED,methodsCalledInSchedulerListener.get(1)); sched.shutdown(); } @Test void broadcastSchedulerListenerCallsSchedulerStartingOnAllItsListeners() throws SchedulerException { methodsCalledInSchedulerListener = new ArrayList(); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); List listeners= new ArrayList(); listeners.add(new TestSchedulerListener()); sched.getListenerManager().addSchedulerListener(new BroadcastSchedulerListener(listeners)); sched.start(); assertEquals(SCHEDULER_STARTING,methodsCalledInSchedulerListener.get(0)); assertEquals(SCHEDULER_STARTED,methodsCalledInSchedulerListener.get(1)); sched.shutdown(); } public static class TestSchedulerListener extends SchedulerListenerSupport { @Override public void schedulerStarted() { methodsCalledInSchedulerListener.add(SCHEDULER_STARTED); System.out.println("schedulerStarted was called"); } @Override public void schedulerStarting() { methodsCalledInSchedulerListener.add(SCHEDULER_STARTING); System.out.println("schedulerStarting was called"); } } } ================================================ FILE: quartz/src/test/java/org/quartz/core/QTZ385Test.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.SQLException; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; import org.quartz.JobBuilder; import org.quartz.JobExecutionContext; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.TriggerBuilder; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JobStoreTX; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.integrations.tests.HelloJob; import org.quartz.listeners.JobListenerSupport; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.JobStore; import static org.junit.jupiter.api.Assertions.assertFalse; /** * * @author cdennis */ class QTZ385Test { private static final Method TRIGGERS_FIRED; static { try { TRIGGERS_FIRED = JobStore.class.getDeclaredMethod("triggersFired", new Class[] {List.class}); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } @Test void testShutdownOrdering() throws SchedulerException, SQLException, InterruptedException, BrokenBarrierException { JdbcQuartzTestUtilities.createDatabase("testShutdownOrdering", DatabaseType.DERBY); try { final CyclicBarrier barrier = new CyclicBarrier(2); final JobStoreTX realJobStore = new JobStoreTX(); realJobStore.setDataSource("testShutdownOrdering"); realJobStore.setInstanceId("SINGLE_NODE_TEST"); realJobStore.setInstanceName("testShutdownOrdering"); JobStore evilJobStore = (JobStore) Proxy.newProxyInstance(JobStore.class.getClassLoader(), new Class[] {JobStore.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (TRIGGERS_FIRED.equals(method)) { Object result = method.invoke(realJobStore, args); barrier.await(); try { barrier.await(1, TimeUnit.SECONDS); } catch (Exception e) { //ignore } return result; } else { return method.invoke(realJobStore, args); } } }); DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance(); factory.createScheduler(new SimpleThreadPool(1, Thread.NORM_PRIORITY), evilJobStore); Scheduler scheduler = factory.getScheduler(); try { scheduler.scheduleJob(JobBuilder.newJob(HelloJob.class).withIdentity("test").requestRecovery().build(), TriggerBuilder.newTrigger().withIdentity("test").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInMilliseconds(1)).build()); scheduler.start(); barrier.await(); } finally { scheduler.shutdown(true); } try { barrier.await(1, TimeUnit.SECONDS); } catch (Exception e) { //ignore } final AtomicBoolean recoveredJob = new AtomicBoolean(false); factory.createScheduler(new SimpleThreadPool(1, Thread.NORM_PRIORITY), realJobStore); Scheduler recovery = factory.getScheduler(); try { recovery.getListenerManager().addJobListener(new JobListenerSupport() { @Override public String getName() { return QTZ385Test.class.getSimpleName(); } @Override public void jobToBeExecuted(JobExecutionContext context) { if (context.isRecovering()) { recoveredJob.set(true); } } }); recovery.start(); Thread.sleep(1000); assertFalse(recoveredJob.get()); } finally { recovery.shutdown(true); } } finally { JdbcQuartzTestUtilities.destroyDatabase("testShutdownOrdering", DatabaseType.DERBY); } } } ================================================ FILE: quartz/src/test/java/org/quartz/core/RecoverJobsTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.core; import org.junit.jupiter.api.Test; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JobStoreTX; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.*; import org.quartz.listeners.JobListenerSupport; import org.quartz.simpl.SimpleThreadPool; import org.quartz.utils.DBConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author https://github.com/eugene-goroschenya */ public class RecoverJobsTest { @Test void testRecoveringRepeatJobWhichIsFiredAndMisfiredAtTheSameTime() throws SchedulerException, SQLException, InterruptedException { String dsName = "recoverJobsTest"; JdbcQuartzTestUtilities.createDatabase(dsName, DatabaseType.DERBY); try { final JobStoreTX jobStore = new JobStoreTX(); jobStore.setDataSource(dsName); jobStore.setInstanceId("SINGLE_NODE_TEST"); jobStore.setInstanceName(dsName); jobStore.setMisfireThreshold(1000); DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance(); factory.createScheduler(new SimpleThreadPool(1, Thread.NORM_PRIORITY), jobStore); Scheduler scheduler = factory.getScheduler(); // run forever up to the first fail over situation RecoverJobsTestJob.runForever = true; scheduler.scheduleJob( JobBuilder.newJob(RecoverJobsTestJob.class) .withIdentity("test") .build(), TriggerBuilder.newTrigger() .withIdentity("test") .withSchedule( SimpleScheduleBuilder.simpleSchedule() .withIntervalInMilliseconds(1000) .repeatForever() ).build() ); scheduler.start(); // wait to be sure job is executing Thread.sleep(2000); // emulate fail over situation scheduler.shutdown(false); Connection conn = DBConnectionManager.getInstance().getConnection(dsName); try { Statement st = conn.createStatement(); ResultSet rs1 = st.executeQuery("SELECT TRIGGER_STATE from QRTZ_TRIGGERS"); rs1.next(); // check that trigger is blocked after fail over situation assertEquals("BLOCKED", rs1.getString(1)); ResultSet rs2 = st.executeQuery("SELECT count(*) from QRTZ_FIRED_TRIGGERS"); rs2.next(); // check that fired trigger remains after fail over situation assertEquals(1, rs2.getLong(1)); st.close(); } finally { conn.close(); } // stop job executing to not as part of emulation fail over situation RecoverJobsTestJob.runForever = false; // emulate down time >> trigger interval - misfireThreshold Thread.sleep(4000); final AtomicBoolean isJobRecovered = new AtomicBoolean(false); factory.createScheduler(new SimpleThreadPool(1, Thread.NORM_PRIORITY), jobStore); Scheduler recovery = factory.getScheduler(); recovery.getListenerManager().addJobListener(new JobListenerSupport() { @Override public String getName() { return RecoverJobsTest.class.getSimpleName(); } @Override public void jobToBeExecuted(JobExecutionContext context) { isJobRecovered.set(true); } }); recovery.start(); // wait to be sure recovered job was executed Thread.sleep(2000); // wait job recovery.shutdown(true); assertTrue(isJobRecovered.get()); } finally { JdbcQuartzTestUtilities.destroyDatabase(dsName, DatabaseType.DERBY); } } /** * @author https://github.com/eugene-goroschenya */ @DisallowConcurrentExecution public static class RecoverJobsTestJob implements Job { private static Logger _log = LoggerFactory.getLogger(RecoverJobsTestJob.class); static boolean runForever = true; @Override public void execute(JobExecutionContext context) throws JobExecutionException { long now = System.currentTimeMillis(); int tic = 0; _log.info("Started - " + now); try { while (runForever) { Thread.sleep(1000); _log.info("Tic " + (++tic) + "- " + now); } _log.info("Stopped - " + now); } catch (InterruptedException e) { _log.info("Interrupted - " + now); } } } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/DirectSchedulerFactoryTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.lang.reflect.Field; import java.util.Collections; import java.util.List; import org.quartz.Scheduler; import org.quartz.core.QuartzScheduler; import org.quartz.core.QuartzSchedulerResources; import org.quartz.simpl.RAMJobStore; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.ClassLoadHelper; import org.quartz.spi.SchedulerPlugin; import org.quartz.spi.ThreadPool; import static org.junit.jupiter.api.Assertions.*; public class DirectSchedulerFactoryTest { void testPlugins() throws Exception { final StringBuffer result = new StringBuffer(); SchedulerPlugin testPlugin = new SchedulerPlugin() { public void initialize(String name, org.quartz.Scheduler scheduler, ClassLoadHelper classLoadHelper) throws org.quartz.SchedulerException { result.append(name).append("|").append(scheduler.getSchedulerName()); }; public void start() { result.append("|start"); }; public void shutdown() { result.append("|shutdown"); }; }; ThreadPool threadPool = new SimpleThreadPool(1, 5); threadPool.initialize(); DirectSchedulerFactory.getInstance().createScheduler( "MyScheduler", "Instance1", threadPool, new RAMJobStore(), Collections.singletonMap("TestPlugin", testPlugin), null, -1, 0, 0, false, null); Scheduler scheduler = DirectSchedulerFactory.getInstance().getScheduler("MyScheduler"); scheduler.start(); scheduler.shutdown(); assertEquals("TestPlugin|MyScheduler|start|shutdown", result.toString()); } void testThreadName() throws Throwable { DirectSchedulerFactory.getInstance().createVolatileScheduler(4); Scheduler scheduler = DirectSchedulerFactory.getInstance().getScheduler(); QuartzScheduler qs = getField(scheduler, "sched"); QuartzSchedulerResources qsr = getField(qs, "resources"); ThreadPool tp = qsr.getThreadPool(); List list = getField(tp,"workers"); Object workerThread = list.get(0); String workerThreadName = workerThread.toString(); assertFalse(workerThreadName.contains("null")); assertTrue(workerThreadName.contains(scheduler.getSchedulerName())); } T getField(Object obj, String fieldName) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return (T)field.get(obj); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/JobDetailImplTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; class JobDetailImplTest { @Test void testHashCode() { JobDetailImpl job = new JobDetailImpl(); assertThat(job.hashCode(), Matchers.is(0)); job.setName("test"); assertThat(job.hashCode(), Matchers.not(Matchers.is(0))); job.setGroup("test"); assertThat(job.hashCode(), Matchers.not(Matchers.is(0))); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/MockConnectionProvider.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.quartz.utils.ConnectionProvider; /** * Mock implementation of a ConnectionProvider * that keeps track of the order of it methods calls * * @author adahanne */ public class MockConnectionProvider implements ConnectionProvider { private String customProperty; public static List methodsCalled = new ArrayList(); public Connection getConnection() throws SQLException { methodsCalled.add("getConnection"); throw new MockSQLException("getConnection correctly called on MockConnectionProvider"); } public void shutdown() throws SQLException { } public void initialize() throws SQLException { methodsCalled.add("initialize"); } public void setCustomProperty(String customProperty) { methodsCalled.add("setCustomProperty("+customProperty+")"); } } class MockSQLException extends SQLException{ public MockSQLException(String string) { super(string); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/RemoteMBeanSchedulerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerMetaData; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.core.QuartzSchedulerResources; import org.quartz.impl.calendar.BaseCalendar; import org.quartz.impl.matchers.GroupMatcher; import java.lang.management.ManagementFactory; import java.util.Collections; import java.util.Date; import java.util.Properties; import javax.management.AttributeList; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; /** * RemoteMBeanSchedulerTest */ public class RemoteMBeanSchedulerTest { public static final String TRIGGER_KEY = "trigger1"; public static final String GROUP_KEY = "group1"; public static final String JOB_KEY = "job1"; public static final String CALENDAR_KEY = "calendar1"; private Scheduler scheduler; private RemoteMBeanScheduler remoteScheduler; @BeforeEach public void setUp() throws Exception { Properties props = new Properties(); props.put("org.quartz.scheduler.instanceName", "TestScheduler"); props.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore"); props.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); props.put("org.quartz.threadPool.threadCount", "1"); props.put("org.quartz.scheduler.jmx.export", "true"); scheduler = new StdSchedulerFactory(props).getScheduler(); JobDetail jobDetail = newJob(HelloJob.class).withIdentity(JOB_KEY, GROUP_KEY).build(); Trigger trigger = newTrigger().withIdentity(TRIGGER_KEY, GROUP_KEY).startAt(new Date()).build(); scheduler.addCalendar(CALENDAR_KEY, new BaseCalendar(), false, false); scheduler.scheduleJob(jobDetail, trigger); String objectName = QuartzSchedulerResources.generateJMXObjectName(scheduler.getSchedulerName(), scheduler.getSchedulerInstanceId()); remoteScheduler = new TestRemoteScheduler(objectName); } @AfterEach public void tearDown() throws SchedulerException { scheduler.shutdown(); } @Test void testJMXAttributesAccess() throws Exception { assertThat(remoteScheduler.getCalendarNames(), equalTo(scheduler.getCalendarNames())); assertThat(remoteScheduler.getJobGroupNames(), equalTo(scheduler.getJobGroupNames())); assertThat(remoteScheduler.getPausedTriggerGroups(), equalTo(scheduler.getPausedTriggerGroups())); assertThat(remoteScheduler.getSchedulerInstanceId(), equalTo(scheduler.getSchedulerInstanceId())); assertThat(remoteScheduler.getSchedulerName(), equalTo(scheduler.getSchedulerName())); assertThat(remoteScheduler.getTriggerGroupNames(), equalTo(scheduler.getTriggerGroupNames())); } @Test void testSchedulerMetaData() throws Exception { SchedulerMetaData remoteSchedulerMetaData = remoteScheduler.getMetaData(); SchedulerMetaData metaData = scheduler.getMetaData(); assertThat(remoteSchedulerMetaData.getSchedulerName(), equalTo(metaData.getSchedulerName())); assertThat(remoteSchedulerMetaData.getSchedulerInstanceId(), equalTo(metaData.getSchedulerInstanceId())); assertThat(remoteSchedulerMetaData.isInStandbyMode(), is(metaData.isInStandbyMode())); assertThat(remoteSchedulerMetaData.getSchedulerClass(), equalTo((Class) TestRemoteScheduler.class)); assertThat(remoteSchedulerMetaData.isSchedulerRemote(), is(true)); assertThat(remoteSchedulerMetaData.isStarted(), is(false)); // information not available through JMX assertThat(remoteSchedulerMetaData.isInStandbyMode(), is(metaData.isInStandbyMode())); assertThat(remoteSchedulerMetaData.isShutdown(), is(metaData.isShutdown())); assertThat(remoteSchedulerMetaData.getRunningSince(), nullValue()); // Information not available through JMX assertThat(remoteSchedulerMetaData.getNumberOfJobsExecuted(), is(metaData.getNumberOfJobsExecuted())); assertThat(remoteSchedulerMetaData.getJobStoreClass(), equalTo((Class) metaData.getJobStoreClass())); assertThat(remoteSchedulerMetaData.isJobStoreSupportsPersistence(), is(false)); // Information not available through JMX assertThat(remoteSchedulerMetaData.isJobStoreClustered(), is(false)); // Information not available through JMX assertThat(remoteSchedulerMetaData.getThreadPoolClass(), equalTo((Class) metaData.getThreadPoolClass())); assertThat(remoteSchedulerMetaData.getThreadPoolSize(), is(metaData.getThreadPoolSize())); assertThat(remoteSchedulerMetaData.getVersion(), equalTo(metaData.getVersion())); assertThat(remoteSchedulerMetaData.getJobStoreClass(), equalTo((Class) metaData.getJobStoreClass())); } @Test void testCalendarOperations() throws Exception { try { remoteScheduler.addCalendar("testCal", new BaseCalendar(), true, true); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.getCalendar("test"); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } remoteScheduler.deleteCalendar(CALENDAR_KEY); assertThat(scheduler.getCalendar(CALENDAR_KEY), nullValue()); } @Test void testTriggerOperations() throws Exception { TriggerKey triggerKey = new TriggerKey(TRIGGER_KEY, GROUP_KEY); GroupMatcher groupMatcher = GroupMatcher.triggerGroupEquals(GROUP_KEY); try { remoteScheduler.getTrigger(triggerKey); fail("Method had a different return type in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.getTriggersOfJob(new JobKey(JOB_KEY, GROUP_KEY)); fail("Method had a different return type in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.checkExists(triggerKey); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } assertThat(remoteScheduler.getTriggerState(triggerKey), is(scheduler.getTriggerState(triggerKey))); try { remoteScheduler.getTriggerKeys(groupMatcher); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } remoteScheduler.pauseTrigger(triggerKey); assertThat(scheduler.getTriggerState(triggerKey), is(Trigger.TriggerState.PAUSED)); remoteScheduler.resumeTrigger(triggerKey); assertThat(scheduler.getTriggerState(triggerKey), is(Trigger.TriggerState.NORMAL)); remoteScheduler.pauseTriggers(groupMatcher); assertThat(scheduler.getTriggerState(triggerKey), is(Trigger.TriggerState.PAUSED)); remoteScheduler.resumeTriggers(groupMatcher); assertThat(scheduler.getTriggerState(triggerKey), is(Trigger.TriggerState.NORMAL)); remoteScheduler.pauseAll(); assertThat(scheduler.getTriggerState(triggerKey), is(Trigger.TriggerState.PAUSED)); remoteScheduler.resumeAll(); assertThat(scheduler.getTriggerState(triggerKey), is(Trigger.TriggerState.NORMAL)); } @Test void testJobOperations() throws Exception { JobKey job2 = new JobKey("job2", GROUP_KEY); JobDetail job2Detail = newJob(HelloJob.class).withIdentity(job2).storeDurably().build(); remoteScheduler.addJob(job2Detail, false); assertThat(remoteScheduler.getJobDetail(job2), equalTo(job2Detail)); try { remoteScheduler.checkExists(job2); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } remoteScheduler.pauseJob(job2); remoteScheduler.resumeJob(job2); GroupMatcher matcher = GroupMatcher.jobGroupEquals(GROUP_KEY); remoteScheduler.pauseJobs(matcher); remoteScheduler.resumeJobs(matcher); assertThat(remoteScheduler.getJobKeys(matcher).size(), is(2)); assertThat(remoteScheduler.interrupt(job2), is(false)); try { remoteScheduler.triggerJob(job2); fail("Method had different parameters in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.scheduleJob(null, null); fail("Method had different parameters in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.scheduleJob(null); fail("Method had different parameters in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.scheduleJobs(null, false); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } assertThat(remoteScheduler.unscheduleJob(TriggerKey.triggerKey(TRIGGER_KEY, GROUP_KEY)), is(true)); try { remoteScheduler.unscheduleJobs(null); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.rescheduleJob(null, null); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } assertThat(remoteScheduler.deleteJob(job2), is(true)); try { remoteScheduler.deleteJobs(Collections.singletonList(JobKey.jobKey(JOB_KEY, GROUP_KEY))); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } } @Test void testLifecycleOperations() throws SchedulerException { try { remoteScheduler.startDelayed(60); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } remoteScheduler.start(); assertThat(remoteScheduler.isStarted(), is(true)); assertThat(scheduler.isStarted(), is(true)); remoteScheduler.standby(); assertThat(remoteScheduler.isInStandbyMode(), is(true)); assertThat(scheduler.isInStandbyMode(), is(true)); try { remoteScheduler.shutdown(true); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } remoteScheduler.shutdown(); try { remoteScheduler.isShutdown(); fail("Shutting down a scheduler un-registers it in JMX"); } catch (SchedulerException e) { // expected } assertThat(scheduler.isShutdown(), is(true)); } @Test void testJMXOperations() throws Exception { remoteScheduler.clear(); assertThat(remoteScheduler.getJobGroupNames().isEmpty(), is(true)); } @Test void testUnsupportedMethods() { try { remoteScheduler.getListenerManager(); fail("Operation should not be supported"); } catch (SchedulerException e) { // expected } try { remoteScheduler.setJobFactory(null); fail("Operation should not be supported"); } catch (SchedulerException e) { // expected } } @Test void testListBrokenAttributes() throws Exception { try { remoteScheduler.getContext(); fail("Method was not exposed in MBean API"); } catch (SchedulerException e) { // expected } try { remoteScheduler.getCurrentlyExecutingJobs(); fail("Method had a different return type in MBean API"); } catch (SchedulerException e) { // expected } } public static class HelloJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("hello world!"); } } public static class TestRemoteScheduler extends RemoteMBeanScheduler { private MBeanServer mBeanServer; private ObjectName objectName; public TestRemoteScheduler(String objectName) throws SchedulerException, MalformedObjectNameException { this.objectName = new ObjectName(objectName); initialize(); } @Override public void initialize() throws SchedulerException { mBeanServer = ManagementFactory.getPlatformMBeanServer(); } @Override protected Object getAttribute(String attribute) throws SchedulerException { try { return mBeanServer.getAttribute(objectName, attribute); } catch (Exception e) { throw new SchedulerException(e); } } @Override protected AttributeList getAttributes(String[] attributes) throws SchedulerException { try { return mBeanServer.getAttributes(objectName, attributes); } catch (Exception e) { throw new SchedulerException(e); } } @Override protected Object invoke(String operationName, Object[] params, String[] signature) throws SchedulerException { try { return mBeanServer.invoke(objectName, operationName, params, signature); } catch (Exception e) { throw new SchedulerException(e); } } } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/SchedulerDetailsSetterTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import java.io.IOException; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.quartz.SchedulerException; import org.quartz.simpl.RAMJobStore; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.ThreadPool; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; public class SchedulerDetailsSetterTest { @Test void testSetter() throws SchedulerException, IOException { Properties props = new Properties(); props.load(getClass().getResourceAsStream("/org/quartz/quartz.properties")); props.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, MyThreadPool.class.getName()); props.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, MyJobStore.class.getName()); StdSchedulerFactory factory = new StdSchedulerFactory(props); factory.getScheduler(); // this will initialize all the test fixtures. assertEquals(3, instanceIdCalls.get()); assertEquals(3, instanceNameCalls.get()); DirectSchedulerFactory directFactory = DirectSchedulerFactory.getInstance(); directFactory.createScheduler("SchedulerDetailsSetterTest.testSetter", "1", new MyThreadPool(), new MyJobStore()); assertEquals(5, instanceIdCalls.get()); assertEquals(6, instanceNameCalls.get()); } @Test void testMissingSetterMethods() throws SchedulerException { SchedulerDetailsSetter.setDetails(new Object(), "name", "id"); } @Test void testUnimplementedMethods() throws Exception { ThreadPool tp = makeIncompleteThreadPool(); try { tp.setInstanceName("name"); fail(); } catch (AbstractMethodError ame) { // expected } SchedulerDetailsSetter.setDetails(tp, "name", "id"); } private ThreadPool makeIncompleteThreadPool() throws InstantiationException, IllegalAccessException { String name = "IncompleteThreadPool"; ClassWriter cw = new ClassWriter(0); cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, name, null, "java/lang/Object", new String[] { "org/quartz/spi/ThreadPool" }); MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V"); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); cw.visitEnd(); return (ThreadPool) new ClassLoader() { Class defineClass(String clname, byte[] b) { return defineClass(clname, b, 0, b.length); } }.defineClass(name, cw.toByteArray()).newInstance(); } private static final AtomicInteger instanceIdCalls = new AtomicInteger(); private static final AtomicInteger instanceNameCalls = new AtomicInteger(); public static class MyThreadPool extends SimpleThreadPool { @Override public void initialize() { } @Override public void setInstanceId(String schedInstId) { super.setInstanceId(schedInstId); instanceIdCalls.incrementAndGet(); } @Override public void setInstanceName(String schedName) { super.setInstanceName(schedName); instanceNameCalls.incrementAndGet(); } } public static class MyJobStore extends RAMJobStore { @Override public void setInstanceId(String schedInstId) { super.setInstanceId(schedInstId); instanceIdCalls.incrementAndGet(); } @Override public void setInstanceName(String schedName) { super.setInstanceName(schedName); instanceNameCalls.incrementAndGet(); } } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/StdSchedulerFactoryCustomConnectionProviderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.quartz.Scheduler; import org.quartz.SchedulerException; /** * TestCase to verify StdSchedulerFactory initializes correctly a custom ConnectionProvider * * @author adahanne * */ class StdSchedulerFactoryCustomConnectionProviderTest { @Test void loadAndInitializeCustomConnectionProviderTest() throws SchedulerException, InterruptedException { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/properties/quartzCustomConnectionProvider.properties"); Scheduler scheduler = factory.getScheduler(); try{ scheduler.start(); } catch(Exception e){ //the mock connection provider throws a MockSQLException assertEquals("org.quartz.impl.MockSQLException",e.getCause().getCause().getClass().getName()); } assertEquals("setCustomProperty(customValue)",MockConnectionProvider.methodsCalled.get(0)); assertEquals("initialize",MockConnectionProvider.methodsCalled.get(1)); assertEquals("getConnection",MockConnectionProvider.methodsCalled.get(2)); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/StdSchedulerFactoryTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Properties; import org.junit.jupiter.api.Test; import org.slf4j.helpers.NOPLogger; class StdSchedulerFactoryTest { @Test void testOverrideSystemProperties() { Properties p = new Properties(); p.setProperty("nonsense1", "hello1"); p.setProperty("nonsense2", "hello2"); System.setProperty("nonsense1", "boo1"); String osName = System.getProperty("os.name"); Properties q = StdSchedulerFactory.overrideWithSysProps(p, NOPLogger.NOP_LOGGER); assertEquals("boo1", q.get("nonsense1")); assertEquals(osName, q.get("os.name")); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/calendar/BaseCalendarTest.java ================================================ package org.quartz.impl.calendar; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class BaseCalendarTest { @Test void testClone() { BaseCalendar base = new BaseCalendar(); BaseCalendar clone = (BaseCalendar) base.clone(); assertEquals(base.getDescription(), clone.getDescription()); assertEquals(base.getBaseCalendar(), clone.getBaseCalendar()); assertEquals(base.getTimeZone(), clone.getTimeZone()); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/calendar/DailyCalendarTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.calendar; import org.junit.jupiter.api.Test; import org.quartz.SerializationTestSupport; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for DailyCalendar. */ public class DailyCalendarTest extends SerializationTestSupport { private static final String[] VERSIONS = new String[] {"1.5.2"}; @Test void testStringStartEndTimes() { DailyCalendar dailyCalendar = new DailyCalendar("1:20", "14:50"); assertTrue(dailyCalendar.toString().indexOf("01:20:00:000 - 14:50:00:000") > 0); dailyCalendar = new DailyCalendar("1:20:1:456", "14:50:15:2"); assertTrue(dailyCalendar.toString().indexOf("01:20:01:456 - 14:50:15:002") > 0); } @Test void testStringInvertTimeRange() { DailyCalendar dailyCalendar = new DailyCalendar("1:20", "14:50"); dailyCalendar.setInvertTimeRange(true); assertTrue(dailyCalendar.toString().indexOf("inverted: true") > 0); dailyCalendar.setInvertTimeRange(false); assertTrue(dailyCalendar.toString().indexOf("inverted: false") > 0); } /** * Get the object to serialize when generating serialized file for future * tests, and against which to validate deserialized object. */ @Override protected Object getTargetObject() { DailyCalendar c = new DailyCalendar("01:20:01:456", "14:50:15:002"); c.setDescription("description"); c.setInvertTimeRange(true); return c; } /** * Get the Quartz versions for which we should verify * serialization backwards compatibility. */ @Override protected String[] getVersions() { return VERSIONS; } /** * Verify that the target object and the object we just deserialized * match. */ @Override protected void verifyMatch(Object target, Object deserialized) { DailyCalendar targetCalendar = (DailyCalendar)target; DailyCalendar deserializedCalendar = (DailyCalendar)deserialized; assertNotNull(deserializedCalendar); assertEquals(targetCalendar.getDescription(), deserializedCalendar.getDescription()); assertTrue(deserializedCalendar.getInvertTimeRange()); assertNull(deserializedCalendar.getTimeZone()); assertTrue(deserializedCalendar.toString().indexOf("01:20:01:456 - 14:50:15:002") > 0); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/DeleteNonExistsJobTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.quartz.*; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.simpl.SimpleThreadPool; import org.quartz.utils.DBConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.Statement; /** * Tests for QTZ-326 * * @author Zemian Deng */ public class DeleteNonExistsJobTest { private static final Logger LOG = LoggerFactory.getLogger(DeleteNonExistsJobTest.class); private static final String DB_NAME = "DeleteNonExistsJobTestDatabase"; private static String SCHEDULER_NAME = "DeleteNonExistsJobTestScheduler"; private static Scheduler scheduler; @BeforeAll public static void beforeClass() throws Exception { JdbcQuartzTestUtilities.createDatabase(DB_NAME, DatabaseType.DERBY); } @BeforeEach public void beforeTest() throws Exception { resetDatabaseData(); JobStoreTX jobStore = new JobStoreTX(); jobStore.setDataSource(DB_NAME); jobStore.setTablePrefix("QRTZ_"); jobStore.setInstanceId("AUTO"); DirectSchedulerFactory.getInstance().createScheduler(SCHEDULER_NAME, "AUTO", new SimpleThreadPool(4, Thread.NORM_PRIORITY), jobStore); scheduler = SchedulerRepository.getInstance().lookup(SCHEDULER_NAME); // scheduler.start(); // Do not start scheduler to produce the defect case. } private void resetDatabaseData() throws Exception { Connection conn = DBConnectionManager.getInstance().getConnection(DB_NAME); Statement statement = conn.createStatement(); statement.addBatch("delete from qrtz_fired_triggers"); statement.addBatch("delete from qrtz_paused_trigger_grps"); statement.addBatch("delete from qrtz_scheduler_state"); statement.addBatch("delete from qrtz_locks"); statement.addBatch("delete from qrtz_simple_triggers"); statement.addBatch("delete from qrtz_simprop_triggers"); statement.addBatch("delete from qrtz_blob_triggers"); statement.addBatch("delete from qrtz_cron_triggers"); statement.addBatch("delete from qrtz_triggers"); statement.addBatch("delete from qrtz_job_details"); statement.addBatch("delete from qrtz_calendars"); statement.executeBatch(); statement.close(); conn.close(); } @AfterEach public void afterTest() throws Exception { scheduler.shutdown(true); } @Test void deleteJobDetailOnly() throws Exception { JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testjob").storeDurably().build(); scheduler.addJob(jobDetail, true); modifyStoredJobClassName(); scheduler.deleteJob(jobDetail.getKey()); } @Test void deleteJobDetailWithTrigger() throws Exception { JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testjob2").storeDurably().build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("testjob2") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")).build(); scheduler.scheduleJob(jobDetail, trigger); modifyStoredJobClassName(); scheduler.deleteJob(jobDetail.getKey()); } @Test void deleteTrigger() throws Exception { JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testjob3").storeDurably().build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("testjob3") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")).build(); scheduler.scheduleJob(jobDetail, trigger); modifyStoredJobClassName(); scheduler.unscheduleJob(trigger.getKey()); } @Test void replaceJobDetail() throws Exception { JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testjob3").storeDurably().build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("testjob3") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")).build(); scheduler.scheduleJob(jobDetail, trigger); modifyStoredJobClassName(); jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testjob3").storeDurably().build(); scheduler.addJob(jobDetail, true); } private void modifyStoredJobClassName() throws Exception { Connection conn = DBConnectionManager.getInstance().getConnection(DB_NAME); Statement statement = conn.createStatement(); statement.executeUpdate("update qrtz_job_details set job_class_name='com.FakeNonExistsJob'"); statement.close(); conn.close(); } public static class TestJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { LOG.info("Job is executing {}", context); } } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/GaussDBDelegateTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.junit.jupiter.api.Test; import org.quartz.JobDataMap; import org.quartz.TriggerKey; import org.quartz.simpl.SimpleClassLoadHelper; import org.quartz.spi.OperableTrigger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; public class GaussDBDelegateTest { @Test void testSelectBlobTriggerWithBlobContent() throws Exception { StdJDBCDelegate jdbcDelegate = new GaussDBDelegate(); jdbcDelegate.initialize(LoggerFactory.getLogger(getClass()), "QRTZ_", "TESTSCHED", "INSTANCE", new SimpleClassLoadHelper(), false, ""); Connection conn = mock(Connection.class); PreparedStatement preparedStatement = mock(PreparedStatement.class); ResultSet resultSet = mock(ResultSet.class); when(conn.prepareStatement(anyString())).thenReturn(preparedStatement); when(preparedStatement.executeQuery()).thenReturn(resultSet); when(resultSet.next()).thenReturn(true).thenReturn(false); when(resultSet.getString(Constants.COL_TRIGGER_TYPE)).thenReturn(Constants.TTYPE_BLOB); when(resultSet.getBytes(Constants.COL_JOB_DATAMAP)).thenReturn(null); OperableTrigger trigger = jdbcDelegate.selectTrigger(conn, TriggerKey.triggerKey("test")); assertNull(trigger); reset(resultSet); when(resultSet.next()).thenReturn(true).thenReturn(false); when(resultSet.getString(Constants.COL_TRIGGER_TYPE)).thenReturn(Constants.TTYPE_BLOB); when(resultSet.getString(Constants.COL_TRIGGER_NAME)).thenReturn("testWithJobData"); when(resultSet.getString(Constants.COL_TRIGGER_GROUP)).thenReturn("DEFAULT"); when(resultSet.getString(Constants.COL_JOB_NAME)).thenReturn("testJob"); when(resultSet.getString(Constants.COL_JOB_GROUP)).thenReturn("DEFAULT"); when(resultSet.getLong(Constants.COL_NEXT_FIRE_TIME)).thenReturn(System.currentTimeMillis()); when(resultSet.getLong(Constants.COL_PREV_FIRE_TIME)).thenReturn(System.currentTimeMillis() - 1000); when(resultSet.getString(Constants.COL_TRIGGER_STATE)).thenReturn("WAITING"); JobDataMap jdm = new JobDataMap(); jdm.put("key1", "value"); jdm.put("key2", true); byte[] jobDataMapBytes = serializeJobDataMap(jdm); when(resultSet.getBytes(Constants.COL_JOB_DATAMAP)).thenReturn(jobDataMapBytes); trigger = jdbcDelegate.selectTrigger(conn, TriggerKey.triggerKey("testWithJobData")); assertNull(trigger); reset(resultSet); when(resultSet.next()).thenReturn(true).thenReturn(false); when(resultSet.getString(Constants.COL_TRIGGER_TYPE)).thenReturn(Constants.TTYPE_BLOB); when(resultSet.getString(Constants.COL_TRIGGER_NAME)).thenReturn("testEmptyJobData"); when(resultSet.getString(Constants.COL_TRIGGER_GROUP)).thenReturn("DEFAULT"); when(resultSet.getString(Constants.COL_JOB_NAME)).thenReturn("testJob"); when(resultSet.getString(Constants.COL_JOB_GROUP)).thenReturn("DEFAULT"); when(resultSet.getBytes(Constants.COL_JOB_DATAMAP)).thenReturn(new byte[0]); trigger = jdbcDelegate.selectTrigger(conn, TriggerKey.triggerKey("testEmptyJobData")); assertNull(trigger); } private byte[] serializeJobDataMap(JobDataMap jobDataMap) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeObject(jobDataMap); } return baos.toByteArray(); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/JdbcJobStoreTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import org.quartz.AbstractJobStoreTest; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.spi.JobStore; public class JdbcJobStoreTest extends AbstractJobStoreTest { private HashMap stores = new HashMap(); protected DatabaseType getDatabaseType() { return DatabaseType.DERBY; } private String name(String prefix) { return prefix + "_" + getDatabaseType().name(); } @Override protected JobStore createJobStore(String prefix) { String name = name(prefix); try { JdbcQuartzTestUtilities.createDatabase(name, getDatabaseType()); JobStoreTX jdbcJobStore = new JobStoreTX(); jdbcJobStore.setDataSource(name); jdbcJobStore.setTablePrefix("QRTZ_"); jdbcJobStore.setInstanceId("SINGLE_NODE_TEST_" + getDatabaseType().name()); jdbcJobStore.setInstanceName(name); jdbcJobStore.setUseDBLocks(true); jdbcJobStore.setDriverDelegateClass(getDatabaseType().getDelegateClassName()); stores.put(name, jdbcJobStore); return jdbcJobStore; } catch (Exception e) { throw new AssertionError(e); } } @Override protected void destroyJobStore(String prefix) { String name = name(prefix); try { JobStoreSupport jdbcJobStore = stores.remove(name); jdbcJobStore.shutdown(); JdbcQuartzTestUtilities.destroyDatabase(name, getDatabaseType()); } catch (SQLException e) { throw new AssertionError(e); } } protected Map stores() { return stores; } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/JdbcQuartzTestUtilities.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; import org.quartz.utils.ConnectionProvider; import org.quartz.utils.DBConnectionManager; public final class JdbcQuartzTestUtilities { public enum DatabaseType { DERBY("org/quartz/impl/jdbcjobstore/tables_derby.sql", StdJDBCDelegate.class.getName()), MSSQL("org/quartz/impl/jdbcjobstore/tables_sqlServer.sql", MSSQLDelegate.class.getName()), MARIADB("org/quartz/impl/jdbcjobstore/tables_mysql.sql", StdJDBCDelegate.class.getName()), POSTGRES("org/quartz/impl/jdbcjobstore/tables_postgres.sql", PostgreSQLDelegate.class.getName()); private final String scriptResource; private final String delegateClassName; DatabaseType(String scriptResource, String delegateClassName) { this.scriptResource = scriptResource; this.delegateClassName = delegateClassName; } public String getDelegateClassName() { return this.delegateClassName; } } private static final Map> DATABASE_SETUP_SCRIPTS = new EnumMap<>(DatabaseType.class); private static List getDatabaseSetupScript(DatabaseType type) { return DATABASE_SETUP_SCRIPTS.computeIfAbsent(type, t -> { List commandList = new ArrayList<>(); String setupScript; try { InputStream setupStream = JdbcQuartzTestUtilities.class.getClassLoader() .getResourceAsStream(t.scriptResource); try { BufferedReader r = new BufferedReader(new InputStreamReader(setupStream, "US-ASCII")); StringBuilder sb = new StringBuilder(); while (true) { String line = r.readLine(); if (line == null) { break; } else if (!line.startsWith("--") && !line.startsWith("#")) { // update script for some database like sql server to be executable with jdbc sb.append(line).append("\n"); } } setupScript = sb.toString(); } finally { setupStream.close(); } } catch (IOException e) { throw new AssertionError(e); } for (String command : setupScript.split(";")) { if (!command.matches("\\s*")) { commandList.add(command); } } return commandList; }); } public static void createDatabase(String name, DatabaseType databaseType) throws SQLException { switch (databaseType) { case DERBY: DBConnectionManager.getInstance().addConnectionProvider(name, new DerbyEmbeddedConnectionProvider(name)); break; case MSSQL: DBConnectionManager.getInstance().addConnectionProvider(name, new TestContainerEmbeddedConnectionProvider("jdbc:tc:sqlserver:latest:///" + name)); break; case MARIADB: DBConnectionManager.getInstance().addConnectionProvider(name, new TestContainerEmbeddedConnectionProvider("jdbc:tc:mariadb:latest:///" + name)); break; case POSTGRES: DBConnectionManager.getInstance().addConnectionProvider(name, new TestContainerEmbeddedConnectionProvider("jdbc:tc:postgresql:latest:///" + name)); break; default: throw new AssertionError("Unsupported database type: " + databaseType); } } public static void destroyDatabase(String name, DatabaseType databaseType) throws SQLException { switch (databaseType) { case DERBY: try { DriverManager.getConnection("jdbc:derby:memory:" + name + ";drop=true").close(); } catch (SQLException e) { if (!("Database 'memory:" + name + "' dropped.").equals(e.getMessage())) { throw e; } } break; case MSSQL: case MARIADB: case POSTGRES: shutdownDatabase(name, databaseType); break; default: throw new AssertionError("Unsupported database type: " + databaseType); } } public static void shutdownDatabase(String name, DatabaseType databaseType) throws SQLException { switch (databaseType) { case DERBY: try { DriverManager.getConnection("jdbc:derby:;shutdown=true").close(); } catch (SQLException e) { if (!("Derby system shutdown.").equals(e.getMessage())) { throw e; } } break; case MSSQL: case MARIADB: case POSTGRES: DBConnectionManager.getInstance().shutdown(name); break; default: throw new AssertionError("Unsupported database type: " + databaseType); } } static class DerbyEmbeddedConnectionProvider implements ConnectionProvider { private final String databaseName; DerbyEmbeddedConnectionProvider(String name) throws SQLException { try { Class.forName("org.apache.derby.jdbc.EmbeddedDriver").getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new AssertionError(e); } this.databaseName = name; Connection conn = DriverManager.getConnection("jdbc:derby:memory:" + databaseName + ";create=true"); try { Statement statement = conn.createStatement(); for (String command : getDatabaseSetupScript(DatabaseType.DERBY)) { statement.addBatch(command); } statement.executeBatch(); } finally { conn.close(); } } public Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:derby:memory:" + databaseName); } public void shutdown() throws SQLException { // nothing to do } public void initialize() throws SQLException { // nothing to do } } static class TestContainerEmbeddedConnectionProvider implements ConnectionProvider { private final String jdbcUrl; //we keep a connection open to keep the testcontainer container alive private final Connection conn; TestContainerEmbeddedConnectionProvider(String jdbcUrl) throws SQLException { this.jdbcUrl = jdbcUrl; this.conn = DriverManager.getConnection(this.jdbcUrl); Statement statement = conn.createStatement(); if(jdbcUrl.contains("sqlserver")) { for (String command : getDatabaseSetupScript(DatabaseType.MSSQL)) { statement.addBatch(command.replace("GO", ";").replace("[enter_db_name_here]", "[master]")); } } else if(jdbcUrl.contains("mariadb")) { for (String command : getDatabaseSetupScript(DatabaseType.MARIADB)) { statement.addBatch(command); } } else if(jdbcUrl.contains("postgresql")) { for (String command : getDatabaseSetupScript(DatabaseType.POSTGRES)) { statement.addBatch(command); } } statement.executeBatch(); } public Connection getConnection() throws SQLException { return DriverManager.getConnection(this.jdbcUrl); } public void shutdown() throws SQLException { // last connection closed shutdown testcontainer container if (!conn.isClosed()) { conn.close(); } } public void initialize() throws SQLException { // nothing to do } } private JdbcQuartzTestUtilities() { // not instantiable } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/MSSQLJdbcStoreTest.java ================================================ /* * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; public class MSSQLJdbcStoreTest extends JdbcJobStoreTest { @Override protected DatabaseType getDatabaseType() { return DatabaseType.MSSQL; } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/MariaDBJdbcStoreTest.java ================================================ /* * Copyright IBM Corp. 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; public class MariaDBJdbcStoreTest extends JdbcJobStoreTest { @Override protected DatabaseType getDatabaseType() { return DatabaseType.MARIADB; } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/PostgresJdbcStoreTest.java ================================================ /* * Copyright IBM Corp. 2025 * * 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 org.quartz.impl.jdbcjobstore; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; public class PostgresJdbcStoreTest extends JdbcJobStoreTest { @Override protected DatabaseType getDatabaseType() { return DatabaseType.POSTGRES; } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/StdJDBCDelegateTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableWithSize.iterableWithSize; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.io.NotSerializableException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.junit.jupiter.api.Test; import org.quartz.JobPersistenceException; import org.quartz.TriggerKey; import org.quartz.spi.OperableTrigger; import org.slf4j.LoggerFactory; import org.quartz.JobDataMap; import org.quartz.simpl.SimpleClassLoadHelper; public class StdJDBCDelegateTest { @Test void testSerializeJobData() throws IOException, NoSuchDelegateException { StdJDBCDelegate delegate = new StdJDBCDelegate(); delegate.initialize(LoggerFactory.getLogger(getClass()), "QRTZ_", "TESTSCHED", "INSTANCE", new SimpleClassLoadHelper(), false, ""); JobDataMap jdm = new JobDataMap(); delegate.serializeJobData(jdm).close(); jdm.clear(); jdm.put("key", "value"); jdm.put("key2", null); delegate.serializeJobData(jdm).close(); jdm.clear(); jdm.put("key1", "value"); jdm.put("key2", null); jdm.put("key3", new Object()); try { delegate.serializeJobData(jdm); fail(); } catch (NotSerializableException e) { assertTrue(e.getMessage().indexOf("key3") >= 0); } } @Test void testSelectBlobTriggerWithNoBlobContent() throws JobPersistenceException, SQLException, IOException, ClassNotFoundException { StdJDBCDelegate jdbcDelegate = new StdJDBCDelegate(); jdbcDelegate.initialize(LoggerFactory.getLogger(getClass()), "QRTZ_", "TESTSCHED", "INSTANCE", new SimpleClassLoadHelper(), false, ""); Connection conn = mock(Connection.class); PreparedStatement preparedStatement = mock(PreparedStatement.class); ResultSet resultSet = mock(ResultSet.class); when(conn.prepareStatement(anyString())).thenReturn(preparedStatement); when(preparedStatement.executeQuery()).thenReturn(resultSet); // First result set has results, second has none when(resultSet.next()).thenReturn(true).thenReturn(false); when(resultSet.getString(Constants.COL_TRIGGER_TYPE)).thenReturn(Constants.TTYPE_BLOB); OperableTrigger trigger = jdbcDelegate.selectTrigger(conn, TriggerKey.triggerKey("test")); assertNull(trigger); } @Test void testSelectSimpleTriggerWithExceptionWithExtendedProps() throws SQLException, JobPersistenceException, IOException, ClassNotFoundException { TriggerPersistenceDelegate persistenceDelegate = mock(TriggerPersistenceDelegate.class); IllegalStateException exception = new IllegalStateException(); when(persistenceDelegate.loadExtendedTriggerProperties(any(Connection.class), any(TriggerKey.class))).thenThrow(exception); StdJDBCDelegate jdbcDelegate = new TestStdJDBCDelegate(persistenceDelegate); jdbcDelegate.initialize(LoggerFactory.getLogger(getClass()), "QRTZ_", "TESTSCHED", "INSTANCE", new SimpleClassLoadHelper(), false, ""); Connection conn = mock(Connection.class); PreparedStatement preparedStatement = mock(PreparedStatement.class); ResultSet resultSet = mock(ResultSet.class); when(conn.prepareStatement(anyString())).thenReturn(preparedStatement); // Mock basic trigger data when(preparedStatement.executeQuery()).thenReturn(resultSet); when(resultSet.next()).thenReturn(true); when(resultSet.getString(Constants.COL_TRIGGER_TYPE)).thenReturn(Constants.TTYPE_SIMPLE); try { jdbcDelegate.selectTrigger(conn, TriggerKey.triggerKey("test")); fail("Trigger selection should result in exception"); } catch (IllegalStateException e) { assertSame(exception, e); } verify(persistenceDelegate).loadExtendedTriggerProperties(any(Connection.class), any(TriggerKey.class)); } @Test void testSelectSimpleTriggerWithDeleteBeforeSelectExtendedProps() throws JobPersistenceException, ClassNotFoundException, SQLException, IOException { TriggerPersistenceDelegate persistenceDelegate = mock(TriggerPersistenceDelegate.class); when(persistenceDelegate.loadExtendedTriggerProperties(any(Connection.class), any(TriggerKey.class))).thenThrow(new IllegalStateException()); StdJDBCDelegate jdbcDelegate = new TestStdJDBCDelegate(persistenceDelegate); jdbcDelegate.initialize(LoggerFactory.getLogger(getClass()), "QRTZ_", "TESTSCHED", "INSTANCE", new SimpleClassLoadHelper(), false, ""); Connection conn = mock(Connection.class); PreparedStatement preparedStatement = mock(PreparedStatement.class); ResultSet resultSet = mock(ResultSet.class); when(conn.prepareStatement(anyString())).thenReturn(preparedStatement); when(preparedStatement.executeQuery()).thenReturn(resultSet); // First result set has results, second has none when(resultSet.next()).thenReturn(true).thenReturn(false); when(resultSet.getString(Constants.COL_TRIGGER_TYPE)).thenReturn(Constants.TTYPE_SIMPLE); OperableTrigger trigger = jdbcDelegate.selectTrigger(conn, TriggerKey.triggerKey("test")); assertNull(trigger); verify(persistenceDelegate).loadExtendedTriggerProperties(any(Connection.class), any(TriggerKey.class)); } @Test void testSelectTriggerToAcquireHonorsMaxCount() throws SQLException { StdJDBCDelegate jdbcDelegate = new StdJDBCDelegate(); Connection conn = mock(Connection.class); PreparedStatement preparedStatement = mock(PreparedStatement.class); ResultSet resultSet = mock(ResultSet.class); when(conn.prepareStatement(anyString())).thenReturn(preparedStatement); when(preparedStatement.executeQuery()).thenReturn(resultSet); when(resultSet.next()).thenReturn(true); when(resultSet.getString(anyString())).thenReturn("test"); List triggerKeys = jdbcDelegate.selectTriggerToAcquire(conn, Long.MAX_VALUE, Long.MIN_VALUE, 10); assertThat(triggerKeys, iterableWithSize(10)); } static class TestStdJDBCDelegate extends StdJDBCDelegate { private final TriggerPersistenceDelegate testDelegate; public TestStdJDBCDelegate(TriggerPersistenceDelegate testDelegate) { this.testDelegate = testDelegate; } @Override public TriggerPersistenceDelegate findTriggerPersistenceDelegate(String discriminator) { return testDelegate; } } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/jdbcjobstore/UpdateLockRowSemaphoreTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.jdbcjobstore; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import org.junit.jupiter.api.Test; /** * * @author cdennis */ class UpdateLockRowSemaphoreTest { private static final PreparedStatement GOOD_STATEMENT = mock(PreparedStatement.class); private static final PreparedStatement FAIL_STATEMENT = mock(PreparedStatement.class); private static final PreparedStatement BAD_STATEMENT = mock(PreparedStatement.class); static { try { when(GOOD_STATEMENT.executeUpdate()).thenReturn(1); when(FAIL_STATEMENT.executeUpdate()).thenReturn(0); when(BAD_STATEMENT.executeUpdate()).thenThrow(SQLException.class); } catch (SQLException e) { throw new AssertionError(e); } } @Test void testSingleSuccessUsingUpdate() throws LockException, SQLException { UpdateLockRowSemaphore semaphore = new UpdateLockRowSemaphore(); semaphore.setSchedName("test"); Connection mockConnection = mock(Connection.class); when(mockConnection.prepareStatement(startsWith("UPDATE"))) .thenReturn(GOOD_STATEMENT) .thenThrow(AssertionError.class); assertTrue(semaphore.obtainLock(mockConnection, "test")); } @Test void testSingleFailureFollowedBySuccessUsingUpdate() throws LockException, SQLException { UpdateLockRowSemaphore semaphore = new UpdateLockRowSemaphore(); semaphore.setSchedName("test"); Connection mockConnection = mock(Connection.class); when(mockConnection.prepareStatement(startsWith("UPDATE"))) .thenReturn(BAD_STATEMENT) .thenReturn(GOOD_STATEMENT) .thenThrow(AssertionError.class); assertTrue(semaphore.obtainLock(mockConnection, "test")); } @Test void testDoubleFailureFollowedBySuccessUsingUpdate() throws LockException, SQLException { UpdateLockRowSemaphore semaphore = new UpdateLockRowSemaphore(); semaphore.setSchedName("test"); Connection mockConnection = mock(Connection.class); when(mockConnection.prepareStatement(startsWith("UPDATE"))) .thenReturn(BAD_STATEMENT, BAD_STATEMENT) .thenThrow(AssertionError.class); try { semaphore.obtainLock(mockConnection, "test"); fail(); } catch (LockException e) { //expected } } @Test void testFallThroughToInsert() throws SQLException, LockException { UpdateLockRowSemaphore semaphore = new UpdateLockRowSemaphore(); semaphore.setSchedName("test"); Connection mockConnection = mock(Connection.class); when(mockConnection.prepareStatement(startsWith("UPDATE"))) .thenReturn(FAIL_STATEMENT) .thenThrow(AssertionError.class); when(mockConnection.prepareStatement(startsWith("INSERT"))) .thenReturn(GOOD_STATEMENT) .thenThrow(AssertionError.class); assertTrue(semaphore.obtainLock(mockConnection, "test")); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/matchers/GroupMatcherTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.matchers; import org.junit.jupiter.api.Test; import org.quartz.JobKey; import org.quartz.TriggerKey; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.quartz.JobKey.jobKey; import static org.quartz.TriggerKey.triggerKey; import static org.quartz.impl.matchers.GroupMatcher.anyJobGroup; import static org.quartz.impl.matchers.GroupMatcher.anyTriggerGroup; /** * Unit test for CronScheduleBuilder. * * @author jhouse * */ class GroupMatcherTest { @Test void testAnyGroupMatchers() { TriggerKey tKey = triggerKey("booboo", "baz"); JobKey jKey = jobKey("frumpwomp", "bazoo"); GroupMatcher tgm = anyTriggerGroup(); GroupMatcher jgm = anyJobGroup(); assertTrue(tgm.isMatch(tKey), "Expected match on trigger group"); assertTrue(jgm.isMatch(jKey), "Expected match on job group"); } } ================================================ FILE: quartz/src/test/java/org/quartz/impl/triggers/DailyTimeIntervalTriggerImplTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.impl.triggers; import static org.junit.jupiter.api.Assertions.*; import static org.quartz.DateBuilder.dateOf; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.quartz.DailyTimeIntervalScheduleBuilder; import org.quartz.DailyTimeIntervalTrigger; import org.quartz.DateBuilder; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.JobKey; import org.quartz.SchedulerException; import org.quartz.TimeOfDay; import org.quartz.TriggerUtils; import org.quartz.impl.calendar.CronCalendar; /** * Unit test for {@link DailyTimeIntervalTriggerImpl}. * * @author Zemian Deng */ class DailyTimeIntervalTriggerImplTest { @Test void testNormalExample() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(11, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(72); // this interval will give three firings per day (8:00, 9:12, and 10:24) List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(10, 24, 0, 16, 1, 2011), fireTimes.get(47)); } @Test void testQuartzCalendarExclusion() throws Exception { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(new TimeOfDay(8, 0)); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); CronCalendar cronCal = new CronCalendar("* * 9-12 * * ?"); // exclude 9-12 List fireTimes = TriggerUtils.computeFireTimes(trigger, cronCal, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(13, 0, 0, 1, 1, 2011), fireTimes.get(1)); assertEquals(dateOf(23, 0, 0, 4, 1, 2011), fireTimes.get(47)); } @Test void testValidateTimeOfDayOrder() { DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTimeOfDay(new TimeOfDay(12, 0, 0)); trigger.setEndTimeOfDay(new TimeOfDay(8, 0, 0)); try { trigger.validate(); fail("Trigger should be invalidate when time of day is not in order."); } catch (SchedulerException e) { // expected. } } @Test void testValidateInterval() throws Exception { DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setName("test"); trigger.setGroup("test"); trigger.setJobKey(JobKey.jobKey("test")); trigger.setRepeatIntervalUnit(IntervalUnit.HOUR); trigger.setRepeatInterval(25); try { trigger.validate(); fail("Trigger should be invalidate when interval is greater than 24 hours."); } catch (SchedulerException e) { // expected. } trigger.setRepeatIntervalUnit(IntervalUnit.MINUTE); trigger.setRepeatInterval(60 * 25); try { trigger.validate(); fail("Trigger should be invalidate when interval is greater than 24 hours."); } catch (SchedulerException e) { // expected. } trigger.setRepeatIntervalUnit(IntervalUnit.SECOND); trigger.setRepeatInterval(60 * 60 * 25); try { trigger.validate(); fail("Trigger should be invalidate when interval is greater than 24 hours."); } catch (SchedulerException e) { // expected. } try { trigger.setRepeatIntervalUnit(IntervalUnit.DAY); trigger.validate(); fail("Trigger should be invalidate when interval unit > HOUR."); } catch (Exception e) { // expected. } try { trigger.setRepeatIntervalUnit(IntervalUnit.SECOND); trigger.setRepeatInterval(0); trigger.validate(); fail("Trigger should be invalidate when interval is zero."); } catch (Exception e) { // expected. } } @Test void testStartTimeWithoutStartTimeOfDay() throws Exception { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(0, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(23, 0, 0, 2, 1, 2011), fireTimes.get(47)); } @Test void testEndTimeWithoutEndTimeOfDay() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); Date endTime = dateOf(22, 0, 0, 2, 1, 2011); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setEndTime(endTime); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(47, fireTimes.size()); assertEquals(dateOf(0, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(22, 0, 0, 2, 1, 2011), fireTimes.get(46)); } @Test void testStartTimeBeforeStartTimeOfDay() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(23, 0, 0, 3, 1, 2011), fireTimes.get(47)); } @Test void testStartTimeBeforeStartTimeOfDayOnInvalidDay() throws Exception { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); // Jan 1, 2011 was a saturday... TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); Set daysOfWeek = new HashSet(); daysOfWeek.add(DateBuilder.MONDAY); daysOfWeek.add(DateBuilder.TUESDAY); daysOfWeek.add(DateBuilder.WEDNESDAY); daysOfWeek.add(DateBuilder.THURSDAY); daysOfWeek.add(DateBuilder.FRIDAY); trigger.setDaysOfWeek(daysOfWeek); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); assertEquals(dateOf(8, 0, 0, 3, 1, 2011), trigger.getFireTimeAfter(dateOf(6, 0, 0, 22, 5, 2010))); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 3, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(23, 0, 0, 5, 1, 2011), fireTimes.get(47)); } @Test void testStartTimeAfterStartTimeOfDay() { Date startTime = dateOf(9, 23, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(10, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(9, 0, 0, 4, 1, 2011), fireTimes.get(47)); } @Test void testEndTimeBeforeEndTimeOfDay() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); Date endTime = dateOf(16, 0, 0, 2, 1, 2011); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setEndTime(endTime); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(35, fireTimes.size()); assertEquals(dateOf(0, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(17, 0, 0, 1, 1, 2011), fireTimes.get(17)); assertEquals(dateOf(16, 0, 0, 2, 1, 2011), fireTimes.get(34)); } @Test void testEndTimeAfterEndTimeOfDay() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); Date endTime = dateOf(18, 0, 0, 2, 1, 2011); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setEndTime(endTime); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(36, fireTimes.size()); assertEquals(dateOf(0, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(17, 0, 0, 1, 1, 2011), fireTimes.get(17)); assertEquals(dateOf(17, 0, 0, 2, 1, 2011), fireTimes.get(35)); } @Test void testTimeOfDayWithStartTime() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(17, 0, 0, 1, 1, 2011), fireTimes.get(9)); // The 10th hours is the end of day. assertEquals(dateOf(15, 0, 0, 5, 1, 2011), fireTimes.get(47)); } @Test void testTimeOfDayWithEndTime() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); Date endTime = dateOf(0, 0, 0, 4, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setEndTime(endTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(30, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(17, 0, 0, 1, 1, 2011), fireTimes.get(9)); // The 10th hours is the end of day. assertEquals(dateOf(17, 0, 0, 3, 1, 2011), fireTimes.get(29)); } @Test void testTimeOfDayWithEndTime2() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 23, 0); TimeOfDay endTimeOfDay = new TimeOfDay(23, 59, 59); // edge case when endTime is last second of day, which is default too. DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 23, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(23, 23, 0, 3, 1, 2011), fireTimes.get(47)); } @Test void testAllDaysOfTheWeek() { Set daysOfWeek = DailyTimeIntervalScheduleBuilder.ALL_DAYS_OF_THE_WEEK; Date startTime = dateOf(0, 0, 0, 1, 1, 2011); // SAT TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setDaysOfWeek(daysOfWeek); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(17, 0, 0, 1, 1, 2011), fireTimes.get(9)); // The 10th hours is the end of day. assertEquals(dateOf(15, 0, 0, 5, 1, 2011), fireTimes.get(47)); } @Test void testMonThroughFri() { Set daysOfWeek = DailyTimeIntervalScheduleBuilder.MONDAY_THROUGH_FRIDAY; Date startTime = dateOf(0, 0, 0, 1, 1, 2011); // SAT(7) TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setDaysOfWeek(daysOfWeek); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 3, 1, 2011), fireTimes.get(0)); assertEquals(Calendar.MONDAY, getDayOfWeek(fireTimes.get(0))); assertEquals(dateOf(8, 0, 0, 4, 1, 2011), fireTimes.get(10)); assertEquals(Calendar.TUESDAY, getDayOfWeek(fireTimes.get(10))); assertEquals(dateOf(15, 0, 0, 7, 1, 2011), fireTimes.get(47)); assertEquals(Calendar.FRIDAY, getDayOfWeek(fireTimes.get(47))); } @Test void testSatAndSun() { Set daysOfWeek = DailyTimeIntervalScheduleBuilder.SATURDAY_AND_SUNDAY; Date startTime = dateOf(0, 0, 0, 1, 1, 2011); // SAT(7) TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setDaysOfWeek(daysOfWeek); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(Calendar.SATURDAY, getDayOfWeek(fireTimes.get(0))); assertEquals(dateOf(8, 0, 0, 2, 1, 2011), fireTimes.get(10)); assertEquals(Calendar.SUNDAY, getDayOfWeek(fireTimes.get(10))); assertEquals(dateOf(15, 0, 0, 15, 1, 2011), fireTimes.get(47)); assertEquals(Calendar.SATURDAY, getDayOfWeek(fireTimes.get(47))); } @Test void testMonOnly() { Set daysOfWeek = new HashSet(); daysOfWeek.add(Calendar.MONDAY); Date startTime = dateOf(0, 0, 0, 1, 1, 2011); // SAT(7) TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(17, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setDaysOfWeek(daysOfWeek); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(60); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 3, 1, 2011), fireTimes.get(0)); assertEquals(Calendar.MONDAY, getDayOfWeek(fireTimes.get(0))); assertEquals(dateOf(8, 0, 0, 10, 1, 2011), fireTimes.get(10)); assertEquals(Calendar.MONDAY, getDayOfWeek(fireTimes.get(10))); assertEquals(dateOf(15, 0, 0, 31, 1, 2011), fireTimes.get(47)); assertEquals(Calendar.MONDAY, getDayOfWeek(fireTimes.get(47))); } private int getDayOfWeek(Date dateTime) { Calendar cal = Calendar.getInstance(); cal.setTime(dateTime); return cal.get(Calendar.DAY_OF_WEEK); } @Test void testTimeOfDayWithEndTimeOddInterval() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); Date endTime = dateOf(0, 0, 0, 4, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(10, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setEndTime(endTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(23); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(18, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(9, 55, 0, 1, 1, 2011), fireTimes.get(5)); assertEquals(dateOf(9, 55, 0, 3, 1, 2011), fireTimes.get(17)); } @Test void testHourInterval() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); Date endTime = dateOf(13, 0, 0, 15, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 1, 15); TimeOfDay endTimeOfDay = new TimeOfDay(16, 1, 15); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTime(endTime); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.HOUR); trigger.setRepeatInterval(2); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 1, 15, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(12, 1, 15, 10, 1, 2011), fireTimes.get(47)); } @Test void testSecondInterval() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 2); TimeOfDay endTimeOfDay = new TimeOfDay(13, 30, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.SECOND); trigger.setRepeatInterval(72); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 2, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(8, 56, 26, 1, 1, 2011), fireTimes.get(47)); } @Test void testRepeatCountInf() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(11, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(72); // Setting this (which is default) should make the trigger just as normal one. trigger.setRepeatCount(DailyTimeIntervalTrigger.REPEAT_INDEFINITELY); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(48, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(10, 24, 0, 16, 1, 2011), fireTimes.get(47)); } @Test void testRepeatCount() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(11, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(72); trigger.setRepeatCount(7); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(8, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); assertEquals(dateOf(9, 12, 0, 3, 1, 2011), fireTimes.get(7)); } @Test void testRepeatCount0() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(11, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.MINUTE); trigger.setRepeatInterval(72); trigger.setRepeatCount(0); List fireTimes = TriggerUtils.computeFireTimes(trigger, null, 48); assertEquals(1, fireTimes.size()); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), fireTimes.get(0)); } @Test void testGetFireTime() { Date startTime = dateOf(0, 0, 0, 1, 1, 2011); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(13, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.HOUR); trigger.setRepeatInterval(1); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), trigger.getFireTimeAfter(dateOf(0, 0, 0, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), trigger.getFireTimeAfter(dateOf(7, 0, 0, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2011), trigger.getFireTimeAfter(dateOf(7, 59, 59, 1, 1, 2011))); assertEquals(dateOf(9, 0, 0, 1, 1, 2011), trigger.getFireTimeAfter(dateOf(8, 0, 0, 1, 1, 2011))); assertEquals(dateOf(10, 0, 0, 1, 1, 2011), trigger.getFireTimeAfter(dateOf(9, 0, 0, 1, 1, 2011))); assertEquals(dateOf(13, 0, 0, 1, 1, 2011), trigger.getFireTimeAfter(dateOf(12, 59, 59, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 2, 1, 2011), trigger.getFireTimeAfter(dateOf(13, 0, 0, 1, 1, 2011))); } @Test void testGetFireTimeWithDateBeforeStartTime() { Date startTime = dateOf(0, 0, 0, 1, 1, 2012); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(13, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.HOUR); trigger.setRepeatInterval(1); // NOTE that if you pass a date past the startTime, you will get the first firing on or after the startTime back! assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(0, 0, 0, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(7, 0, 0, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(7, 59, 59, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(8, 0, 0, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(9, 0, 0, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(12, 59, 59, 1, 1, 2011))); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(13, 0, 0, 1, 1, 2011))); // Now try some test times at or after startTime assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(0, 0, 0, 1, 1, 2012))); assertEquals(dateOf(8, 0, 0, 2, 1, 2012), trigger.getFireTimeAfter(dateOf(13, 0, 0, 1, 1, 2012))); } @Test void testGetFireTimeWhenStartTimeAndTimeOfDayIsSame() { // A test case for QTZ-369 Date startTime = dateOf(8, 0, 0, 1, 1, 2012); TimeOfDay startTimeOfDay = new TimeOfDay(8, 0, 0); TimeOfDay endTimeOfDay = new TimeOfDay(13, 0, 0); DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); trigger.setStartTime(startTime); trigger.setStartTimeOfDay(startTimeOfDay); trigger.setEndTimeOfDay(endTimeOfDay); trigger.setRepeatIntervalUnit(DateBuilder.IntervalUnit.HOUR); trigger.setRepeatInterval(1); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getFireTimeAfter(dateOf(0, 0, 0, 1, 1, 2012))); } @Test void testExtraConstructors() { // A test case for QTZ-389 - some extra constructors didn't set all parameters DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl( "triggerName", "triggerGroup", "jobName", "jobGroup", dateOf(8, 0, 0, 1, 1, 2012), null, new TimeOfDay(8, 0, 0), new TimeOfDay(17, 0, 0), IntervalUnit.HOUR, 1); assertEquals("triggerName", trigger.getName()); assertEquals("triggerGroup", trigger.getGroup()); assertEquals("jobName", trigger.getJobName()); assertEquals("jobGroup", trigger.getJobGroup()); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getStartTime()); assertNull(trigger.getEndTime()); assertEquals(new TimeOfDay(8, 0, 0), trigger.getStartTimeOfDay()); assertEquals(new TimeOfDay(17, 0, 0), trigger.getEndTimeOfDay()); assertEquals(IntervalUnit.HOUR, trigger.getRepeatIntervalUnit()); assertEquals(1, trigger.getRepeatInterval()); trigger = new DailyTimeIntervalTriggerImpl( "triggerName", "triggerGroup", dateOf(8, 0, 0, 1, 1, 2012), null, new TimeOfDay(8, 0, 0), new TimeOfDay(17, 0, 0), IntervalUnit.HOUR, 1); assertEquals("triggerName", trigger.getName()); assertEquals("triggerGroup", trigger.getGroup()); assertNull(trigger.getJobName()); assertEquals("DEFAULT", trigger.getJobGroup()); assertEquals(dateOf(8, 0, 0, 1, 1, 2012), trigger.getStartTime()); assertNull(trigger.getEndTime()); assertEquals(new TimeOfDay(8, 0, 0), trigger.getStartTimeOfDay()); assertEquals(new TimeOfDay(17, 0, 0), trigger.getEndTimeOfDay()); assertEquals(IntervalUnit.HOUR, trigger.getRepeatIntervalUnit()); assertEquals(1, trigger.getRepeatInterval()); } @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, 0}) void testSetRepeatIntervalWithInvalidValues(int repeatInterval) { DailyTimeIntervalTriggerImpl trigger = new DailyTimeIntervalTriggerImpl(); assertThrows(IllegalArgumentException.class, () -> trigger.setRepeatInterval(repeatInterval)); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/HelloJob.java ================================================ package org.quartz.integrations.tests; /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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. * */ import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is just a simple job that says "Hello" to the world. *

* * @author Bill Kratzer */ public class HelloJob implements Job { private static Logger _log = LoggerFactory.getLogger(HelloJob.class); /** *

* Empty constructor for job initialization *

*

* Quartz requires a public empty constructor so that the scheduler can * instantiate the class whenever it needs. *

*/ public HelloJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // Say Hello to the World and display the date/time _log.info("Hello World! - " + new Date()); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/JdbcQuartzDerbyUtilities.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.nio.file.FileSystems; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.quartz.utils.ConnectionProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class JdbcQuartzDerbyUtilities { private static final Logger LOG = LoggerFactory .getLogger(JdbcQuartzDerbyUtilities.class); private static final String DATABASE_DRIVER_CLASS = "org.apache.derby.jdbc.ClientDriver"; public static final String DATABASE_PORT = System.getProperty("test.databasePort", "1527");; public static final String DATABASE_CONNECTION_PREFIX; private static final List DATABASE_SETUP_STATEMENTS; private static final List DATABASE_TEARDOWN_STATEMENTS; private static final String DERBY_DIRECTORY; private final static Properties PROPS = new Properties(); static { String derbyDirectory; if (System.getProperty("buildDirectory") != null) { // running the tests from maven, the db will be stored in target/ derbyDirectory = System.getProperty("buildDirectory"); } else if (System.getProperty("org.gradle.internal.worker.tmpdir") != null) { derbyDirectory = System.getProperty("org.gradle.internal.worker.tmpdir"); } else { derbyDirectory = System.getProperty("java.io.tmpdir"); } if(derbyDirectory.endsWith(":")) { derbyDirectory = derbyDirectory.substring(0, derbyDirectory.length() - 1); } if(!derbyDirectory.endsWith("/") && !derbyDirectory.endsWith("\\")) { derbyDirectory = derbyDirectory + FileSystems.getDefault().getSeparator(); } derbyDirectory = derbyDirectory + "quartzTestDB_" + System.nanoTime(); DERBY_DIRECTORY = derbyDirectory; LOG.info("The test db will be stored in " + derbyDirectory); DATABASE_CONNECTION_PREFIX = "jdbc:derby://localhost:" + DATABASE_PORT + "/" + DERBY_DIRECTORY + ";create=true"; PROPS.setProperty("user","quartz"); PROPS.setProperty("password","quartz"); try { Class.forName(DATABASE_DRIVER_CLASS).newInstance(); } catch (ClassNotFoundException e) { throw new AssertionError(e); } catch (InstantiationException e) { throw new AssertionError(e); } catch (IllegalAccessException e) { throw new AssertionError(e); } List setup = new ArrayList(); String setupScript; try { InputStream setupStream = DerbyConnectionProvider.class .getClassLoader().getResourceAsStream("org/quartz/impl/jdbcjobstore/tables_derby.sql"); try { BufferedReader r = new BufferedReader(new InputStreamReader(setupStream, "US-ASCII")); StringBuilder sb = new StringBuilder(); while (true) { String line = r.readLine(); if (line == null) { break; } else if (!line.startsWith("--")) { sb.append(line).append("\n"); } } setupScript = sb.toString(); } finally { setupStream.close(); } } catch (IOException e) { throw new AssertionError(e); } for (String command : setupScript.split(";")) { if (!command.matches("\\s*")) { setup.add(command); } } DATABASE_SETUP_STATEMENTS = setup; List tearDown = new ArrayList(); String tearDownScript; try { InputStream tearDownStream = DerbyConnectionProvider.class .getClassLoader().getResourceAsStream("tables_derby_drop.sql"); try { BufferedReader r = new BufferedReader(new InputStreamReader(tearDownStream, "US-ASCII")); StringBuilder sb = new StringBuilder(); while (true) { String line = r.readLine(); if (line == null) { break; } else if (!line.startsWith("--")) { sb.append(line).append("\n"); } } tearDownScript = sb.toString(); } finally { tearDownStream.close(); } } catch (IOException e) { throw new AssertionError(e); } for (String command : tearDownScript.split(";")) { if (!command.matches("\\s*")) { tearDown.add(command); } } DATABASE_TEARDOWN_STATEMENTS = tearDown; } public static void createDatabase() throws SQLException { File derbyDirectory = new File(DERBY_DIRECTORY); delete(derbyDirectory); Connection conn = DriverManager.getConnection(DATABASE_CONNECTION_PREFIX ,PROPS); try { Statement statement = conn.createStatement(); for (String command : DATABASE_SETUP_STATEMENTS) { statement.addBatch(command); } statement.executeBatch(); } finally { conn.close(); } } public static int triggersInAcquiredState() throws SQLException { int triggersInAcquiredState = 0; Connection conn = DriverManager.getConnection(DATABASE_CONNECTION_PREFIX, PROPS); try { Statement statement = conn.createStatement(); ResultSet result = statement.executeQuery("SELECT count( * ) FROM QRTZ_TRIGGERS WHERE TRIGGER_STATE = 'ACQUIRED' "); while (result.next()) { triggersInAcquiredState = result.getInt(1); } } finally { conn.close(); } return triggersInAcquiredState; } public static BigDecimal timesTriggered(String triggerName,String triggerGroup) throws SQLException { BigDecimal timesTriggered = BigDecimal.ZERO; Connection conn = DriverManager.getConnection(DATABASE_CONNECTION_PREFIX, PROPS); try { PreparedStatement ps = conn.prepareStatement("SELECT TIMES_TRIGGERED FROM QRTZ_SIMPLE_TRIGGERS WHERE TRIGGER_NAME = ? AND TRIGGER_GROUP = ? "); ps.setString(1, triggerName); ps.setString(2, triggerGroup); ResultSet result = ps.executeQuery(); result.next(); timesTriggered = result.getBigDecimal(1); } finally { conn.close(); } return timesTriggered; } public static void destroyDatabase() throws SQLException { Connection conn = DriverManager.getConnection(DATABASE_CONNECTION_PREFIX ,PROPS); try { Statement statement = conn.createStatement(); for (String command : DATABASE_TEARDOWN_STATEMENTS) { statement.addBatch(command); } statement.executeBatch(); } finally { conn.close(); } File derbyDirectory = new File(DERBY_DIRECTORY); delete(derbyDirectory); } static class DerbyConnectionProvider implements ConnectionProvider { public Connection getConnection() throws SQLException { return DriverManager.getConnection(DATABASE_CONNECTION_PREFIX , PROPS); } public void shutdown() throws SQLException { // nothing to do } @Override public void initialize() throws SQLException { // nothing to do } } private JdbcQuartzDerbyUtilities() { // not instantiable } static void delete(File f) { if (f.isDirectory()) { for (File c : f.listFiles()) delete(c); } if (!f.delete()) LOG.debug("Failed to delete file: " + f +" certainly because it does not exist yet"); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/JobClassNotFoundExceptionErrorsTriggersTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import org.junit.jupiter.api.Test; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.JobExecutionException; import org.quartz.SchedulerException; import org.quartz.simpl.CascadingClassLoadHelper; import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS; public class JobClassNotFoundExceptionErrorsTriggersTest extends QuartzDerbyTestSupport { private static final String BARRIER_KEY = "BARRIER"; public static class BadJob implements Job { public void execute(JobExecutionContext context) { //no-op } } public static class GoodJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { try { ((CyclicBarrier) context.getScheduler().getContext().get(BARRIER_KEY)).await(20, TimeUnit.SECONDS); } catch (SchedulerException ex) { throw new JobExecutionException(ex); } catch (InterruptedException ex) { throw new JobExecutionException(ex); } catch (BrokenBarrierException ex) { throw new JobExecutionException(ex); } catch (TimeoutException ex) { throw new JobExecutionException(ex); } } } public static class SpecialClassLoadHelper extends CascadingClassLoadHelper { @Override public Class loadClass(String name) throws ClassNotFoundException { if (BadJob.class.getName().equals(name)) { throw new ClassNotFoundException(); } else { return super.loadClass(name); } } } protected Properties createSchedulerProperties() { Properties properties = super.createSchedulerProperties(); properties.put(PROP_SCHED_CLASS_LOAD_HELPER_CLASS, SpecialClassLoadHelper.class.getName()); return properties; } @Test void testJobClassNotFoundDoesntBlock() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); scheduler.getContext().put(BARRIER_KEY, barrier); JobDetail goodJob = JobBuilder.newJob(GoodJob.class).withIdentity("good").build(); JobDetail badJob = JobBuilder.newJob(BadJob.class).withIdentity("bad").build(); long now = System.currentTimeMillis(); Trigger goodTrigger = TriggerBuilder.newTrigger().withIdentity("good").forJob(goodJob) .startAt(new Date(now + 1)) .build(); Trigger badTrigger = TriggerBuilder.newTrigger().withIdentity("bad").forJob(badJob) .startAt(new Date(now)) .build(); Map> toSchedule = new HashMap>(); toSchedule.put(badJob, Collections.singleton(badTrigger)); toSchedule.put(goodJob, Collections.singleton(goodTrigger)); scheduler.scheduleJobs(toSchedule, true); barrier.await(20, TimeUnit.SECONDS); assertThat(scheduler.getTriggerState(badTrigger.getKey()), is(Trigger.TriggerState.ERROR)); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/JobDataMapStorageTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import org.quartz.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import static org.quartz.TriggerKey.triggerKey; /** * Created by zemian on 10/25/16. */ public class JobDataMapStorageTest extends QuartzDerbyTestSupport { @Test void testJobDataMapDirtyFlag() throws Exception { JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test") .usingJobData("jfoo", "bar") .build(); CronTrigger trigger = newTrigger() .withIdentity("test") .withSchedule(cronSchedule("0 0 0 * * ?")) .usingJobData("tfoo", "bar") .build(); scheduler.scheduleJob(jobDetail, trigger); JobDetail storedJobDetail = scheduler.getJobDetail(JobKey.jobKey("test")); JobDataMap storedJobMap = storedJobDetail.getJobDataMap(); assertThat(storedJobMap.isDirty(), is(false)); Trigger storedTrigger = scheduler.getTrigger(triggerKey("test")); JobDataMap storedTriggerMap = storedTrigger.getJobDataMap(); assertThat(storedTriggerMap.isDirty(), is(false)); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QTZ179_TriggerLostAfterDbRestart_Test.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.junit.jupiter.api.Test; import org.quartz.JobDetail; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.fail; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; public class QTZ179_TriggerLostAfterDbRestart_Test extends QuartzDerbyTestSupport { private static final long DURATION_OF_FIRST_SCHEDULING = 9L; private static final long DURATION_OF_NETWORK_FAILURE = 10L; private static final long DURATION_OF_SECOND_SCHEDULING = 10L; private static final Logger LOG = LoggerFactory.getLogger(QTZ179_TriggerLostAfterDbRestart_Test.class); private static final int INTERVAL_IN_SECONDS = 3; private static Trigger trigger1_1; private static Trigger trigger2_1; private static Trigger trigger1_2; private static Trigger trigger2_2; @Override protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName", "TestScheduler"); properties.put("org.quartz.scheduler.instanceId", "AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck", "true"); properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount", "12"); properties.put("org.quartz.threadPool.threadPriority", "5"); properties.put("org.quartz.jobStore.misfireThreshold", "10000"); properties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); properties.put("org.quartz.jobStore.useProperties", "true"); properties.put("org.quartz.jobStore.dataSource", "myDS"); properties.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); properties.put("org.quartz.jobStore.isClustered", "false"); properties.put("org.quartz.dataSource.myDS.driver", "org.apache.derby.jdbc.ClientDriver"); properties.put("org.quartz.dataSource.myDS.URL", JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX); properties.put("org.quartz.dataSource.myDS.user", "quartz"); properties.put("org.quartz.dataSource.myDS.password", "quartz"); properties.put("org.quartz.dataSource.myDS.maxConnections", "5"); return properties; } @Override protected void afterSchedulerInit() throws Exception { LOG.info("------- Scheduling Job -------------------"); // define the jobs and tie them to our HelloJob class JobDetail job1_1 = newJob(HelloJob.class).withIdentity("job1", "group1").build(); JobDetail job2_1 = newJob(HelloJob.class).withIdentity("job2", "group1").build(); JobDetail job1_2 = newJob(HelloJob.class).withIdentity("job1", "group2").build(); JobDetail job2_2 = newJob(HelloJob.class).withIdentity("job2", "group2").build(); trigger1_1 = newTrigger() .withIdentity("job1", "group1") .startNow() .withSchedule( SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(INTERVAL_IN_SECONDS) .repeatForever()) .build(); trigger2_1 = newTrigger() .withIdentity("job2", "group1") .startNow() .withSchedule( SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(INTERVAL_IN_SECONDS) .repeatForever()) .build(); trigger1_2 = newTrigger() .withIdentity("job1", "group2") .startNow() .withSchedule( SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(INTERVAL_IN_SECONDS) .repeatForever()) .build(); trigger2_2 = newTrigger() .withIdentity("job2", "group2") .startNow() .withSchedule( SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(INTERVAL_IN_SECONDS) .repeatForever()) .build(); Trigger[] triggers = new Trigger[]{trigger1_1, trigger1_2, trigger2_1, trigger2_2}; JobDetail[] jobDetails = new JobDetail[]{job1_1, job1_2, job2_1, job2_2}; for (int i = 0; i < triggers.length; i++) { JobDetail job = jobDetails[i]; Trigger trigger = triggers[i]; if (scheduler.checkExists(job.getKey())) { // the job already exists in jdbcjobstore; let's reschedule it scheduler.rescheduleJob(trigger.getKey(), trigger); } else { scheduler.scheduleJob(job, trigger); } } // Start up the scheduler (nothing can actually run until the // scheduler has been started) scheduler.start(); } @Test public void checkAll4TriggersStillRunningTest() throws Exception { LOG.info("------- Scheduler Started -----------------"); // wait long enough so that the scheduler as an opportunity to // run the job! try { Thread.sleep(DURATION_OF_FIRST_SCHEDULING * 1000L); } catch (Exception e) { } //there should be maximum 1 trigger in acquired state if (JdbcQuartzDerbyUtilities.triggersInAcquiredState() > 1) { fail("There should not be more than 1 trigger in ACQUIRED state in the DB."); } // Shutting down and starting up again the database to simulate a // network error try { LOG.info("------- Shutting down database ! -----------------"); derbyServer.shutdown(); Thread.sleep(DURATION_OF_NETWORK_FAILURE * 1000L); derbyServer.start(null); LOG.info("------- Database back online ! -----------------"); Thread.sleep(DURATION_OF_SECOND_SCHEDULING * 1000L); } catch (Exception e) { e.printStackTrace(); } int triggersInAcquiredState = JdbcQuartzDerbyUtilities.triggersInAcquiredState(); assertFalse(triggersInAcquiredState > 1, "There should not be more than 1 trigger in ACQUIRED state in the DB, but found " + triggersInAcquiredState); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QTZ283_IgnoreMisfirePolicyJdbcStore_Test.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.junit.jupiter.api.Test; import org.quartz.DateBuilder; import org.quartz.JobDetail; import org.quartz.SimpleTrigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; import java.sql.SQLException; import java.util.Date; import java.util.Properties; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; public class QTZ283_IgnoreMisfirePolicyJdbcStore_Test extends QuartzDerbyTestSupport { private static final long DURATION_OF_FIRST_SCHEDULING = 10L; private static final Logger LOG = LoggerFactory.getLogger(QTZ283_IgnoreMisfirePolicyJdbcStore_Test.class); private static final int INTERVAL_IN_SECONDS = 5; @Override protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName", "TestScheduler"); properties.put("org.quartz.scheduler.instanceId", "AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck", "true"); properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount", "12"); properties.put("org.quartz.threadPool.threadPriority", "5"); properties.put("org.quartz.jobStore.misfireThreshold", "10000"); properties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); properties.put("org.quartz.jobStore.useProperties", "true"); properties.put("org.quartz.jobStore.dataSource", "myDS"); properties.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); properties.put("org.quartz.jobStore.isClustered", "false"); properties.put("org.quartz.dataSource.myDS.driver", "org.apache.derby.jdbc.ClientDriver"); properties.put("org.quartz.dataSource.myDS.URL", JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX); properties.put("org.quartz.dataSource.myDS.user", "quartz"); properties.put("org.quartz.dataSource.myDS.password", "quartz"); properties.put("org.quartz.dataSource.myDS.maxConnections", "5"); return properties; } @Override protected void afterSchedulerInit() throws Exception { LOG.info("------- Scheduling Job -------------------"); // define the jobs and tie them to our HelloJob class JobDetail job1 = newJob(HelloJob.class).withIdentity("job1", "group1").build(); // trigger should have started the even minute before now // due to its ignore policy, it will be triggered Date startTime1 = DateBuilder.evenMinuteDateBefore(null); SimpleTrigger oldtriggerMisfirePolicyIgnore = newTrigger() .withIdentity("trigger1", "group1") .startAt(startTime1) .withSchedule( simpleSchedule().withIntervalInSeconds(INTERVAL_IN_SECONDS) .repeatForever() .withMisfireHandlingInstructionIgnoreMisfires()) .build(); if (scheduler.checkExists(job1.getKey())) { // the job already exists in jdbcjobstore; let's reschedule it scheduler.rescheduleJob(oldtriggerMisfirePolicyIgnore.getKey(), oldtriggerMisfirePolicyIgnore); } else { scheduler.scheduleJob(job1, oldtriggerMisfirePolicyIgnore); } // Start up the scheduler (nothing can actually run until the // scheduler has been started) scheduler.start(); LOG.info("------- Scheduler Started -----------------"); // wait long enough so that the scheduler as an opportunity to // run the job! try { Thread.sleep(DURATION_OF_FIRST_SCHEDULING * 1000L); } catch (Exception e) { e.printStackTrace(); } } @Test public void checkOldTriggerGetsFired() throws SQLException { BigDecimal misfirePolicyIgnoreTimesTriggered = JdbcQuartzDerbyUtilities.timesTriggered("trigger1", "group1"); assertThat("The old trigger has never been fired, even if the policy is ignore", misfirePolicyIgnoreTimesTriggered, not(equalTo(BigDecimal.ZERO))); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QTZ336_MissSchedulingChangeSignalTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Properties; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.RAMJobStore; import org.quartz.spi.OperableTrigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Integration test for reproducing QTZ-336 where we don't check for the scheduling change signal. */ @Disabled /* ignoring now since the bug is years ago fixed, and the test takes a full minute! */ class QTZ336_MissSchedulingChangeSignalTest { private static final Logger LOG = LoggerFactory.getLogger(QTZ336_MissSchedulingChangeSignalTest.class); @Test void simpleScheduleAlwaysFiredUnder20s() throws Exception { Properties properties = new Properties(); InputStream propertiesIs = getClass().getResourceAsStream("/org/quartz/quartz.properties"); try { properties.load(propertiesIs); } finally { propertiesIs.close(); } properties.setProperty("org.quartz.scheduler.name", "QTZ336_MissSchedulingChangeSignalTest.simpleScheduleAlwaysFiredUnder20s"); properties.setProperty("org.quartz.scheduler.skipUpdateCheck", "true"); // Use a custom RAMJobStore to produce context switches leading to the race condition properties.setProperty("org.quartz.jobStore.class", SlowRAMJobStore.class.getName()); SchedulerFactory sf = new StdSchedulerFactory(properties); Scheduler sched = sf.getScheduler(); LOG.info("------- Initialization Complete -----------"); LOG.info("------- Scheduling Job -------------------"); JobDetail job = newJob(CollectDurationBetweenFireTimesJob.class).withIdentity("job", "group").build(); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startAt(new Date(System.currentTimeMillis() + 1000)) .withSchedule(simpleSchedule() .withIntervalInSeconds(1) .repeatForever() .withMisfireHandlingInstructionIgnoreMisfires()) .build(); sched.scheduleJob(job, trigger); // Start up the scheduler (nothing can actually run until the // scheduler has been started) sched.start(); LOG.info("------- Scheduler Started -----------------"); // wait long enough so that the scheduler has an opportunity to // run the job in theory around 50 times try { Thread.sleep(50000L); } catch (Exception e) { e.printStackTrace(); } List durationBetweenFireTimesInMillis = CollectDurationBetweenFireTimesJob.getDurations(); assertFalse(durationBetweenFireTimesInMillis.isEmpty(), "Job was not executed once!"); // Let's check that every call for around 1 second and not between 23 and 30 seconds // which would be the case if the scheduling change signal were not checked for (long durationInMillis : durationBetweenFireTimesInMillis) { assertTrue(durationInMillis < 20000, "Missed an execution with one duration being between two fires: " + durationInMillis + " (all: " + durationBetweenFireTimesInMillis + ")"); } } /** * A simple job for collecting fire times in order to check that we did not miss one call, for having the race * condition the job must be real quick and not allowing concurrent executions. */ @DisallowConcurrentExecution public static class CollectDurationBetweenFireTimesJob implements Job { private static final Logger log = LoggerFactory.getLogger(CollectDurationBetweenFireTimesJob.class); private static final List durationBetweenFireTimes = Collections.synchronizedList(new ArrayList()); private static Long lastFireTime = null; public void execute(JobExecutionContext context) throws JobExecutionException { Date now = new Date(); log.info("Fire time: " + now); if (lastFireTime != null) { durationBetweenFireTimes.add(now.getTime() - lastFireTime); } lastFireTime = now.getTime(); } /** * Retrieves the durations between fire times. * * @return the durations in millis as an immutable list. */ public static List getDurations() { synchronized (durationBetweenFireTimes) { return Collections.unmodifiableList(new ArrayList(durationBetweenFireTimes)); } } } /** * Custom RAMJobStore for producing context switches. */ public static class SlowRAMJobStore extends RAMJobStore { @Override public List acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow) { List nextTriggers = super.acquireNextTriggers(noLaterThan, maxCount, timeWindow); try { // Wait just a bit for hopefully having a context switch leading to the race condition Thread.sleep(10); } catch (InterruptedException e) { } return nextTriggers; } } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzDatabasePauseAndResumeTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.junit.jupiter.api.Test; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.impl.matchers.GroupMatcher; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.not; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; /** * Created by zemian on 10/25/16. */ public class QuartzDatabasePauseAndResumeTest extends QuartzDerbyTestSupport { @Test void testPauseAndResumeTriggers() throws Exception { JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test_1") .build(); CronTrigger trigger = newTrigger() .withIdentity("test_1", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test_1", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_1", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_1", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } @Test void testResumeTriggersBeforeAddJob() throws Exception { scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("abc")); scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("abc")); JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test_2") .build(); CronTrigger trigger = newTrigger() .withIdentity("test_2", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test_2", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_2", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_2", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } @Test void testPauseAndResumeJobs() throws Exception { JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test_3", "abc") .build(); CronTrigger trigger = newTrigger() .withIdentity("test_3", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test_3", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_3", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_3", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } @Test void testResumeJobsBeforeAddJobs() throws Exception { scheduler.pauseJobs(GroupMatcher.jobGroupEquals("abc")); scheduler.resumeJobs(GroupMatcher.jobGroupEquals("abc")); JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test_4", "abc") .build(); CronTrigger trigger = newTrigger() .withIdentity("test_4", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test_4", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_4", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test_4", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzDatabaseSimplePropertiesTest.java ================================================ /* * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.math.BigDecimal; import java.util.Properties; import org.junit.jupiter.api.Test; import org.quartz.JobDetail; import org.quartz.ScheduleBuilder; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.quartz.TriggerKey; import org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerPersistenceDelegateSupport; import org.quartz.impl.jdbcjobstore.SimplePropertiesTriggerProperties; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.spi.MutableTrigger; import org.quartz.spi.OperableTrigger; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.samePropertyValuesAs; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; public class QuartzDatabaseSimplePropertiesTest extends QuartzDerbyTestSupport { @Override protected Properties createSchedulerProperties() { Properties defaultSchedulerProperties = super.createSchedulerProperties(); defaultSchedulerProperties.put( "org.quartz.jobStore.driverDelegateInitString", "triggerPersistenceDelegateClasses=" + TestSimplePropertiesTriggerPersistenceDelegate.class.getName() ); return defaultSchedulerProperties; } @Test public void insertAndLoadProperties() throws SchedulerException { TriggerKey triggerKey = new TriggerKey("test"); TestTriggerImpl trigger = (TestTriggerImpl) newTrigger() .withIdentity(triggerKey) .withSchedule(new TestTriggerScheduleBuilder()) .build(); SimplePropertiesTriggerProperties insertedProperties = new SimplePropertiesTriggerProperties(); insertedProperties.setString1(""); insertedProperties.setString2(null); insertedProperties.setString3("test-string"); insertedProperties.setInt1(Integer.MIN_VALUE); insertedProperties.setInt2(Integer.MAX_VALUE); insertedProperties.setLong1(Long.MIN_VALUE); insertedProperties.setLong2(Long.MAX_VALUE); insertedProperties.setDecimal1(new BigDecimal("0.0000")); insertedProperties.setDecimal2(new BigDecimal("-10.0000")); insertedProperties.setBoolean1(true); insertedProperties.setBoolean2(false); trigger.setAdditionalProperties(insertedProperties); JobDetail job = newJob(HelloJob.class).withIdentity("test").build(); scheduler.scheduleJob(job, trigger); TestTriggerImpl loadedTrigger = ((TestTriggerImpl) scheduler.getTrigger(triggerKey)); SimplePropertiesTriggerProperties loadedProperties = loadedTrigger.getAdditionalProperties(); assertThat(loadedProperties, samePropertyValuesAs(insertedProperties)); } public static class TestTriggerImpl extends SimpleTriggerImpl { private SimplePropertiesTriggerProperties additionalProperties; @Override public boolean hasAdditionalProperties() { return true; } public SimplePropertiesTriggerProperties getAdditionalProperties() { return additionalProperties; } public void setAdditionalProperties(SimplePropertiesTriggerProperties additionalProperties) { this.additionalProperties = additionalProperties; } @Override public ScheduleBuilder getScheduleBuilder() { return new TestTriggerScheduleBuilder(); } } public static class TestTriggerScheduleBuilder extends ScheduleBuilder { @Override protected MutableTrigger build() { return new TestTriggerImpl(); } } public static class TestSimplePropertiesTriggerPersistenceDelegate extends SimplePropertiesTriggerPersistenceDelegateSupport { @Override public boolean canHandleTriggerType(OperableTrigger trigger) { return trigger instanceof TestTriggerImpl; } @Override public String getHandledTriggerTypeDiscriminator() { return "TEST"; } @Override protected SimplePropertiesTriggerProperties getTriggerProperties(OperableTrigger trigger) { return ((TestTriggerImpl) trigger).getAdditionalProperties(); } @Override protected TriggerPropertyBundle getTriggerPropertyBundle(SimplePropertiesTriggerProperties properties) { return new TriggerPropertyBundle( new TestTriggerScheduleBuilder(), new String[] {"additionalProperties"}, new Object[] {properties} ); } } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzDerbyCronTriggerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.Is.is; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import org.quartz.*; import static org.quartz.integrations.tests.TrackingJob.SCHEDULED_TIMES_KEY; /** * A integration test for Quartz Database Scheduler with Cron Trigger. * @author Zemian Deng */ public class QuartzDerbyCronTriggerTest extends QuartzDerbyTestSupport { @Test void testCronRepeatCount() throws Exception { CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("test") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")) .build(); List scheduledTimes = Collections.synchronizedList(new LinkedList()); scheduler.getContext().put(SCHEDULED_TIMES_KEY, scheduledTimes); JobDetail jobDetail = JobBuilder.newJob(TrackingJob.class).withIdentity("test").build(); scheduler.scheduleJob(jobDetail, trigger); for (int i = 0; i < 20 && scheduledTimes.size() < 3; i++) { Thread.sleep(500); } assertThat(scheduledTimes, hasSize(greaterThanOrEqualTo(3))); Long[] times = scheduledTimes.toArray(new Long[scheduledTimes.size()]); long baseline = times[0]; assertThat(baseline % 1000, is(0L)); for (int i = 1; i < times.length; i++) { assertThat(times[i], is(baseline + TimeUnit.SECONDS.toMillis(i))); } } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzDerbyTestSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.apache.derby.drda.NetworkServerControl; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.PrintWriter; import java.net.InetAddress; import java.sql.SQLException; import java.util.Properties; /** * A base class to support database (DERBY) scheduler integration testing. Each test will have a fresh * scheduler created and started, and it will auto shutdown upon each test run. The database will * be created with schema before class and destroy after class test. * * @author Zemian Deng */ public class QuartzDerbyTestSupport extends QuartzMemoryTestSupport { protected static final Logger LOG = LoggerFactory.getLogger(QuartzDerbyTestSupport.class); protected static NetworkServerControl derbyServer; @BeforeAll public static void initialize() throws Exception { LOG.info("Starting DERBY database."); InetAddress localhost = InetAddress.getByName("localhost"); int portNum = Integer.parseInt(JdbcQuartzDerbyUtilities.DATABASE_PORT); derbyServer = new NetworkServerControl(localhost, portNum); derbyServer.start(new PrintWriter(System.out)); int tries = 0; while (tries < 5) { try { Thread.sleep(500); derbyServer.ping(); break; } catch (Exception e) { tries++; } } if (tries == 5) { throw new Exception("Failed to start Derby!"); } LOG.info("Database started"); try { LOG.info("Creating Database tables for Quartz."); JdbcQuartzDerbyUtilities.createDatabase(); LOG.info("Database tables created."); } catch (SQLException e) { throw new Exception("Failed to create Quartz tables.", e); } } @AfterAll public static void shutdownDb() throws Exception { try { LOG.info("Destroying Database."); JdbcQuartzDerbyUtilities.destroyDatabase(); LOG.info("Database destroyed."); } catch (SQLException e) { e.printStackTrace(); e.getNextException().printStackTrace(); throw new AssertionError(e); } derbyServer.shutdown(); LOG.info("Database shutdown."); } protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName","TestScheduler"); properties.put("org.quartz.scheduler.instanceId","AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck","true"); properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount","12"); properties.put("org.quartz.threadPool.threadPriority","5"); properties.put("org.quartz.jobStore.misfireThreshold","10000"); properties.put("org.quartz.jobStore.class","org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.put("org.quartz.jobStore.driverDelegateClass","org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); properties.put("org.quartz.jobStore.useProperties","true"); properties.put("org.quartz.jobStore.dataSource","myDS"); properties.put("org.quartz.jobStore.tablePrefix","QRTZ_"); properties.put("org.quartz.jobStore.isClustered", "false"); properties.put("org.quartz.dataSource.myDS.driver", "org.apache.derby.jdbc.ClientDriver"); properties.put("org.quartz.dataSource.myDS.URL",JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX); properties.put("org.quartz.dataSource.myDS.user","quartz"); properties.put("org.quartz.dataSource.myDS.password","quartz"); properties.put("org.quartz.dataSource.myDS.maxConnections","5"); return properties; } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzMSSQLDatabaseCronTriggerTest.java ================================================ /* * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import org.quartz.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.Is.is; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.quartz.integrations.tests.TrackingJob.SCHEDULED_TIMES_KEY; /** * A integration test for Quartz MSSQL Database Scheduler with Cron Trigger. * * @author Arnaud Mergey */ public class QuartzMSSQLDatabaseCronTriggerTest extends QuartzMSSQLTestSupport { @Test void testCronRepeatCount() throws Exception { CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("test") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")) .build(); List scheduledTimes = Collections.synchronizedList(new LinkedList()); scheduler.getContext().put(SCHEDULED_TIMES_KEY, scheduledTimes); JobDetail jobDetail = JobBuilder.newJob(TrackingJob.class).withIdentity("test").build(); scheduler.scheduleJob(jobDetail, trigger); for (int i = 0; i < 20 && scheduledTimes.size() < 3; i++) { Thread.sleep(500); } assertThat(scheduledTimes, hasSize(greaterThanOrEqualTo(3))); Long[] times = scheduledTimes.toArray(new Long[scheduledTimes.size()]); long baseline = times[0]; assertThat(baseline % 1000, is(0L)); for (int i = 1; i < times.length; i++) { assertThat(times[i], is(baseline + TimeUnit.SECONDS.toMillis(i))); } } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzMSSQLTestSupport.java ================================================ /* * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.sql.SQLException; import java.util.Properties; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.impl.jdbcjobstore.MSSQLDelegate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A base class to support database (MSSQL) scheduler integration testing. Each * test will have a fresh scheduler created and started, and it will auto * shutdown upon each test run. The database will be created with schema before * class and destroy after class test. * * @author Arnaud Mergey */ public class QuartzMSSQLTestSupport extends QuartzMemoryTestSupport { protected static final Logger LOG = LoggerFactory.getLogger(QuartzMSSQLTestSupport.class); private static final DatabaseType DATABASE_TYPE = DatabaseType.MSSQL; @BeforeAll public static void initialize() throws Exception { try { LOG.info("Creating Database tables for Quartz."); JdbcQuartzTestUtilities.createDatabase(DATABASE_TYPE.name(), DATABASE_TYPE); LOG.info("Database tables created."); } catch (SQLException e) { throw new Exception("Failed to create Quartz tables.", e); } } @AfterAll public static void shutdownDb() throws Exception { JdbcQuartzTestUtilities.shutdownDatabase(DATABASE_TYPE.name(),DATABASE_TYPE); LOG.info("Database shutdown."); } protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName", "TestScheduler"); properties.put("org.quartz.scheduler.instanceId", "AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck", "true"); properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount", "12"); properties.put("org.quartz.threadPool.threadPriority", "5"); properties.put("org.quartz.jobStore.misfireThreshold", "10000"); properties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.put("org.quartz.jobStore.driverDelegateClass", DATABASE_TYPE.getDelegateClassName()); properties.put("org.quartz.jobStore.useProperties", "true"); properties.put("org.quartz.jobStore.dataSource", DATABASE_TYPE.name()); properties.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); properties.put("org.quartz.jobStore.isClustered", "false"); return properties; } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzMemoryCronTriggerTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.Is.is; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import org.quartz.*; import static org.quartz.integrations.tests.TrackingJob.SCHEDULED_TIMES_KEY; /** * A integration test for Quartz In-Memory Scheduler with Cron Trigger. * @author Zemian Deng */ public class QuartzMemoryCronTriggerTest extends QuartzMemoryTestSupport { @Test void testCronRepeatCount() throws Exception { CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("test") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")) .build(); List scheduledTimes = Collections.synchronizedList(new LinkedList()); scheduler.getContext().put(SCHEDULED_TIMES_KEY, scheduledTimes); JobDetail jobDetail = JobBuilder.newJob(TrackingJob.class).withIdentity("test").build(); scheduler.scheduleJob(jobDetail, trigger); for (int i = 0; i < 20 && scheduledTimes.size() < 3; i++) { Thread.sleep(500); } assertThat(scheduledTimes, hasSize(greaterThanOrEqualTo(3))); Long[] times = scheduledTimes.toArray(new Long[scheduledTimes.size()]); long baseline = times[0]; assertThat(baseline % 1000, is(0L)); for (int i = 1; i < times.length; i++) { assertThat(times[i], is(baseline + TimeUnit.SECONDS.toMillis(i))); } } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzMemoryPauseAndResumeTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.junit.jupiter.api.Test; import org.quartz.impl.matchers.GroupMatcher; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.not; import org.quartz.*; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import static org.quartz.TriggerKey.triggerKey; /** * Created by zemian on 10/25/16. */ public class QuartzMemoryPauseAndResumeTest extends QuartzMemoryTestSupport { @Test void testPauseAndResumeTriggers() throws Exception { JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test") .build(); CronTrigger trigger = newTrigger() .withIdentity("test", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } @Test void testResumeTriggersBeforeAddJob() throws Exception { scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("abc")); scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("abc")); JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test") .build(); CronTrigger trigger = newTrigger() .withIdentity("test", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } @Test void testPauseAndResumeJobs() throws Exception { JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test", "abc") .build(); CronTrigger trigger = newTrigger() .withIdentity("test", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } @Test void testResumeJobsBeforeAddJobs() throws Exception { scheduler.pauseJobs(GroupMatcher.jobGroupEquals("abc")); scheduler.resumeJobs(GroupMatcher.jobGroupEquals("abc")); JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("test", "abc") .build(); CronTrigger trigger = newTrigger() .withIdentity("test", "abc") .withSchedule(cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); Trigger.TriggerState state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); scheduler.pauseJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.PAUSED)); assertThat(state, not(Trigger.TriggerState.NORMAL)); scheduler.resumeJobs(GroupMatcher.jobGroupEquals("abc")); state = scheduler.getTriggerState(TriggerKey.triggerKey("test", "abc")); assertThat(state, is(Trigger.TriggerState.NORMAL)); assertThat(state, not(Trigger.TriggerState.PAUSED)); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/QuartzMemoryTestSupport.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; /** * A base class to support in-memory scheduler integration testing. Each test will have a fresh * scheduler created and started, and it will auto shutdown upon each test run. * * @author Zemian Deng */ public class QuartzMemoryTestSupport { protected static final Logger LOG = LoggerFactory.getLogger(QuartzMemoryTestSupport.class); protected Scheduler scheduler; @BeforeEach public void initSchedulerBeforeTest() throws Exception { Properties properties = createSchedulerProperties(); SchedulerFactory sf = new StdSchedulerFactory(properties); scheduler = sf.getScheduler(); afterSchedulerInit(); } protected void afterSchedulerInit() throws Exception { LOG.info("Scheduler starting."); scheduler.start(); LOG.info("Scheduler started."); } protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName","TestScheduler"); properties.put("org.quartz.scheduler.instanceId","AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck","true"); properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount","12"); properties.put("org.quartz.threadPool.threadPriority","5"); properties.put("org.quartz.jobStore.misfireThreshold","10000"); return properties; } @AfterEach public void initSchedulerAfterTest() throws Exception { LOG.info("Scheduler shutting down."); scheduler.shutdown(true); LOG.info("Scheduler shutdown complete."); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/StdRowLockSemaphoreTest.java ================================================ package org.quartz.integrations.tests; import java.sql.Connection; import java.util.HashSet; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.jdbcjobstore.LockException; import org.quartz.impl.jdbcjobstore.StdRowLockSemaphore; import static org.hamcrest.MatcherAssert.assertThat; public class StdRowLockSemaphoreTest extends QuartzDerbyTestSupport { static volatile boolean myLockInvoked = false; static volatile int maxRetry = -1; static volatile long retryPeriod = -1; static CountDownLatch latch = new CountDownLatch(1); public static class MyLock extends StdRowLockSemaphore { @Override protected void executeSQL(Connection conn, String lockName, String expandedSQL, String expandedInsertSQL) throws LockException { myLockInvoked = true; maxRetry = getMaxRetry(); retryPeriod = getRetryPeriod(); super.executeSQL(conn, lockName, expandedSQL, expandedInsertSQL); latch.countDown(); } } @Override public void initSchedulerBeforeTest() throws Exception { // Override to use initSchedulerBeforeTest(Properties) instead. } public void initSchedulerBeforeTest(Properties properties) throws Exception { SchedulerFactory sf = new StdSchedulerFactory(properties); scheduler = sf.getScheduler(); afterSchedulerInit(); } Properties createDefaultProperties() { Properties props = super.createSchedulerProperties(); props.setProperty("org.quartz.jobStore.lockHandler.class", "org.quartz.integrations.tests.StdRowLockSemaphoreTest$MyLock"); props.setProperty("org.quartz.jobStore.acquireTriggersWithinLock", "true"); return props; } Properties createMyLockProperties() { Properties props = super.createSchedulerProperties(); props.setProperty("org.quartz.jobStore.lockHandler.class", "org.quartz.integrations.tests.StdRowLockSemaphoreTest$MyLock"); props.setProperty("org.quartz.jobStore.lockHandler.maxRetry", "7"); props.setProperty("org.quartz.jobStore.lockHandler.retryPeriod", "3000"); props.setProperty("org.quartz.jobStore.acquireTriggersWithinLock", "true"); return props; } @Test void testDefaultStdRowLockSemaphore() throws Exception { initSchedulerBeforeTest(createDefaultProperties()); JobDetail job1 = JobBuilder.newJob(HelloJob.class).withIdentity("job1"). build(); HashSet triggers = new HashSet<>(); triggers.add(TriggerBuilder.newTrigger().forJob(job1) .build()); scheduler.scheduleJob(job1, triggers, true); latch.await(1L, TimeUnit.MINUTES); assertThat(myLockInvoked, Matchers.is(true)); assertThat(maxRetry, Matchers.is(3)); assertThat(retryPeriod, Matchers.is(1000L)); } @Test void testCustomStdRowLockSemaphore() throws Exception { initSchedulerBeforeTest(createMyLockProperties()); JobDetail job1 = JobBuilder.newJob(HelloJob.class).withIdentity("job1"). build(); HashSet triggers = new HashSet<>(); triggers.add(TriggerBuilder.newTrigger().forJob(job1) .build()); scheduler.scheduleJob(job1, triggers, true); latch.await(1L, TimeUnit.MINUTES); assertThat(myLockInvoked, Matchers.is(true)); assertThat(maxRetry, Matchers.is(7)); assertThat(retryPeriod, Matchers.is(3000L)); } } ================================================ FILE: quartz/src/test/java/org/quartz/integrations/tests/TrackingJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import java.util.List; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerException; /** * * @author cdennis */ public class TrackingJob implements Job { public static String SCHEDULED_TIMES_KEY = "TrackingJob.ScheduledTimes"; @Override public void execute(JobExecutionContext context) throws JobExecutionException { try { Scheduler scheduler = context.getScheduler(); List scheduledFires = (List)scheduler.getContext().get(SCHEDULED_TIMES_KEY); scheduledFires.add(context.getScheduledFireTime().getTime()); } catch (SchedulerException e) { throw new JobExecutionException(e); } } } ================================================ FILE: quartz/src/test/java/org/quartz/simpl/PropertySettingJobFactoryTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.quartz.JobDataMap; import org.quartz.SchedulerException; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for PropertySettingJobFactory. */ class PropertySettingJobFactoryTest { private PropertySettingJobFactory factory; @BeforeEach protected void setUp() throws Exception { factory = new PropertySettingJobFactory(); factory.setThrowIfPropertyNotFound(true); } @Test void testSetBeanPropsPrimatives() throws SchedulerException { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("intValue", Integer.valueOf(1)); jobDataMap.put("longValue", Long.valueOf(2l)); jobDataMap.put("floatValue", Float.valueOf(3.0f)); jobDataMap.put("doubleValue", Double.valueOf(4.0)); jobDataMap.put("booleanValue", Boolean.TRUE); jobDataMap.put("shortValue", Short.valueOf(((short)5))); jobDataMap.put("charValue", 'a'); jobDataMap.put("byteValue", Byte.valueOf((byte)6)); jobDataMap.put("stringValue", "S1"); jobDataMap.put("mapValue", Collections.singletonMap("A", "B")); TestBean myBean = new TestBean(); factory.setBeanProps(myBean, jobDataMap); assertEquals(1, myBean.getIntValue()); assertEquals(2l, myBean.getLongValue()); assertEquals(3.0f, myBean.getFloatValue(), 0.0001); assertEquals(4.0, myBean.getDoubleValue(), 0.0001); assertTrue(myBean.getBooleanValue()); assertEquals(5, myBean.getShortValue()); assertEquals('a', myBean.getCharValue()); assertEquals((byte)6, myBean.getByteValue()); assertEquals("S1", myBean.getStringValue()); assertTrue(myBean.getMapValue().containsKey("A")); } @Test void testSetBeanPropsUnknownProperty() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("bogusValue", Integer.valueOf(1)); try { factory.setBeanProps(new TestBean(), jobDataMap); fail(); } catch (SchedulerException ignore) { // ignore } } @Test void testSetBeanPropsNullPrimative() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("intValue", null); try { factory.setBeanProps(new TestBean(), jobDataMap); fail(); } catch (SchedulerException ignore) { // ignore } } @Test void testSetBeanPropsNullNonPrimative() throws SchedulerException { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("mapValue", null); TestBean testBean = new TestBean(); testBean.setMapValue(Collections.singletonMap("A", "B")); factory.setBeanProps(testBean, jobDataMap); assertNull(testBean.getMapValue()); } @Test void testSetBeanPropsWrongPrimativeType() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("intValue", new Float(7)); try { factory.setBeanProps(new TestBean(), jobDataMap); fail(); } catch (SchedulerException ignore) { // ignore } } @Test void testSetBeanPropsWrongNonPrimativeType() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("mapValue", new Float(7)); try { factory.setBeanProps(new TestBean(), jobDataMap); fail(); } catch (SchedulerException ignore) { // ignore } } @Test void testSetBeanPropsCharStringTooShort() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("charValue", ""); try { factory.setBeanProps(new TestBean(), jobDataMap); fail(); } catch (SchedulerException ignore) { // ignore } } @Test void testSetBeanPropsCharStringTooLong() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("charValue", "abba"); try { factory.setBeanProps(new TestBean(), jobDataMap); fail(); } catch (SchedulerException ignore) { // ignore } } @Test void testSetBeanPropsFromStrings() throws SchedulerException { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("intValue", "1"); jobDataMap.put("longValue", "2"); jobDataMap.put("floatValue", "3.0"); jobDataMap.put("doubleValue", "4.0"); jobDataMap.put("booleanValue", "true"); jobDataMap.put("shortValue", "5"); jobDataMap.put("charValue", "a"); jobDataMap.put("byteValue", "6"); TestBean myBean = new TestBean(); factory.setBeanProps(myBean, jobDataMap); assertEquals(1, myBean.getIntValue()); assertEquals(2l, myBean.getLongValue()); assertEquals(3.0f, myBean.getFloatValue(), 0.0001); assertEquals(4.0, myBean.getDoubleValue(), 0.0001); assertTrue(myBean.getBooleanValue()); assertEquals(5, myBean.getShortValue()); assertEquals('a', myBean.getCharValue()); assertEquals((byte)6, myBean.getByteValue()); } private static final class TestBean { private int intValue; private long longValue; private float floatValue; private double doubleValue; private boolean booleanValue; private byte byteValue; private short shortValue; private char charValue; private String stringValue; private Map mapValue; public boolean getBooleanValue() { return booleanValue; } @SuppressWarnings("unused") public void setBooleanValue(boolean booleanValue) { this.booleanValue = booleanValue; } public double getDoubleValue() { return doubleValue; } @SuppressWarnings("unused") public void setDoubleValue(double doubleValue) { this.doubleValue = doubleValue; } public float getFloatValue() { return floatValue; } @SuppressWarnings("unused") public void setFloatValue(float floatValue) { this.floatValue = floatValue; } public int getIntValue() { return intValue; } @SuppressWarnings("unused") public void setIntValue(int intValue) { this.intValue = intValue; } public long getLongValue() { return longValue; } @SuppressWarnings("unused") public void setLongValue(long longValue) { this.longValue = longValue; } public Map getMapValue() { return mapValue; } public void setMapValue(Map mapValue) { this.mapValue = mapValue; } public String getStringValue() { return stringValue; } @SuppressWarnings("unused") public void setStringValue(String stringValue) { this.stringValue = stringValue; } public byte getByteValue() { return byteValue; } @SuppressWarnings("unused") public void setByteValue(byte byteValue) { this.byteValue = byteValue; } public char getCharValue() { return charValue; } @SuppressWarnings("unused") public void setCharValue(char charValue) { this.charValue = charValue; } public short getShortValue() { return shortValue; } @SuppressWarnings("unused") public void setShortValue(short shortValue) { this.shortValue = shortValue; } } } ================================================ FILE: quartz/src/test/java/org/quartz/simpl/RAMJobStoreTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.util.HashMap; import java.util.Map; import org.quartz.AbstractJobStoreTest; import org.quartz.spi.JobStore; public class RAMJobStoreTest extends AbstractJobStoreTest { private HashMap stores = new HashMap<>(); @Override protected JobStore createJobStore(String name) { RAMJobStore rs = new RAMJobStore(); stores.put(name, rs); return rs; } @Override protected void destroyJobStore(String name) { } protected Map stores() { return stores; } } ================================================ FILE: quartz/src/test/java/org/quartz/simpl/SystemPropertyInstanceIdGeneratorTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.simpl; import java.sql.SQLException; import java.util.Properties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.quartz.Scheduler; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import static org.junit.jupiter.api.Assertions.assertEquals; /** * Unit test for SystemPropertyInstanceIdGenerator. */ class SystemPropertyInstanceIdGeneratorTest { @BeforeEach protected void setUp() throws Exception { System.setProperty(SystemPropertyInstanceIdGenerator.SYSTEM_PROPERTY, "foo"); System.setProperty("blah.blah", "goo"); } @Test void testGetInstanceId() throws Exception { SystemPropertyInstanceIdGenerator gen = new SystemPropertyInstanceIdGenerator(); String instId = gen.generateInstanceId(); assertEquals("foo", instId); } @Test void testGetInstanceIdWithPrepend() throws Exception { SystemPropertyInstanceIdGenerator gen = new SystemPropertyInstanceIdGenerator(); gen.setPrepend("1"); String instId = gen.generateInstanceId(); assertEquals("1foo", instId); } @Test void testGetInstanceIdWithPostpend() throws Exception { SystemPropertyInstanceIdGenerator gen = new SystemPropertyInstanceIdGenerator(); gen.setPostpend("2"); String instId = gen.generateInstanceId(); assertEquals("foo2", instId); } @Test void testGetInstanceIdWithPrependAndPostpend() throws Exception { SystemPropertyInstanceIdGenerator gen = new SystemPropertyInstanceIdGenerator(); gen.setPrepend("1"); gen.setPostpend("2"); String instId = gen.generateInstanceId(); assertEquals("1foo2", instId); } @Test void testGetInstanceIdFromCustomSystemProperty() throws Exception { SystemPropertyInstanceIdGenerator gen = new SystemPropertyInstanceIdGenerator(); gen.setSystemPropertyName("blah.blah"); String instId = gen.generateInstanceId(); assertEquals("goo", instId); } @Test void testGeneratorThroughSchedulerInstantiation() throws Exception { try { JdbcQuartzTestUtilities.createDatabase("MeSchedulerDatabase", DatabaseType.DERBY); } catch (SQLException e) { throw new AssertionError(e); } Properties config = new Properties(); config.setProperty("org.quartz.scheduler.instanceName", "MeScheduler"); config.setProperty("org.quartz.scheduler.instanceId", "AUTO"); config.setProperty("org.quartz.scheduler.instanceIdGenerator.class", org.quartz.simpl.SystemPropertyInstanceIdGenerator.class.getName()); config.setProperty("org.quartz.scheduler.instanceIdGenerator.prepend", "1"); config.setProperty("org.quartz.scheduler.instanceIdGenerator.postpend", "2"); config.setProperty("org.quartz.scheduler.instanceIdGenerator.systemPropertyName", "blah.blah"); config.setProperty("org.quartz.threadPool.threadCount", "1"); config.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); config.setProperty("org.quartz.jobStore.class", org.quartz.impl.jdbcjobstore.JobStoreTX.class.getName()); config.setProperty("org.quartz.jobStore.isClustered", "true"); config.setProperty("org.quartz.jobStore.dataSource", "MeSchedulerDatabase"); Scheduler sched = new StdSchedulerFactory(config).getScheduler(); assertEquals("1goo2", sched.getSchedulerInstanceId()); } } ================================================ FILE: quartz/src/test/java/org/quartz/utils/C3p0PoolingConnectionProviderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.quartz.integrations.tests.JdbcQuartzDerbyUtilities; import org.quartz.integrations.tests.QuartzDerbyTestSupport; import java.util.Properties; import static org.hamcrest.MatcherAssert.assertThat; /** * A integration test to ensure PoolConnectionProvider is working properly. */ public class C3p0PoolingConnectionProviderTest extends QuartzDerbyTestSupport { boolean testConnectionProviderClass = false; @Test void testC3p0PoolProviderWithExtraProps() throws Exception { validateC3p0PoolProviderClassWithExtraProps(); // Turn flag on for next test. testConnectionProviderClass = true; } @Test void testC3p0PoolProviderClassWithExtraProps() throws Exception { validateC3p0PoolProviderClassWithExtraProps(); // Turn flag off for next test. testConnectionProviderClass = false; } private void validateC3p0PoolProviderClassWithExtraProps() { DBConnectionManager dbManager = DBConnectionManager.getInstance(); ConnectionProvider provider = dbManager.getConnectionProvider("myDS"); ComboPooledDataSource ds = ((C3p0PoolingConnectionProvider)provider).getDataSource(); assertThat(ds.getDriverClass(), Matchers.is("org.apache.derby.jdbc.ClientDriver")); assertThat(ds.getJdbcUrl(), Matchers.is(JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX)); assertThat(ds.getUser(), Matchers.is("quartz")); assertThat(ds.getPassword(), Matchers.is("quartz")); assertThat(ds.getMaxPoolSize(), Matchers.is(5)); assertThat(ds.getMinPoolSize(), Matchers.is(5)); assertThat(ds.getAcquireIncrement(), Matchers.is(5)); assertThat(ds.getAcquireRetryAttempts(), Matchers.is(3)); assertThat(ds.getAcquireRetryDelay(), Matchers.is(3000)); } @Override protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName","TestScheduler"); properties.put("org.quartz.scheduler.instanceId","AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck","true"); properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount","12"); properties.put("org.quartz.threadPool.threadPriority","5"); properties.put("org.quartz.jobStore.misfireThreshold","10000"); properties.put("org.quartz.jobStore.class","org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.put("org.quartz.jobStore.driverDelegateClass","org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); properties.put("org.quartz.jobStore.useProperties","true"); properties.put("org.quartz.jobStore.dataSource","myDS"); properties.put("org.quartz.jobStore.tablePrefix","QRTZ_"); properties.put("org.quartz.jobStore.isClustered", "false"); if (testConnectionProviderClass) properties.put("org.quartz.dataSource.myDS.connectionProvider.class", "org.quartz.utils.PoolingConnectionProvider"); properties.put("org.quartz.dataSource.myDS.provider", "c3p0"); properties.put("org.quartz.dataSource.myDS.driver", "org.apache.derby.jdbc.ClientDriver"); properties.put("org.quartz.dataSource.myDS.URL",JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX); properties.put("org.quartz.dataSource.myDS.user","quartz"); properties.put("org.quartz.dataSource.myDS.password","quartz"); properties.put("org.quartz.dataSource.myDS.maxConnections","5"); // Set extra properties properties.put("org.quartz.dataSource.myDS.minPoolSize","5"); properties.put("org.quartz.dataSource.myDS.acquireIncrement","5"); properties.put("org.quartz.dataSource.myDS.acquireRetryAttempts","3"); properties.put("org.quartz.dataSource.myDS.acquireRetryDelay","3000"); properties.put("org.quartz.dataSource.myDS.maxIdleTime","60"); return properties; } } ================================================ FILE: quartz/src/test/java/org/quartz/utils/CircularLossyQueueTest.java ================================================ package org.quartz.utils; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class CircularLossyQueueTest { @Test void testNewCircularLossyQueueShouldBeEmpty() { Long[] array = new Long[5]; CircularLossyQueue queue = new CircularLossyQueue<>(5); queue.toArray(array); assertAll("emptyQueue", () -> assertTrue(queue.isEmpty()), () -> assertNull(queue.peek()), () -> assertArrayEquals(new Long[] { null, null, null, null, null }, array) ); } @Test void testPushIntoNotFullQueue() { Long[] array = new Long[5]; CircularLossyQueue queue = new CircularLossyQueue<>(5); int depthBefore = queue.depth(); queue.push(1L); queue.toArray(array); assertAll("pushIntoNotFullQueue", () -> assertFalse(queue.isEmpty()), () -> assertEquals(depthBefore + 1, queue.depth()), () -> assertEquals(1L, queue.peek()), () -> assertArrayEquals(new Long[] { 1L, null, null, null, null }, array) ); } @Test void testPushIntoFullQueue() { Long[] array = new Long[5]; CircularLossyQueue queue = new CircularLossyQueue<>(5); for (long value = 1L; value < 6L; value++) { queue.push(value); } int depthBefore = queue.depth(); queue.push(6L); queue.toArray(array); assertAll("pushIntoFullQueue", () -> assertFalse(queue.isEmpty()), () -> assertEquals(depthBefore, queue.depth()), () -> assertEquals(6L, queue.peek()), () -> assertArrayEquals(new Long[] { 6L, 5L, 4L, 3L, 2L }, array) ); } @Test void testToArrayShouldThrowWhenProvidedArrayIsBiggerThanInternalOne() { CircularLossyQueue queue = new CircularLossyQueue<>(5); Long[] array = new Long[10]; assertThrows(IllegalArgumentException.class, () -> queue.toArray(array)); } @Test void testToArray() { CircularLossyQueue queue = new CircularLossyQueue<>(5); Long[] array = new Long[5]; for (long value = 1L; value < 4L; value++) { queue.push(value); } array = queue.toArray(array); assertArrayEquals(new Long[] { 3L, 2L, 1L, null, null }, array); for (long value = 4L; value < 9L; value++) { queue.push(value); } array = queue.toArray(array); assertArrayEquals(new Long[] { 8L, 7L, 6L, 5L, 4L }, array); } } ================================================ FILE: quartz/src/test/java/org/quartz/utils/ClassUtilsTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import org.junit.jupiter.api.Test; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Alex Snaps */ public class ClassUtilsTest { @Test void testIsAnnotationPresentOnSuperClass() throws Exception { assertTrue(ClassUtils.isAnnotationPresent(BaseJob.class, DisallowConcurrentExecution.class)); assertFalse(ClassUtils.isAnnotationPresent(BaseJob.class, PersistJobDataAfterExecution.class)); assertTrue(ClassUtils.isAnnotationPresent(ExtendedJob.class, DisallowConcurrentExecution.class)); assertFalse(ClassUtils.isAnnotationPresent(ExtendedJob.class, PersistJobDataAfterExecution.class)); assertTrue(ClassUtils.isAnnotationPresent(ReallyExtendedJob.class, DisallowConcurrentExecution.class)); assertTrue(ClassUtils.isAnnotationPresent(ReallyExtendedJob.class, PersistJobDataAfterExecution.class)); } @DisallowConcurrentExecution private static class BaseJob implements Job { public void execute(final JobExecutionContext context) throws JobExecutionException { System.out.println(this.getClass().getSimpleName()); } } private static class ExtendedJob extends BaseJob { } @PersistJobDataAfterExecution private static class ReallyExtendedJob extends ExtendedJob { } } ================================================ FILE: quartz/src/test/java/org/quartz/utils/DirtyFlagMapTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import org.junit.jupiter.api.Test; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.*; /** * Unit test for DirtyFlagMap. These tests focus on making * sure the isDirty flag is set correctly. */ class DirtyFlagMapTest { @Test void testClear() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.clear(); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("X", "Y"); dirtyFlagMap.clearDirtyFlag(); dirtyFlagMap.clear(); assertTrue(dirtyFlagMap.isDirty()); } @Test void testPut() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); dirtyFlagMap.put("a", "Y"); assertTrue(dirtyFlagMap.isDirty()); } @Test void testRemove() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); dirtyFlagMap.remove("b"); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.remove("a"); assertTrue(dirtyFlagMap.isDirty()); } @Test void testEntrySetRemove() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); dirtyFlagMap.remove("a"); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); entrySet.remove("b"); assertFalse(dirtyFlagMap.isDirty()); entrySet.remove(entrySet.iterator().next()); assertTrue(dirtyFlagMap.isDirty()); } @Test void testEntrySetRetainAll() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); entrySet.retainAll(Collections.EMPTY_LIST); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); entrySet.retainAll(Collections.singletonList(entrySet.iterator().next())); assertFalse(dirtyFlagMap.isDirty()); entrySet.retainAll(Collections.EMPTY_LIST); assertTrue(dirtyFlagMap.isDirty()); } @Test void testEntrySetRemoveAll() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); entrySet.removeAll(Collections.EMPTY_LIST); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); entrySet.removeAll(Collections.EMPTY_LIST); assertFalse(dirtyFlagMap.isDirty()); entrySet.removeAll(Collections.singletonList(entrySet.iterator().next())); assertTrue(dirtyFlagMap.isDirty()); } @Test void testEntrySetClear() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); entrySet.clear(); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); entrySet.clear(); assertTrue(dirtyFlagMap.isDirty()); } @SuppressWarnings("unchecked") @Test void testEntrySetIterator() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); dirtyFlagMap.put("a", "A"); dirtyFlagMap.put("b", "B"); dirtyFlagMap.put("c", "C"); dirtyFlagMap.clearDirtyFlag(); Iterator entrySetIter = entrySet.iterator(); Map.Entry entryToBeRemoved = (Map.Entry) entrySetIter.next(); String removedKey = (String) entryToBeRemoved.getKey(); entrySetIter.remove(); assertEquals(2, dirtyFlagMap.size()); assertTrue(dirtyFlagMap.isDirty()); assertFalse(dirtyFlagMap.containsKey(removedKey)); dirtyFlagMap.clearDirtyFlag(); Map.Entry entry = (Map.Entry) entrySetIter.next(); entry.setValue("BB"); assertTrue(dirtyFlagMap.isDirty()); assertTrue(dirtyFlagMap.containsValue("BB")); } @SuppressWarnings("unchecked") @Test void testEntrySetToArray() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); dirtyFlagMap.put("a", "A"); dirtyFlagMap.put("b", "B"); dirtyFlagMap.put("c", "C"); dirtyFlagMap.clearDirtyFlag(); Object[] array = entrySet.toArray(); assertEquals(3, array.length); Map.Entry entry = (Map.Entry) array[0]; entry.setValue("BB"); assertTrue(dirtyFlagMap.isDirty()); assertTrue(dirtyFlagMap.containsValue("BB")); } @SuppressWarnings("unchecked") @Test void testEntrySetToArrayWithArg() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set> entrySet = dirtyFlagMap.entrySet(); dirtyFlagMap.put("a", "A"); dirtyFlagMap.put("b", "B"); dirtyFlagMap.put("c", "C"); dirtyFlagMap.clearDirtyFlag(); Object[] array = entrySet.toArray(new Map.Entry[]{}); assertEquals(3, array.length); Map.Entry entry = (Map.Entry) array[0]; entry.setValue("BB"); assertTrue(dirtyFlagMap.isDirty()); assertTrue(dirtyFlagMap.containsValue("BB")); } @Test void testKeySetClear() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Set keySet = dirtyFlagMap.keySet(); keySet.clear(); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); keySet.clear(); assertTrue(dirtyFlagMap.isDirty()); assertEquals(0, dirtyFlagMap.size()); } @Test void testValuesClear() { DirtyFlagMap dirtyFlagMap = new DirtyFlagMap(); Collection values = dirtyFlagMap.values(); values.clear(); assertFalse(dirtyFlagMap.isDirty()); dirtyFlagMap.put("a", "Y"); dirtyFlagMap.clearDirtyFlag(); values.clear(); assertTrue(dirtyFlagMap.isDirty()); assertEquals(0, dirtyFlagMap.size()); } } ================================================ FILE: quartz/src/test/java/org/quartz/utils/HikariCpPoolingConnectionProviderTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import com.zaxxer.hikari.HikariDataSource; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.quartz.integrations.tests.JdbcQuartzDerbyUtilities; import org.quartz.integrations.tests.QuartzDerbyTestSupport; import java.util.Properties; import static org.hamcrest.MatcherAssert.assertThat; /** * A integration test to ensure PoolConnectionProvider is working properly. */ public class HikariCpPoolingConnectionProviderTest extends QuartzDerbyTestSupport { boolean testConnectionProviderClass = false; @Test void testHikariCpPoolProviderWithExtraProps() throws Exception { validateHikariCpPoolProviderClassWithExtraProps(); // Turn flag on for next test. testConnectionProviderClass = true; } @Test void testHikariCpPoolProviderClassWithExtraProps() throws Exception { validateHikariCpPoolProviderClassWithExtraProps(); // Turn flag off for next test. testConnectionProviderClass = false; } private void validateHikariCpPoolProviderClassWithExtraProps() { DBConnectionManager dbManager = DBConnectionManager.getInstance(); ConnectionProvider provider = dbManager.getConnectionProvider("myDS"); HikariDataSource ds = ((HikariCpPoolingConnectionProvider)provider).getDataSource(); assertThat(ds.getDriverClassName(), Matchers.is("org.apache.derby.jdbc.ClientDriver")); assertThat(ds.getJdbcUrl(), Matchers.is(JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX)); assertThat(ds.getUsername(), Matchers.is("quartz")); assertThat(ds.getPassword(), Matchers.is("quartz")); assertThat(ds.getMaximumPoolSize(), Matchers.is(5)); assertThat(ds.getTransactionIsolation(), Matchers.is("TRANSACTION_REPEATABLE_READ")); assertThat(ds.isReadOnly(), Matchers.is(false)); assertThat(ds.isAutoCommit(), Matchers.is(false)); } @Override protected Properties createSchedulerProperties() { Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName","TestScheduler"); properties.put("org.quartz.scheduler.instanceId","AUTO"); properties.put("org.quartz.scheduler.skipUpdateCheck","true"); properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool"); properties.put("org.quartz.threadPool.threadCount","12"); properties.put("org.quartz.threadPool.threadPriority","5"); properties.put("org.quartz.jobStore.misfireThreshold","10000"); properties.put("org.quartz.jobStore.class","org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.put("org.quartz.jobStore.driverDelegateClass","org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); properties.put("org.quartz.jobStore.useProperties","true"); properties.put("org.quartz.jobStore.dataSource","myDS"); properties.put("org.quartz.jobStore.tablePrefix","QRTZ_"); properties.put("org.quartz.jobStore.isClustered", "false"); properties.put("org.quartz.dataSource.myDS.provider", "hikaricp"); if (testConnectionProviderClass) properties.put("org.quartz.dataSource.myDS.connectionProvider.class", "org.quartz.utils.HikariCpPoolingConnectionProvider"); properties.put("org.quartz.dataSource.myDS.provider", "hikaricp"); properties.put("org.quartz.dataSource.myDS.driver", "org.apache.derby.jdbc.ClientDriver"); properties.put("org.quartz.dataSource.myDS.URL",JdbcQuartzDerbyUtilities.DATABASE_CONNECTION_PREFIX); properties.put("org.quartz.dataSource.myDS.username","quartz"); properties.put("org.quartz.dataSource.myDS.password","quartz"); properties.put("org.quartz.dataSource.myDS.maxConnections","5"); // Set extra properties properties.put("org.quartz.dataSource.myDS.transactionIsolation","TRANSACTION_REPEATABLE_READ"); properties.put("org.quartz.dataSource.myDS.readOnly","false"); properties.put("org.quartz.dataSource.myDS.autoCommit","false"); return properties; } } ================================================ FILE: quartz/src/test/java/org/quartz/utils/PropertiesParserTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.utils; import org.junit.jupiter.api.Test; import java.util.Properties; import static org.junit.jupiter.api.Assertions.assertEquals; /** * Unit tests for PropertiesParser. */ public class PropertiesParserTest { /** * Unit test for full getPropertyGroup() method. */ @Test void testGetPropertyGroupStringBooleanStringArray() { // Test that an empty property does not cause an exception Properties props = new Properties(); props.put("x.y.z", ""); PropertiesParser propertiesParser = new PropertiesParser(props); Properties propGroup = propertiesParser.getPropertyGroup("x.y", true, new String[] {}); assertEquals("", propGroup.getProperty("z")); } } ================================================ FILE: quartz/src/test/java/org/quartz/xml/XMLSchedulingDataProcessorPluginTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.xml; import org.junit.jupiter.api.Test; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.simpl.CascadingClassLoadHelper; import org.quartz.spi.ClassLoadHelper; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class XMLSchedulingDataProcessorPluginTest implements TriggerListener { CountDownLatch latch = new CountDownLatch(1); boolean jobRan = false; @Test void testPluginSchedulesFromSimpleXMLFile() throws Exception { Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-xml-plugin-test.properties"); scheduler = factory.getScheduler(); scheduler.getListenerManager().addTriggerListener(this); scheduler.start(); latch.await(1, TimeUnit.MINUTES); assert(jobRan); } finally { if (scheduler != null) scheduler.shutdown(); } } @Override public String getName() { return "XMLSchedulingDataProcessorPluginTestListener"; } @Override public void triggerFired(Trigger trigger, JobExecutionContext context) { jobRan = true; latch.countDown(); } @Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { return false; } @Override public void triggerMisfired(Trigger trigger) { } @Override public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) { } } ================================================ FILE: quartz/src/test/java/org/quartz/xml/XMLSchedulingDataProcessorTest.java ================================================ package org.quartz.xml; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.repeatHourlyForever; import static org.quartz.TriggerBuilder.newTrigger; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.Statement; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.ObjectAlreadyExistsException; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities; import org.quartz.impl.jdbcjobstore.JdbcQuartzTestUtilities.DatabaseType; import org.quartz.impl.jdbcjobstore.JobStoreTX; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.simpl.CascadingClassLoadHelper; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.ClassLoadHelper; import org.quartz.utils.DBConnectionManager; import org.xml.sax.SAXParseException; /** * Unit test for XMLSchedulingDataProcessor. * * @author Zemian Deng * @author Tomasz Nurkiewicz (QTZ-273) */ public class XMLSchedulingDataProcessorTest { /** QTZ-185 *

The default XMLSchedulingDataProcessor will setOverWriteExistingData(true), and we want to * test programmatically overriding this value. * *

Note that XMLSchedulingDataProcessor#processFileAndScheduleJobs(Scheduler,boolean) will only * read default "quartz_data.xml" in current working directory. So to test this, we must create * this file. If this file already exist, it will be overwritten! */ void testOverwriteFlag() throws Exception { //Prepare a quartz_data.xml in current working directory by copy a test case file. File file = new File(XMLSchedulingDataProcessor.QUARTZ_XML_DEFAULT_FILE_NAME); copyResourceToFile("/org/quartz/xml/simple-job-trigger.xml", file); Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); // Let's setup a fixture job data that we know test is not going modify it. JobDetail job = newJob(MyJob.class).withIdentity("job1").usingJobData("foo", "dont_chg_me").build(); Trigger trigger = newTrigger().withIdentity("job1").withSchedule(repeatHourlyForever()).build(); scheduler.scheduleJob(job, trigger); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); try { processor.processFileAndScheduleJobs(scheduler, false); fail("OverWriteExisting flag didn't work. We should get Exception when overwrite is set to false."); } catch (ObjectAlreadyExistsException e) { // This is expected. Do nothing. } // We should still have what we start with. assertEquals(1, scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT")).size()); assertEquals(1, scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("DEFAULT")).size()); job = scheduler.getJobDetail(JobKey.jobKey("job1")); String fooValue = job.getJobDataMap().getString("foo"); assertEquals("dont_chg_me", fooValue); } finally { // remove test file if(file.exists() && !file.delete()) throw new RuntimeException("Failed to remove test file " + file); // shutdown scheduler if (scheduler != null) scheduler.shutdown(); } } private void copyResourceToFile(String resName, File file) throws IOException { // Copy streams InputStream inStream = null; FileOutputStream outStream = null; try { // Copy input resource stream to output file. inStream = getClass().getResourceAsStream(resName); outStream = new FileOutputStream(file); int BLOCK_SIZE = 1024 * 1024 * 5; // 5 MB byte[] buffer = new byte[BLOCK_SIZE]; int len = -1; while ((len = inStream.read(buffer, 0, BLOCK_SIZE)) != -1) { outStream.write(buffer, 0, len); } } finally { if (outStream != null) outStream.close(); if (inStream != null) inStream.close(); } } /** QTZ-187 */ @Test void testDirectivesNoOverwriteWithIgnoreDups() throws Exception { Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); // Setup existing job with same names as in xml data. JobDetail job = newJob(MyJob.class).withIdentity("job1").build(); Trigger trigger = newTrigger().withIdentity("job1").withSchedule(repeatHourlyForever()).build(); scheduler.scheduleJob(job, trigger); job = newJob(MyJob.class).withIdentity("job2").build(); trigger = newTrigger().withIdentity("job2").withSchedule(repeatHourlyForever()).build(); scheduler.scheduleJob(job, trigger); // Now load the xml data with directives: overwrite-existing-data=false, ignore-duplicates=true ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/directives_no-overwrite_ignoredups.xml", scheduler); assertEquals(2, scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT")).size()); assertEquals(2, scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("DEFAULT")).size()); } finally { if (scheduler != null) scheduler.shutdown(); } } @Test void testDirectivesOverwriteWithNoIgnoreDups() throws Exception { Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); // Setup existing job with same names as in xml data. JobDetail job = newJob(MyJob.class).withIdentity("job1").build(); Trigger trigger = newTrigger().withIdentity("job1").withSchedule(repeatHourlyForever()).build(); scheduler.scheduleJob(job, trigger); job = newJob(MyJob.class).withIdentity("job2").build(); trigger = newTrigger().withIdentity("job2").withSchedule(repeatHourlyForever()).build(); scheduler.scheduleJob(job, trigger); // Now load the xml data with directives: overwrite-existing-data=false, ignore-duplicates=true ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/directives_overwrite_no-ignoredups.xml", scheduler); assertEquals(2, scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT")).size()); assertEquals(2, scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("DEFAULT")).size()); } finally { if (scheduler != null) scheduler.shutdown(); } } /** QTZ-180 */ @Test void testXsdSchemaValidationOnVariousTriggers() throws Exception { Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/job-scheduling-data-2.0_trigger-samples.xml", scheduler); assertEquals(1, scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT")).size()); assertEquals(35, scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("DEFAULT")).size()); } finally { if (scheduler != null) scheduler.shutdown(); } } @Test void testQTZ327SimpleTriggerNoRepeat() throws Exception { Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/simple-job-trigger-no-repeat.xml", scheduler); assertEquals(1, scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT")).size()); assertEquals(1, scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("DEFAULT")).size()); } finally { if (scheduler != null) scheduler.shutdown(); } } private Date dateOfGMT_UTC(int hour, int minute, int second, int dayOfMonth, int month, int year) { final GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT")); calendar.set(year, month, dayOfMonth, hour, minute, second); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } private Date dateOfLocalTime(int hour, int minute, int second, int dayOfMonth, int month, int year) { final GregorianCalendar calendar = new GregorianCalendar(); calendar.set(year, month, dayOfMonth, hour, minute, second); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } /** QTZ-273 */ @Test void testTimeZones() throws Exception { Scheduler scheduler = null; try { // given StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); // when processor.processFileAndScheduleJobs("org/quartz/xml/simple-job-trigger-with-timezones.xml", scheduler); // then Trigger trigger = scheduler.getTrigger(new TriggerKey("job1", "DEFAULT")); assertNotNull(trigger); assertEquals(dateOfGMT_UTC(18, 0, 0, 1, Calendar.JANUARY, 2012), trigger.getStartTime()); assertEquals(dateOfGMT_UTC(19, 0, 0, 1, Calendar.JANUARY, 2012), trigger.getEndTime()); trigger = scheduler.getTrigger(new TriggerKey("job2", "DEFAULT")); assertNotNull(trigger); assertEquals(dateOfLocalTime(6, 0, 0, 1, Calendar.JANUARY, 2012), trigger.getStartTime()); assertEquals(dateOfGMT_UTC(19, 0, 0, 1, Calendar.JANUARY, 2012), trigger.getEndTime()); } finally { if (scheduler != null) scheduler.shutdown(); } } /** An empty job for testing purpose. */ public static class MyJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { // } } /** Test for QTZ-353, where it requires a JDBC storage */ void testRemoveJobClassNotFound() throws Exception { String DB_NAME = "XmlDeleteNonExistsJobTestDatabase"; String SCHEDULER_NAME = "XmlDeleteNonExistsJobTestScheduler"; JdbcQuartzTestUtilities.createDatabase(DB_NAME, DatabaseType.DERBY); JobStoreTX jobStore = new JobStoreTX(); jobStore.setDataSource(DB_NAME); jobStore.setTablePrefix("QRTZ_"); jobStore.setInstanceId("AUTO"); DirectSchedulerFactory.getInstance().createScheduler(SCHEDULER_NAME, "AUTO", new SimpleThreadPool(4, Thread.NORM_PRIORITY), jobStore); Scheduler scheduler = SchedulerRepository.getInstance().lookup(SCHEDULER_NAME); try { JobDetail jobDetail = JobBuilder.newJob(MyJob.class) .withIdentity("testjob1", "DEFAULT") .usingJobData("foo", "foo") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("testjob1", "DEFAULT") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); JobDetail jobDetail2 = scheduler.getJobDetail(jobDetail.getKey()); Trigger trigger2 = scheduler.getTrigger(trigger.getKey()); assertThat(jobDetail2.getJobDataMap().getString("foo"), Matchers.is("foo")); assertThat(trigger2, Matchers.instanceOf(CronTrigger.class)); modifyStoredJobClassName(); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/delete-no-jobclass.xml", scheduler); jobDetail2 = scheduler.getJobDetail(jobDetail.getKey()); trigger2 = scheduler.getTrigger(trigger.getKey()); assertThat(trigger2, Matchers.nullValue()); assertThat(jobDetail2, Matchers.nullValue()); jobDetail2 = scheduler.getJobDetail(new JobKey("job1", "DEFAULT")); trigger2 = scheduler.getTrigger(new TriggerKey("job1", "DEFAULT")); assertThat(jobDetail2.getJobDataMap().getString("foo"), Matchers.is("bar")); assertThat(trigger2, Matchers.instanceOf(SimpleTrigger.class)); } finally { scheduler.shutdown(false); JdbcQuartzTestUtilities.destroyDatabase(DB_NAME, DatabaseType.DERBY); } } @Test void testOverwriteJobClassNotFound() throws Exception { String DB_NAME = "XmlDeleteNonExistsJobTestDatabase"; String SCHEDULER_NAME = "XmlDeleteNonExistsJobTestScheduler"; JdbcQuartzTestUtilities.createDatabase(DB_NAME, DatabaseType.DERBY); JobStoreTX jobStore = new JobStoreTX(); jobStore.setDataSource(DB_NAME); jobStore.setTablePrefix("QRTZ_"); jobStore.setInstanceId("AUTO"); DirectSchedulerFactory.getInstance().createScheduler(SCHEDULER_NAME, "AUTO", new SimpleThreadPool(4, Thread.NORM_PRIORITY), jobStore); Scheduler scheduler = SchedulerRepository.getInstance().lookup(SCHEDULER_NAME); try { JobDetail jobDetail = JobBuilder.newJob(MyJob.class) .withIdentity("job1", "DEFAULT") .usingJobData("foo", "foo") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("job1", "DEFAULT") .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")) .build(); scheduler.scheduleJob(jobDetail, trigger); JobDetail jobDetail2 = scheduler.getJobDetail(jobDetail.getKey()); Trigger trigger2 = scheduler.getTrigger(trigger.getKey()); assertThat(jobDetail2.getJobDataMap().getString("foo"), Matchers.is("foo")); assertThat(trigger2, Matchers.instanceOf(CronTrigger.class)); modifyStoredJobClassName(); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/overwrite-no-jobclass.xml", scheduler); jobDetail2 = scheduler.getJobDetail(jobDetail.getKey()); trigger2 = scheduler.getTrigger(trigger.getKey()); assertThat(jobDetail2.getJobDataMap().getString("foo"), Matchers.is("bar")); assertThat(trigger2, Matchers.instanceOf(SimpleTrigger.class)); } finally { scheduler.shutdown(false); JdbcQuartzTestUtilities.destroyDatabase(DB_NAME, DatabaseType.DERBY); } } @Test void testXmlParserConfiguration() throws Exception { Scheduler scheduler = null; try { StdSchedulerFactory factory = new StdSchedulerFactory("org/quartz/xml/quartz-test.properties"); scheduler = factory.getScheduler(); ClassLoadHelper clhelper = new CascadingClassLoadHelper(); clhelper.initialize(); XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(clhelper); processor.processFileAndScheduleJobs("org/quartz/xml/bad-job-config.xml", scheduler); final JobKey jobKey = scheduler.getJobKeys(GroupMatcher.jobGroupEquals("native")).iterator().next(); final JobDetail jobDetail = scheduler.getJobDetail(jobKey); final String description = jobDetail.getDescription(); fail("Expected parser configuration to block DOCTYPE. The following was injected into the job description field: " + description); } catch (SAXParseException e) { assertTrue(e.getMessage().toLowerCase().contains("doctype")); } finally { if (scheduler != null) scheduler.shutdown(); } } @Test private void modifyStoredJobClassName() throws Exception { String DB_NAME = "XmlDeleteNonExistsJobTestDatabase"; Connection conn = DBConnectionManager.getInstance().getConnection(DB_NAME); Statement statement = conn.createStatement(); statement.executeUpdate("update qrtz_job_details set job_class_name='com.FakeNonExistsJob'"); statement.close(); conn.close(); } } ================================================ FILE: quartz/src/test/resources/container-license-acceptance.txt ================================================ mcr.microsoft.com/mssql/server:latest ================================================ FILE: quartz/src/test/resources/log4j.properties ================================================ # Configure logging for testing log4j.rootLogger=INFO, stdout #log4j.logger.org.quartz=DEBUG log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n ================================================ FILE: quartz/src/test/resources/org/quartz/properties/quartzCustomConnectionProvider.properties ================================================ # Main Quartz configuration org.quartz.scheduler.instanceName = DatabaseScheduler org.quartz.scheduler.instanceId = NON_CLUSTERED org.quartz.scheduler.jobFactory.class = org.quartz.simpl.SimpleJobFactory org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource = quartzDataSource org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 # JobStore: JDBC jobStoreTX org.quartz.dataSource.quartzDataSource.connectionProvider.class = org.quartz.impl.MockConnectionProvider org.quartz.dataSource.quartzDataSource.customProperty = customValue ================================================ FILE: quartz/src/test/resources/org/quartz/xml/bad-job-config.xml ================================================ ]> xxe native &xxe; org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob true false ================================================ FILE: quartz/src/test/resources/org/quartz/xml/delete-no-jobclass.xml ================================================ testjob1 DEFAULT job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob foobar job1 DEFAULT job1 DEFAULT -1 1500 ================================================ FILE: quartz/src/test/resources/org/quartz/xml/directives_no-overwrite_ignoredups.xml ================================================ false true job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob job1 DEFAULT job1 DEFAULT -1 1500 job2 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob color GREEN job2 DEFAULT job2 DEFAULT * * * * * ? ================================================ FILE: quartz/src/test/resources/org/quartz/xml/directives_overwrite_no-ignoredups.xml ================================================ true false job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob job1 DEFAULT job1 DEFAULT -1 1500 job2 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob color GREEN job2 DEFAULT job2 DEFAULT * * * * * ? ================================================ FILE: quartz/src/test/resources/org/quartz/xml/job-scheduling-data-2.0_trigger-samples.xml ================================================ job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob true false every_sec_no_day_of_week job1 * * * * * ? every_sec_no_day_of_month job1 * * * ? * * every_min job1 0 * * * * ? every_hour job1 0 0 * * * ? every_day job1 0 0 0 * * ? every_first_of_month job1 0 0 0 1 * ? every_first_of_JAN job1 0 0 0 1 1 ? every_day_8am_to_5pm job1 0 0 8-17 * * ? every_weekdays_8am_to_5pm job1 0 0 8-17 ? * MON-FRI every_weekdays_8am_to_5pm_JUN_ot_SEP job1 0 0 8-17 ? JUN-SEP MON-FRI every_weekdays_8am_to_5pm_JUN_ot_SEP_within_years job1 0 0 8-17 ? JUN-SEP MON-FRI 1970-2099 last_day_of_work_OR_SAT job1 0 15 10 ? * L last_FRI_of_month job1 0 15 10 ? * 6L last_SAT_of_month job1 0 15 10 ? * SATL last_day_of_month job1 0 15 10 L * ? third_to_last_day_of_month job1 0 15 10 L-3 * ? 31th_to_last_day_of_month_or_new_offset_feature_in_2_0 job1 0 15 10 L-30 * ? every_weekdays_near_first_of_month_at_noon_time job1 0 0 12 1W * ? every_weekdays_near_last_day_of_month_at_noon_time job1 0 0 12 LW * ? unspecified_year job1 0 * * * * ? specific_year job1 0 * * * * ? 2099 year_range job1 0 * * * * ? 2012-2099 any_year job1 0 * * * * ? * every_4th_MON_of_month_at_12AM job1 0 0 0 ? * 2#4 every_4th_TUE_of_month_at_12AM job1 0 0 0 ? * TUE#4 every_4th_TUE_of_month_at_12AM_in_2050 job1 0 0 0 ? * WED#4 2050 every_MON_WED_FRI_of_month_at_12AM job1 0 0 0 ? * MON,WED,FRI every_1st_and_15th_of_month_at_12AM job1 0 0 0 1,15 * ? 1st_and_15th_of_JAN_JUN_DEC_at_12AM job1 0 0 0 1,15 JAN,JUN,DEC ? 1st_and_15th_of_JAN_JUN_DEC_at_12AM_year_50_n_51 job1 0 0 0 1,15 JAN,JUN,DEC ? 2050,2051 every_15_sec_inc job1 0/15 * * * * ? every_15_sec_inc_with_star job1 */15 * * * * ? every_inc_2_fields job1 */15 5/50 * * * ? every_inc_of_something job1 */15 5/50 5/15 1/3 7/6 ? QTZ-329 job1 0 0/30 0-6,12,19-21,22 * * ? ================================================ FILE: quartz/src/test/resources/org/quartz/xml/overwrite-no-jobclass.xml ================================================ true false job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob foobar job1 DEFAULT job1 DEFAULT -1 1500 ================================================ FILE: quartz/src/test/resources/org/quartz/xml/quartz-test.properties ================================================ org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore ================================================ FILE: quartz/src/test/resources/org/quartz/xml/quartz-xml-plugin-test.properties ================================================ org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin org.quartz.plugin.jobInitializer.fileNames = org/quartz/xml/simple-job-trigger-no-repeat.xml org.quartz.plugin.jobInitializer.failOnFileNotFound = true #org.quartz.plugin.jobInitializer.scanInterval = 5 ================================================ FILE: quartz/src/test/resources/org/quartz/xml/simple-job-trigger-no-repeat.xml ================================================ job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob true false job1_trigger DEFAULT job1 DEFAULT ================================================ FILE: quartz/src/test/resources/org/quartz/xml/simple-job-trigger-with-timezones.xml ================================================ job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob true false foobar job1 DEFAULT job1 DEFAULT 2012-01-01T18:00:00Z 2012-01-01T22:00:00+03:00 -1 1500 job2 DEFAULT job1 DEFAULT 2012-01-01T06:00:00 2012-01-01T16:00:00-03:00 -1 1500 ================================================ FILE: quartz/src/test/resources/org/quartz/xml/simple-job-trigger.xml ================================================ job1 DEFAULT org.quartz.xml.XMLSchedulingDataProcessorTest$MyJob true false foobar job1 DEFAULT job1 DEFAULT -1 1500 ================================================ FILE: quartz/src/test/resources/tables_derby_drop.sql ================================================ -- -- Apache Derby scripts by Steve Stewart, updated by Ronald Pomeroy -- Based on Srinivas Venkatarangaiah's file for Cloudscape -- -- Known to work with Apache Derby 10.0.2.1, or 10.6.2.1 -- -- Updated by Zemian Deng on 08/21/2011 -- * Fixed nullable fields on qrtz_simprop_triggers table. -- * Added Derby QuickStart comments and drop tables statements. -- -- DerbyDB + Quartz Quick Guide: -- * Derby comes with Oracle JDK! For Java6, it default install into C:/Program Files/Sun/JavaDB on Windows. -- 1. Create a derby.properties file under JavaDB directory, and have the following: -- derby.connection.requireAuthentication = true -- derby.authentication.provider = BUILTIN -- derby.user.quartz2=quartz2123 -- 2. Start the DB server by running bin/startNetworkServer script. -- 3. On a new terminal, run bin/ij tool to bring up an SQL prompt, then run: -- connect 'jdbc:derby://localhost:1527/quartz2;user=quartz2;password=quartz2123;create=true'; -- run 'quartz/docs/dbTables/tables_derby.sql'; -- Now in quartz.properties, you may use these properties: -- org.quartz.dataSource.quartzDataSource.driver = org.apache.derby.jdbc.ClientDriver -- org.quartz.dataSource.quartzDataSource.URL = jdbc:derby://localhost:1527/quartz2 -- org.quartz.dataSource.quartzDataSource.user = quartz2 -- org.quartz.dataSource.quartzDataSource.password = quartz2123 -- -- Auto drop and reset tables -- Derby doesn't support if exists condition on table drop, so user must manually do this step if needed to. drop table qrtz_fired_triggers; drop table qrtz_paused_trigger_grps; drop table qrtz_scheduler_state; drop table qrtz_locks; drop table qrtz_simple_triggers; drop table qrtz_simprop_triggers; drop table qrtz_cron_triggers; drop table qrtz_blob_triggers; drop table qrtz_triggers; drop table qrtz_job_details; drop table qrtz_calendars; ================================================ FILE: quartz-jobs/build.gradle ================================================ plugins { id 'java-library' id 'maven-publish' id 'biz.aQute.bnd.builder' } dependencies { implementation project(':quartz') implementation "org.slf4j:slf4j-api:$slf4jVersion" runtimeOnly "org.slf4j:slf4j-log4j12:$slf4jVersion" compileOnly 'jakarta.jms:jakarta.jms-api:3.1.0' compileOnly 'jakarta.mail:jakarta.mail-api:2.1.5' compileOnly 'jakarta.ejb:jakarta.ejb-api:4.0.1' compileOnly 'org.glassfish.corba:rmic:5.0.0' testImplementation 'jakarta.transaction:jakarta.transaction-api:2.0.1' testImplementation 'commons-io:commons-io:2.21.0' testImplementation "junit:junit:$junitVersion" testImplementation 'org.hamcrest:hamcrest-library:3.0' testImplementation 'jakarta.mail:jakarta.mail-api:2.1.5' testImplementation 'org.subethamail:subethasmtp:3.1.7' } java { withJavadocJar() withSourcesJar() } test { maxParallelForks 1 forkEvery 1 } publishing { publications { getByName('maven').pom.description = 'Quartz Enterprise Job Scheduler - additional library of ready to use Jobs' } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/DirectoryScanJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; import java.io.File; import java.io.FileFilter; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Inspects a directory and compares whether any files' "last modified dates" * have changed since the last time it was inspected. If one or more files * have been updated (or created), the job invokes a "call-back" method on an * identified DirectoryScanListener that can be found in the * SchedulerContext. * * @author pl47ypus * @author jhouse * @see org.quartz.jobs.DirectoryScanListener * @see org.quartz.SchedulerContext */ @DisallowConcurrentExecution @PersistJobDataAfterExecution public class DirectoryScanJob implements Job { /** * JobDataMap key with which to specify the directory to be * monitored - an absolute path is recommended. */ public static final String DIRECTORY_NAME = "DIRECTORY_NAME"; /** * JobDataMap key with which to specify the * {@link org.quartz.jobs.DirectoryScanListener} to be * notified when the directory contents change. */ public static final String DIRECTORY_SCAN_LISTENER_NAME = "DIRECTORY_SCAN_LISTENER_NAME"; /** * JobDataMap key with which to specify a long * value that represents the minimum number of milliseconds that must have * past since the file's last modified time in order to consider the file * new/altered. This is necessary because another process may still be * in the middle of writing to the file when the scan occurs, and the * file may therefore not yet be ready for processing. * *

If this parameter is not specified, a default value of * 5000 (five seconds) will be used.

*/ public static final String MINIMUM_UPDATE_AGE = "MINIMUM_UPDATE_AGE"; private static final String LAST_MODIFIED_TIME = "LAST_MODIFIED_TIME"; private final Logger log = LoggerFactory.getLogger(getClass()); public DirectoryScanJob() { } /** * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) */ public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); SchedulerContext schedCtx = null; try { schedCtx = context.getScheduler().getContext(); } catch (SchedulerException e) { throw new JobExecutionException("Error obtaining scheduler context.", e, false); } String dirName = mergedJobDataMap.getString(DIRECTORY_NAME); String listenerName = mergedJobDataMap.getString(DIRECTORY_SCAN_LISTENER_NAME); if(dirName == null) { throw new JobExecutionException("Required parameter '" + DIRECTORY_NAME + "' not found in merged JobDataMap"); } if(listenerName == null) { throw new JobExecutionException("Required parameter '" + DIRECTORY_SCAN_LISTENER_NAME + "' not found in merged JobDataMap"); } DirectoryScanListener listener = (DirectoryScanListener)schedCtx.get(listenerName); if(listener == null) { throw new JobExecutionException("DirectoryScanListener named '" + listenerName + "' not found in SchedulerContext"); } long lastDate = -1; if(mergedJobDataMap.containsKey(LAST_MODIFIED_TIME)) { lastDate = mergedJobDataMap.getLong(LAST_MODIFIED_TIME); } long minAge = 5000; if(mergedJobDataMap.containsKey(MINIMUM_UPDATE_AGE)) { minAge = mergedJobDataMap.getLong(MINIMUM_UPDATE_AGE); } long maxAgeDate = System.currentTimeMillis() - minAge; File[] updatedFiles = getUpdatedOrNewFiles(dirName, lastDate, maxAgeDate); if(updatedFiles == null) { log.warn("Directory '"+dirName+"' does not exist."); return; } long latestMod = lastDate; for(File updFile: updatedFiles) { long lm = updFile.lastModified(); latestMod = (lm > latestMod) ? lm : latestMod; } if(updatedFiles.length > 0) { // notify call back... log.info("Directory '"+dirName+"' contents updated, notifying listener."); listener.filesUpdatedOrAdded(updatedFiles); } else if (log.isDebugEnabled()) { log.debug("Directory '"+dirName+"' contents unchanged."); } // It is the JobDataMap on the JobDetail which is actually stateful context.getJobDetail().getJobDataMap().put(LAST_MODIFIED_TIME, latestMod); } protected File[] getUpdatedOrNewFiles(String dirName, final long lastDate, final long maxAgeDate) { File dir = new File(dirName); if(!dir.exists() || !dir.isDirectory()) { return null; } File[] files = dir.listFiles(new FileFilter() { public boolean accept(File pathname) { if(pathname.lastModified() > lastDate && pathname.lastModified() < maxAgeDate) return true; return false; }}); if(files == null) files = new File[0]; return files; } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/DirectoryScanListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; import java.io.File; /** * Interface for objects wishing to receive a 'call-back' from a * DirectoryScanJob. * *

Instances should be stored in the {@link org.quartz.SchedulerContext} * such that the DirectoryScanJob can find it.

* * @author jhouse * @see org.quartz.jobs.DirectoryScanJob * @see org.quartz.SchedulerContext */ public interface DirectoryScanListener { /** * @param updatedFiles The set of files that were updated/added since the * last scan of the directory */ void filesUpdatedOrAdded(File[] updatedFiles); } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/FileScanJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; import java.io.File; import java.net.URL; import java.net.URLDecoder; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 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 org.quartz.jobs.FileScanListener */ @DisallowConcurrentExecution @PersistJobDataAfterExecution public class FileScanJob implements Job { /** * JobDataMap key with which to specify * the name of the file to monitor. */ public static final String FILE_NAME = "FILE_NAME"; /** * JobDataMap key with which to specify the * {@link org.quartz.jobs.FileScanListener} to be * notified when the file contents change. */ public static final String FILE_SCAN_LISTENER_NAME = "FILE_SCAN_LISTENER_NAME"; /** * JobDataMap key with which to specify a long * value that represents the minimum number of milliseconds that must have * past since the file's last modified time in order to consider the file * new/altered. This is necessary because another process may still be * in the middle of writing to the file when the scan occurs, and the * file may therefore not yet be ready for processing. * *

If this parameter is not specified, a default value of * 5000 (five seconds) will be used.

*/ public static final String MINIMUM_UPDATE_AGE = "MINIMUM_UPDATE_AGE"; private static final String LAST_MODIFIED_TIME = "LAST_MODIFIED_TIME"; private final Logger log = LoggerFactory.getLogger(getClass()); public FileScanJob() { } /** * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) */ public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); SchedulerContext schedCtx = null; try { schedCtx = 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)schedCtx.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 minAge = 5000; if(mergedJobDataMap.containsKey(MINIMUM_UPDATE_AGE)) { minAge = mergedJobDataMap.getLong(MINIMUM_UPDATE_AGE); } long maxAgeDate = System.currentTimeMillis() + minAge; long newDate = getLastModifiedDate(fileName); if(newDate < 0) { log.warn("File '"+fileName+"' does not exist."); return; } if(lastDate > 0 && (newDate > lastDate && newDate < maxAgeDate)) { // 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(); } } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/FileScanListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; /** * Interface for objects wishing to receive a 'call-back' from a * FileScanJob. * * @author jhouse * @see org.quartz.jobs.FileScanJob */ public interface FileScanListener { void fileUpdated(String fileName); } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/NoOpJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* An implementation of Job, that does absolutely nothing - useful for system * which only wish to use {@link org.quartz.TriggerListener}s * and {@link 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 { } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/ejb/EJB3InvokerJob.java ================================================ package org.quartz.jobs.ee.ejb; import java.lang.reflect.InvocationTargetException; import javax.naming.InitialContext; import javax.naming.NamingException; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A Job that invokes a method on an EJB3. *

* * Expects the properties corresponding to the following keys to be in the * JobDataMap when it executes: *
    *
  • EJB_JNDI_NAME_KEY- the JNDI name (location) of the EJB's * home interface.
  • *
  • EJB_METHOD_KEY- the name of the method to invoke on the EJB. *
  • *
  • EJB_ARGS_KEY- an Object[] of the args to pass to the method * (optional, if left out, there are no arguments).
  • *
  • EJB_ARG_TYPES_KEY- an Class[] of the types of the args to * pass to the method (optional, if left out, the types will be derived by * calling getClass() on each of the arguments).
  • *
*
* The following keys can also be used at need: *
    *
  • INITIAL_CONTEXT_FACTORY - the context factory used to build * the context.
  • *
  • PROVIDER_URL - the name of the environment property for * specifying configuration information for the service provider to use.
  • *
* *

* The result of the EJB method invocation will be available to * Job/TriggerListeners via * {@link org.quartz.JobExecutionContext#getResult()}. *

* * @author hhuynh * @see org.quartz.jobs.ee.ejb.EJBInvokerJob */ public class EJB3InvokerJob extends EJBInvokerJob { @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getMergedJobDataMap(); String ejb = dataMap.getString(EJB_JNDI_NAME_KEY); String method = dataMap.getString(EJB_METHOD_KEY); Object[] arguments = (Object[]) dataMap.get(EJB_ARGS_KEY); if (arguments == null) { arguments = new Object[0]; } if (ejb == null) { throw new JobExecutionException("must specify EJB_JNDI_NAME_KEY"); } if (method == null) { throw new JobExecutionException("must specify EJB_METHOD_KEY"); } InitialContext jndiContext = null; Object value = null; try { try { jndiContext = getInitialContext(dataMap); value = jndiContext.lookup(ejb); } catch (NamingException ne) { throw new JobExecutionException(ne); } Class[] argTypes = (Class[]) dataMap.get(EJB_ARG_TYPES_KEY); if (argTypes == null) { argTypes = new Class[arguments.length]; for (int i = 0; i < arguments.length; i++) { argTypes[i] = arguments[i].getClass(); } } try { Object returnValue = value.getClass() .getMethod(method, argTypes).invoke(value, arguments); context.setResult(returnValue); } catch (IllegalAccessException iae) { throw new JobExecutionException(iae); } catch (InvocationTargetException ite) { throw new JobExecutionException(ite.getTargetException()); } catch (NoSuchMethodException nsme) { throw new JobExecutionException(nsme); } } finally { if (jndiContext != null) { try { jndiContext.close(); } catch (Exception e) { // ignored } } } } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/ejb/EJBInvokerJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.ejb; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.rmi.RemoteException; import java.util.Hashtable; import jakarta.ejb.EJBHome; import jakarta.ejb.EJBMetaData; import jakarta.ejb.EJBObject; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A Job that invokes a method on an EJB. *

* * Expects the properties corresponding to the following keys to be in the * JobDataMap when it executes: *
    *
  • EJB_JNDI_NAME_KEY- the JNDI name (location) of the * EJB's home interface.
  • *
  • EJB_METHOD_KEY- the name of the method to invoke on the * EJB.
  • *
  • EJB_ARGS_KEY- an Object[] of the args to pass to the * method (optional, if left out, there are no arguments).
  • *
  • EJB_ARG_TYPES_KEY- an Class[] of the types of the args to * pass to the method (optional, if left out, the types will be derived by * calling getClass() on each of the arguments).
  • *
*
* The following keys can also be used at need: *
    *
  • INITIAL_CONTEXT_FACTORY - the context factory used to * build the context.
  • *
  • PROVIDER_URL - the name of the environment property * for specifying configuration information for the service provider to use. *
  • *
* *

* The result of the EJB method invocation will be available to * Job/TriggerListeners via * {@link org.quartz.JobExecutionContext#getResult()}. *

* * @author Andrew Collins * @author James House * @author Joel Shellman * @author Chris Bonham */ public class EJBInvokerJob implements Job { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public static final String EJB_JNDI_NAME_KEY = "ejb"; public static final String EJB_METHOD_KEY = "method"; public static final String EJB_ARG_TYPES_KEY = "argTypes"; public static final String EJB_ARGS_KEY = "args"; public static final String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"; public static final String PROVIDER_URL = "java.naming.provider.url"; public static final String PRINCIPAL = "java.naming.security.principal"; public static final String CREDENTIALS = "java.naming.security.credentials"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public EJBInvokerJob() { // nothing } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getMergedJobDataMap(); String ejb = dataMap.getString(EJB_JNDI_NAME_KEY); String method = dataMap.getString(EJB_METHOD_KEY); Object[] arguments = (Object[]) dataMap.get(EJB_ARGS_KEY); if (arguments == null) { arguments = new Object[0]; } if (ejb == null) { // must specify remote home throw new JobExecutionException(); } InitialContext jndiContext = null; // get initial context try { jndiContext = getInitialContext(dataMap); } catch (NamingException ne) { throw new JobExecutionException(ne); } try { Object value = null; // locate home interface try { value = jndiContext.lookup(ejb); } catch (NamingException ne) { throw new JobExecutionException(ne); } // get home interface EJBHome ejbHome = (EJBHome) PortableRemoteObject.narrow(value, EJBHome.class); // get meta data EJBMetaData metaData = null; try { metaData = ejbHome.getEJBMetaData(); } catch (RemoteException re) { throw new JobExecutionException(re); } // get home interface class Class homeClass = metaData.getHomeInterfaceClass(); // get remote interface class Class remoteClass = metaData.getRemoteInterfaceClass(); // get home interface ejbHome = (EJBHome) PortableRemoteObject.narrow(ejbHome, homeClass); Method methodCreate = null; try { // create method 'create()' on home interface methodCreate = homeClass.getMethod("create", ((Class[])null)); } catch (NoSuchMethodException nsme) { throw new JobExecutionException(nsme); } // create remote object EJBObject remoteObj = null; try { // invoke 'create()' method on home interface remoteObj = (EJBObject) methodCreate.invoke(ejbHome, ((Object[])null)); } catch (IllegalAccessException iae) { throw new JobExecutionException(iae); } catch (InvocationTargetException ite) { throw new JobExecutionException(ite); } // execute user-specified method on remote object Method methodExecute = null; try { // create method signature Class[] argTypes = (Class[]) dataMap.get(EJB_ARG_TYPES_KEY); if (argTypes == null) { argTypes = new Class[arguments.length]; for (int i = 0; i < arguments.length; i++) { argTypes[i] = arguments[i].getClass(); } } // get method on remote object methodExecute = remoteClass.getMethod(method, argTypes); } catch (NoSuchMethodException nsme) { throw new JobExecutionException(nsme); } try { // invoke user-specified method on remote object Object returnObj = methodExecute.invoke(remoteObj, arguments); // Return any result in the JobExecutionContext so it will be // available to Job/TriggerListeners context.setResult(returnObj); } catch (IllegalAccessException iae) { throw new JobExecutionException(iae); } catch (InvocationTargetException ite) { throw new JobExecutionException(ite); } } finally { // Don't close jndiContext until after method execution because // WebLogic requires context to be open to keep the user credentials // available. See Jira Issue: QUARTZ-401 if (jndiContext != null) { try { jndiContext.close(); } catch (NamingException e) { // Ignore any errors closing the initial context } } } } protected InitialContext getInitialContext(JobDataMap jobDataMap) throws NamingException { Hashtable params = new Hashtable(2); String initialContextFactory = jobDataMap.getString(INITIAL_CONTEXT_FACTORY); if (initialContextFactory != null) { params.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); } String providerUrl = jobDataMap.getString(PROVIDER_URL); if (providerUrl != null) { params.put(Context.PROVIDER_URL, providerUrl); } String principal = jobDataMap.getString(PRINCIPAL); if ( principal != null ) { params.put( Context.SECURITY_PRINCIPAL, principal ); } String credentials = jobDataMap.getString(CREDENTIALS); if ( credentials != null ) { params.put( Context.SECURITY_CREDENTIALS, credentials ); } return (params.size() == 0) ? new InitialContext() : new InitialContext(params); } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jms/JmsHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.jms; import java.lang.reflect.Method; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import org.quartz.JobDataMap; /** * Utility class that aids in the processing of JMS based jobs and sending of * jakarta.jms.Message * * @author Fernando Ribeiro * @author Weston M. Price */ public final class JmsHelper { public static final String CREDENTIALS = "java.naming.security.credentials"; public static final String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"; public static final String JMS_ACK_MODE = "jms.acknowledge"; public static final String JMS_CONNECTION_FACTORY_JNDI = "jms.connection.factory"; public static final String JMS_DESTINATION_JNDI = "jms.destination"; public static final String JMS_MSG_FACTORY_CLASS_NAME = "jms.message.factory.class.name"; public static final String JMS_PASSWORD = "jms.password"; public static final String JMS_USE_TXN = "jms.use.transaction"; public static final String JMS_USER = "jms.user"; public static final String PRINCIPAL = "java.naming.security.principal"; public static final String PROVIDER_URL = "java.naming.provider.url"; public static void closeResource(final Object resource) { if (resource == null) return; try { final Method m = resource.getClass().getMethod("close", new Class[0]); m.invoke(resource, new Object[0]); } catch (final Exception e) { } } public static InitialContext getInitialContext(final JobDataMap dataMap) throws NamingException { final Hashtable params = new Hashtable(4); final String initialContextFactory = dataMap .getString(INITIAL_CONTEXT_FACTORY); if (initialContextFactory != null) params.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); final String providerUrl = dataMap.getString(PROVIDER_URL); if (providerUrl != null) params.put(Context.PROVIDER_URL, providerUrl); final String principal = dataMap.getString(PRINCIPAL); if (principal != null) params.put(Context.SECURITY_PRINCIPAL, principal); final String credentials = dataMap.getString(CREDENTIALS); if (credentials != null) params.put(Context.SECURITY_CREDENTIALS, credentials); if (params.size() == 0) return new InitialContext(); else return new InitialContext(params); } public static JmsMessageFactory getMessageFactory(final String name) throws JmsJobException { try { final Class cls = Class.forName(name); final JmsMessageFactory factory = (JmsMessageFactory) cls .newInstance(); return factory; } catch (final Exception e) { throw new JmsJobException(e.getMessage(), e); } } public static boolean isDestinationSecure(final JobDataMap dataMap) { return ((dataMap.getString(JmsHelper.JMS_USER) != null) && (dataMap .getString(JmsHelper.JMS_PASSWORD) != null)); } public static boolean useTransaction(final JobDataMap dataMap) { return dataMap.getBoolean(JMS_USE_TXN); } private JmsHelper() { } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jms/JmsJobException.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.jms; import org.quartz.SchedulerException; /** * The JmsJobException is used to indicate an error during sending of a * jakarta.jms.Message. * * @author Fernando Ribeiro * @author Weston M. Price */ public final class JmsJobException extends SchedulerException { private static final long serialVersionUID = 3045647075496522093L; public JmsJobException(final String message) { super(message); } public JmsJobException(final String message, final Throwable cause) { super(message, cause); } public JmsJobException(final Throwable cause) { super(cause); } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jms/JmsMessageFactory.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.jms; import jakarta.jms.Message; import jakarta.jms.Session; import org.quartz.JobDataMap; /** * The JmsMessageFactory interface allows for the creation of a * jakarta.jms.Message. This interface is used in constructing a * jakarta.jms.Message that is to be sent upon execution of a JMS * enabled job. * * @see SendDestinationMessageJob * @see SendQueueMessageJob * @see SendTopicMessageJob * * @author Weston M. Price */ public interface JmsMessageFactory { /** * Creates a jakarta.jms.Message. * * @param jobDataMap * the JobDataMap * @param session * the jakarta.jms.Session * * @return the jakarta.jms.Message */ Message createMessage(JobDataMap jobDataMap, Session session); } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jms/SendDestinationMessageJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.jms; import jakarta.jms.Connection; import jakarta.jms.ConnectionFactory; import jakarta.jms.Destination; import jakarta.jms.Message; import jakarta.jms.MessageProducer; import jakarta.jms.Session; import javax.naming.Context; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A Job that sends a jakarta.jms.Message to a * jakarta.jms.Destination. Note, this class can only be used in a * JMS-based environment. * *

* The following properties are expected to be provided in the * JobDataMap: * *

    *
  • JMS_CONNECTION_FACTORY_JNDI - The JNDI name of the JMS * Connection Factory.
  • *
  • JMS_DESTINATION_JNDI - The JNDI name of the JMS * destination.
  • *
  • JMS_USE_TXN - Whether or not to use a transacted * jakarta.jms.Session.
  • *
  • JMS_ACK_MODE - The acknowledgement mode for the * jakarta.jms.Session.
  • *
  • JMS_MSG_FACTORY_CLASS_NAME - The implementation class * name for the JmsMessageFactory.
  • *
* *

* The following properties are optional * *

    *
  • JMS_USER - The JMS user for secure destinations. *
  • JMS_PASSWORD - The JMS password for secure destinations. *
* *

* The following properties can be used for JNDI support: * *

    *
  • INITIAL_CONTEXT_FACTORY - The java.naming.factory.initial * setting for JNDI. *
  • PROVIDER_URL - The java.naming.provider.url for JNDI. *
* * @see JmsMessageFactory * * @author Fernando Ribeiro * @author Weston M. Price * @author Frank Van Uffelen */ public final class SendDestinationMessageJob implements Job { public void execute(final JobExecutionContext jobCtx) throws JobExecutionException { Connection conn = null; Session sess = null; MessageProducer producer = null; try { final JobDataMap dataMap = jobCtx.getMergedJobDataMap(); final Context namingCtx = JmsHelper.getInitialContext(dataMap); final ConnectionFactory connFactory = (ConnectionFactory) namingCtx .lookup(dataMap .getString(JmsHelper.JMS_CONNECTION_FACTORY_JNDI)); if (!JmsHelper.isDestinationSecure(dataMap)) { conn = connFactory.createConnection(); } else { final String user = dataMap.getString(JmsHelper.JMS_USER); final String password = dataMap .getString(JmsHelper.JMS_PASSWORD); conn = connFactory.createConnection(user, password); } final boolean useTransaction = JmsHelper.useTransaction(dataMap); final int ackMode = dataMap.getInt(JmsHelper.JMS_ACK_MODE); sess = conn.createSession(useTransaction, ackMode); final Destination destination = (Destination) namingCtx .lookup(dataMap.getString(JmsHelper.JMS_DESTINATION_JNDI)); producer = sess.createProducer(destination); final JmsMessageFactory messageFactory = JmsHelper .getMessageFactory(dataMap .getString(JmsHelper.JMS_MSG_FACTORY_CLASS_NAME)); final Message msg = messageFactory.createMessage(dataMap, sess); producer.send(msg); } catch (final Exception e) { throw new JobExecutionException(e); } finally { JmsHelper.closeResource(producer); JmsHelper.closeResource(sess); JmsHelper.closeResource(conn); } } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jms/SendQueueMessageJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.jms; import jakarta.jms.Message; import jakarta.jms.Queue; import jakarta.jms.QueueConnection; import jakarta.jms.QueueConnectionFactory; import jakarta.jms.QueueSender; import jakarta.jms.QueueSession; import javax.naming.Context; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A Job that sends a jakarta.jms.Message to a * jakarta.jms.Queue. This class is for older JMS. If you are using * JMS 1.1, you should use {@link SendDestinationMessageJob} instead. * *

* The following properties are expected to be provided in the JobDataMap: * *

    *
  • JMS_CONNECTION_FACTORY_JNDI - The JNDI name of the JMS Connection Factory.
  • *
  • JMS_DESTINATION_JNDI - The JNDI name of the JMS destination.
  • *
  • JMS_USE_TXN - Whether or not to use a transacted jakarta.jms.Session.
  • *
  • JMS_ACK_MODE - The acknowledgement mode for the jakarta.jms.Session.
  • *
  • JMS_MSG_FACTORY_CLASS_NAME - The implementation class name for the JmsMessageFactory.
  • *
* *

* The following properties are optional * *

    *
  • JMS_USER - The JMS user for secure destinations. *
  • JMS_PASSWORD - The JMS password for secure destinations. *
* *

* The following properties can be used for JNDI support: *

    *
  • INITIAL_CONTEXT_FACTORY - The java.naming.factory.initial setting for JNDI. *
  • PROVIDER_URL - The java.naming.provider.url for JNDI. *
* * * @see JmsMessageFactory * * @author Weston M. Price (little fixes v. in 1.6.0 by Toni Alatalo) * * */ public final class SendQueueMessageJob implements Job { public void execute(final JobExecutionContext jobCtx) throws JobExecutionException { QueueConnection conn = null; QueueSession sess = null; QueueSender sender = null; try { final JobDataMap dataMap = jobCtx.getMergedJobDataMap(); final Context namingCtx = JmsHelper.getInitialContext(dataMap); final QueueConnectionFactory connFactory = (QueueConnectionFactory) namingCtx .lookup(dataMap .getString(JmsHelper.JMS_CONNECTION_FACTORY_JNDI)); if (!JmsHelper.isDestinationSecure(dataMap)) { conn = connFactory.createQueueConnection(); } else { final String user = dataMap.getString(JmsHelper.JMS_USER); final String password = dataMap .getString(JmsHelper.JMS_PASSWORD); conn = connFactory.createQueueConnection(user, password); } final boolean useTransactions = JmsHelper.useTransaction(dataMap); final int ackMode = dataMap.getInt(JmsHelper.JMS_ACK_MODE); sess = conn.createQueueSession(useTransactions, ackMode); final Queue queue = (Queue) namingCtx.lookup(dataMap .getString(JmsHelper.JMS_DESTINATION_JNDI)); sender = sess.createSender(queue); final JmsMessageFactory msgFactory = JmsHelper .getMessageFactory(dataMap .getString(JmsHelper.JMS_MSG_FACTORY_CLASS_NAME)); final Message msg = msgFactory.createMessage(dataMap, sess); sender.send(msg); } catch (final Exception e) { throw new JobExecutionException(e.getMessage()); } finally { JmsHelper.closeResource(sender); JmsHelper.closeResource(sess); JmsHelper.closeResource(conn); } } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jms/SendTopicMessageJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.jms; import jakarta.jms.Message; import jakarta.jms.Topic; import jakarta.jms.TopicConnection; import jakarta.jms.TopicConnectionFactory; import jakarta.jms.TopicPublisher; import jakarta.jms.TopicSession; import javax.naming.Context; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A Job that sends a jakarta.jms.Message to a * jakarta.jms.Topic. This class is for older JMS. If you are using * JMS 1.1, you should use {@link SendDestinationMessageJob} instead. * *

* The following properties are expected to be provided in the * JobDataMap: * *

    *
  • JMS_CONNECTION_FACTORY_JNDI - The JNDI name of the JMS * Connection Factory.
  • *
  • JMS_DESTINATION_JNDI - The JNDI name of the JMS * destination.
  • *
  • JMS_USE_TXN - Whether or not to use a transacted * jakarta.jms.Session.
  • *
  • JMS_ACK_MODE - The acknowledgment mode for the * jakarta.jms.Session.
  • *
  • JMS_MSG_FACTORY_CLASS_NAME - The implementation class * name for the JmsMessageFactory.
  • *
* *

* The following properties are optional * *

    *
  • JMS_USER - The JMS user for secure destinations. *
  • JMS_PASSWORD - The JMS password for secure destinations. *
* *

* The following properties can be used for JNDI support: * *

    *
  • INITIAL_CONTEXT_FACTORY - The java.naming.factory.initial * setting for JNDI. *
  • PROVIDER_URL - The java.naming.provider.url for JNDI. *
* * @see JmsMessageFactory * * @author Fernando Ribeiro * @author Weston M. Price */ public final class SendTopicMessageJob implements Job { public void execute(final JobExecutionContext jobCtx) throws JobExecutionException { TopicConnection conn = null; TopicSession sess = null; TopicPublisher publisher = null; try { final JobDataMap dataMap = jobCtx.getMergedJobDataMap(); final Context namingCtx = JmsHelper.getInitialContext(dataMap); final TopicConnectionFactory connFactory = (TopicConnectionFactory) namingCtx .lookup(dataMap .getString(JmsHelper.JMS_CONNECTION_FACTORY_JNDI)); if (!JmsHelper.isDestinationSecure(dataMap)) { conn = connFactory.createTopicConnection(); } else { final String user = dataMap.getString(JmsHelper.JMS_USER); final String password = dataMap .getString(JmsHelper.JMS_PASSWORD); conn = connFactory.createTopicConnection(user, password); } final boolean useTransaction = JmsHelper.useTransaction(dataMap); final int ackMode = dataMap.getInt(JmsHelper.JMS_ACK_MODE); sess = conn.createTopicSession(useTransaction, ackMode); final Topic topic = (Topic) namingCtx.lookup(dataMap .getString(JmsHelper.JMS_DESTINATION_JNDI)); publisher = sess.createPublisher(topic); final JmsMessageFactory messageFactory = JmsHelper .getMessageFactory(dataMap .getString(JmsHelper.JMS_MSG_FACTORY_CLASS_NAME)); final Message msg = messageFactory.createMessage(dataMap, sess); publisher.publish(msg); } catch (final Exception e) { throw new JobExecutionException(e); } finally { JmsHelper.closeResource(publisher); JmsHelper.closeResource(sess); JmsHelper.closeResource(conn); } } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/jmx/JMXInvokerJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import 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 qualified 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 separated * and has the following parts: <type>:<name>. 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 Logger log = LoggerFactory.getLogger(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 l = new LinkedList(); StringTokenizer strTok = new StringTokenizer(str, splitStr); while(strTok.hasMoreTokens()) { String tok = strTok.nextToken(); l.add(tok); } return (String[])l.toArray(new String[l.size()]); } private Object invoke(String objectName, String method, Object[] params, String[] types) throws Exception { MBeanServer server = (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0); ObjectName mbean = new ObjectName(objectName); if (server == null) { throw new Exception("Can't find mbean server"); } getLog().info("invoking " + method); return server.invoke(mbean, method, params, types); } protected Logger getLog() { return log; } } ================================================ FILE: quartz-jobs/src/main/java/org/quartz/jobs/ee/mail/SendMailJob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs.ee.mail; import java.util.Date; import java.util.Properties; import jakarta.mail.Address; import jakarta.mail.Authenticator; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.PasswordAuthentication; import jakarta.mail.Session; import jakarta.mail.Transport; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A Job which sends an e-mail with the configured content to the configured * recipient. * * Arbitrary mail.smtp.xxx settings can be added to job data and they will be * passed along the mail session *

* * @author James House */ public class SendMailJob implements Job { private final Logger log = LoggerFactory.getLogger(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"; /** * Username for authenticated session. Password must also be set if username is used. Optional. */ public static final String PROP_USERNAME = "username"; /** * Password for authenticated session. Optional. */ public static final String PROP_PASSWORD = "password"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * @see org.quartz.Job#execute(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 Logger 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(final MailInfo mailInfo) throws MessagingException { Properties properties = new Properties(); properties.put("mail.smtp.host", mailInfo.getSmtpHost()); // pass along extra smtp settings from users Properties extraSettings = mailInfo.getSmtpProperties(); if (extraSettings != null) { properties.putAll(extraSettings); } Authenticator authenticator = null; if (mailInfo.getUsername() != null && mailInfo.getPassword() != null) { log.info("using username '{}' and password 'xxx'", mailInfo.getUsername()); authenticator = new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(mailInfo.getUsername(), mailInfo.getPassword()); } }; } log.debug("Sending mail with properties: {}", properties); return Session.getDefaultInstance(properties, authenticator); } 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)); mailInfo.setUsername(getOptionalParm(data, PROP_USERNAME)); mailInfo.setPassword(getOptionalParm(data, PROP_PASSWORD)); // extra mail.smtp. properties from user Properties smtpProperties = new Properties(); for (String key : data.keySet()) { if (key.startsWith("mail.smtp.")) { smtpProperties.put(key, data.getString(key)); } } if (mailInfo.getSmtpProperties() == null) { mailInfo.setSmtpProperties(smtpProperties); } else { mailInfo.getSmtpProperties().putAll(smtpProperties); } 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; private String username; private String password; private Properties smtpProperties; @Override 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; } public Properties getSmtpProperties() { return smtpProperties; } public void setSmtpProperties(Properties smtpProperties) { this.smtpProperties = smtpProperties; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/integrations/tests/AutoInterruptableJobTest.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import java.util.List; import java.util.Properties; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicBoolean; import junit.framework.TestCase; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.plugins.interrupt.JobInterruptMonitorPlugin; /** * AutoInterruptable Job Monitor test * * @see org.quartz.Scheduler#interrupt() * * @author Rama Chavali */ public class AutoInterruptableJobTest extends TestCase { static final CyclicBarrier sync = new CyclicBarrier(2); public static class TestInterruptableJob implements InterruptableJob { public static final AtomicBoolean interrupted = new AtomicBoolean(false); public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("TestInterruptableJob is executing."); try { sync.await(); // wait for test thread to notice the job is now running } catch (InterruptedException e1) { } catch (BrokenBarrierException e1) { } for(int i=0; i < 200; i++) { try { Thread.sleep(50); // simulate being busy for a while, then checking interrupted flag... } catch (InterruptedException ignore) { } if(TestInterruptableJob.interrupted.get()) { System.out.println("TestInterruptableJob main loop detected interrupt signal."); break; } } try { System.out.println("TestInterruptableJob exiting with interrupted = " + interrupted); sync.await(); } catch (InterruptedException e) { } catch (BrokenBarrierException e) { } } public void interrupt() throws UnableToInterruptJobException { TestInterruptableJob.interrupted.set(true); System.out.println("TestInterruptableJob.interrupt() called."); } } @Override protected void setUp() throws Exception { } public void testJobAutoInterruption() throws Exception { // create a simple scheduler Properties config = new Properties(); config.setProperty("org.quartz.scheduler.instanceName", "InterruptableJobTest_Scheduler"); config.setProperty("org.quartz.scheduler.instanceId", "AUTO"); config.setProperty("org.quartz.threadPool.threadCount", "2"); config.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); config.setProperty("org.quartz.plugin.jobInterruptor.class", "org.quartz.plugins.interrupt.JobInterruptMonitorPlugin"); config.setProperty("org.quartz.plugin.jobInterruptor.defaultMaxRunTime", "1000"); Scheduler sched = new StdSchedulerFactory(config).getScheduler(); sched.start(); // add a job with a trigger that will fire immediately JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(JobInterruptMonitorPlugin.AUTO_INTERRUPTIBLE, "true"); JobDetail job = newJob() .ofType(TestInterruptableJob.class) .withIdentity("j1") .setJobData(jobDataMap) .build(); Trigger trigger = newTrigger() .withIdentity("t1") .forJob(job) .startNow() .build(); sched.scheduleJob(job, trigger); sync.await(); // make sure the job starts running... List executingJobs = sched.getCurrentlyExecutingJobs(); assertTrue("Number of executing jobs should be 1 ", executingJobs.size() == 1); sync.await(); // wait for the job to terminate assertTrue("Expected interrupted flag to be set on job class ", TestInterruptableJob.interrupted.get()); sched.clear(); sched.shutdown(); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/integrations/tests/DummyClassLoadHelper.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import org.quartz.simpl.CascadingClassLoadHelper; /** * * Always mimic CascadingClassLoadHelper except... * when looking for OnlyVisibleByDummyClassLoadHelperJob * * @author adahanne * */ public class DummyClassLoadHelper extends CascadingClassLoadHelper { @Override public Class loadClass(String className) throws ClassNotFoundException { if(className.equals("imaginary.class.OnlyVisibleByDummyClassLoadHelperJob")){ //we just translate the className ! Only DummyClassLoadHelper is able to do that ! className = "org.quartz.integrations.tests.HelloJob"; } return super.loadClass(className); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/integrations/tests/HelloJob.java ================================================ package org.quartz.integrations.tests; /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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. * */ import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* This is just a simple job that says "Hello" to the world. *

* * @author Bill Kratzer */ public class HelloJob implements Job { private static Logger _log = LoggerFactory.getLogger(HelloJob.class); /** *

* Empty constructor for job initialization *

*

* Quartz requires a public empty constructor so that the scheduler can * instantiate the class whenever it needs. *

*/ public HelloJob() { } /** *

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

* * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // Say Hello to the World and display the date/time _log.info("Hello World! - " + new Date()); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/integrations/tests/QTZ225_SchedulerClassLoadHelperForPlugins_Test.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.integrations.tests; import static org.junit.Assert.fail; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This test is rather a "smoke test" * * It will configure the scheduler with a DummyClassLoadHelper (that extends * CascadeClassLoadHelper) and using the XMLSchedulingDataProcessorPlugin * * The job class configured in quartz_data.xml does not exist. * * This test passes only if the plugin uses, like the SchedulerFactory does, the * DummyClassLoadHelper which is capable to load this imaginary job class * (previous behavior was : always instantiates the CascadeClassLoadHelper , not * considering the SchedulerFactory classLoadHelper) * * @author adahanne * */ public class QTZ225_SchedulerClassLoadHelperForPlugins_Test { private Scheduler sched; Logger log = LoggerFactory.getLogger(QTZ225_SchedulerClassLoadHelperForPlugins_Test.class); @Before public void setUp() throws SchedulerException { // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory("org/quartz/tests/QTZ225/quartz.properties"); sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- (Not Scheduling any Jobs - relying on XML definitions --"); log.info("------- Starting Scheduler ----------------"); // start the schedule sched.start(); } @Test public void dummyClassLoadHelperSuccessfullyLoadedImaginaryJobClassTest() throws SchedulerException { if (!sched.checkExists(new JobKey("ImaginaryJob"))) { fail("The dummy job was not added to the scheduler, certainly because the dummy classloadhelper was not used by the plugin"); } } @After public void tearDown() throws SchedulerException { log.info("------- Shutting down Scheduler ----------------"); sched.shutdown(); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/jobs/MyJobListener.java ================================================ package org.quartz.jobs; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; public class MyJobListener implements JobListener { public volatile JobExecutionException jobException; public CyclicBarrier barrier = new CyclicBarrier(2); @Override public String getName() { return "MyJobListener"; } @Override public void jobToBeExecuted(JobExecutionContext context) { // } @Override public void jobExecutionVetoed(JobExecutionContext context) { // } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { this.jobException = jobException; try { barrier.await(30, TimeUnit.SECONDS); } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/jobs/SendMailJobAuthTestBase.java ================================================ package org.quartz.jobs; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.jobs.ee.mail.SendMailJob; import org.subethamail.smtp.auth.PlainAuthenticationHandlerFactory; import org.subethamail.wiser.Wiser; public abstract class SendMailJobAuthTestBase { private Wiser wiser; private Scheduler scheduler; protected SimpleValidator simpleValidator; protected MyJobListener jobListener; private final String sender; private final String username; private final String password; public SendMailJobAuthTestBase(String sender, String username, String password) { this.sender = sender; this.username = username; this.password = password; } @Before public void setUp() throws Exception { simpleValidator = new SimpleValidator(); wiser = new Wiser(2500); wiser.getServer() .setAuthenticationHandlerFactory(new PlainAuthenticationHandlerFactory( simpleValidator)); wiser.start(); // set up scheduler jobListener = new MyJobListener(); scheduler = new StdSchedulerFactory().getScheduler(); scheduler.getListenerManager().addJobListener(jobListener); } @After public void tearDown() throws Exception { wiser.stop(); scheduler.shutdown(true); } @Test public void testWithAuthentication() throws Exception { JobDetail job = newJob(SendMailJob.class) .withIdentity("job1", "group1").build(); configureSendMailJob(job); Trigger trigger = newTrigger().withIdentity("trigger1", "group1") .startNow().build(); scheduler.scheduleJob(job, trigger); scheduler.start(); jobListener.barrier.await(30, TimeUnit.SECONDS); assertAuthentication(); } public abstract void assertAuthentication() throws Exception; protected void configureSendMailJob(JobDetail job) { JobDataMap jobData = job.getJobDataMap(); jobData.put(SendMailJob.PROP_SMTP_HOST, "localhost"); jobData.put(SendMailJob.PROP_SENDER, sender); jobData.put(SendMailJob.PROP_RECIPIENT, "receiver@host.com"); jobData.put(SendMailJob.PROP_SUBJECT, "test subject"); jobData.put(SendMailJob.PROP_MESSAGE, "do not reply"); jobData.put(SendMailJob.PROP_USERNAME, username); jobData.put(SendMailJob.PROP_PASSWORD, password); jobData.put("mail.smtp.port", "2500"); jobData.put("mail.smtp.auth", "true"); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/jobs/SendMailJobFakeAuth.java ================================================ package org.quartz.jobs; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import org.junit.Ignore; import org.subethamail.smtp.auth.LoginFailedException; @Ignore public class SendMailJobFakeAuth extends SendMailJobAuthTestBase { public SendMailJobFakeAuth() { super("fake@host.name", "fakeusername", "fakepassword"); } @Override public void assertAuthentication() throws Exception { assertThat(this.jobListener.jobException, notNullValue()); assertThat(this.simpleValidator.error, instanceOf(LoginFailedException.class)); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/jobs/SendMailJobRealAuth.java ================================================ package org.quartz.jobs; import org.junit.Ignore; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; @Ignore public class SendMailJobRealAuth extends SendMailJobAuthTestBase { public SendMailJobRealAuth() { super("real@host.name", "realusername", "realpassword"); } @Override public void assertAuthentication() throws Exception { assertThat(this.jobListener.jobException, nullValue()); assertThat(this.simpleValidator.error, nullValue()); } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/jobs/SendMailJobTest.java ================================================ package org.quartz.jobs; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.util.Properties; import java.util.concurrent.TimeUnit; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.PasswordAuthentication; import jakarta.mail.Session; import jakarta.mail.Transport; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.jobs.ee.mail.SendMailJob; import org.subethamail.wiser.Wiser; import org.subethamail.wiser.WiserMessage; @Ignore public class SendMailJobTest { private Wiser wiser; private Scheduler scheduler; private MyJobListener jobListener; @Before public void setup() throws Exception { wiser = new Wiser(); wiser.setPort(2500); wiser.start(); jobListener = new MyJobListener(); scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.getListenerManager().addJobListener(jobListener); } @After public void tearDown() throws Exception { scheduler.shutdown(); wiser.stop(); } @Test public void testSendMailJobNoAuthentication() throws Exception { JobDetail job = newJob(SendMailJob.class) .withIdentity("job1", "group1").build(); JobDataMap jobData = job.getJobDataMap(); jobData.put(SendMailJob.PROP_SMTP_HOST, "localhost"); jobData.put(SendMailJob.PROP_SENDER, "sender@host.com"); jobData.put(SendMailJob.PROP_RECIPIENT, "receiver@host.com"); jobData.put(SendMailJob.PROP_SUBJECT, "test subject"); jobData.put(SendMailJob.PROP_MESSAGE, "do not reply"); jobData.put("mail.smtp.port", "2500"); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startNow() .withSchedule( simpleSchedule().withIntervalInSeconds(120) .withRepeatCount(1)).build(); scheduler.scheduleJob(job, trigger); scheduler.start(); jobListener.barrier.await(30, TimeUnit.SECONDS); assertThat(wiser.getMessages().size(), equalTo(1)); WiserMessage message = wiser.getMessages().get(0); System.out.println(message); System.out.println(message.getMimeMessage().getSubject()); assertThat(message.getEnvelopeSender(), equalTo("sender@host.com")); assertThat(message.getEnvelopeReceiver(), equalTo("receiver@host.com")); assertThat(message.getMimeMessage().getSubject(), equalTo("test subject")); assertThat(IOUtils.toString(message.getMimeMessage().getInputStream()) .trim(), equalTo("do not reply")); } /** * replace xxx with your real credentials * * @throws Exception */ @Ignore @Test public void testRealAccountSendMail() throws Exception { Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.port", "465"); props.put("mail.smtp.ssl.enable", "true"); props.put("mail.smtp.host", "smtp.gmail.com"); props.put("mail.debug", "true"); Session session = Session.getInstance(props, new jakarta.mail.Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("xxx", "xxx"); } }); try { Message message = new MimeMessage(session); message.setFrom(new InternetAddress("xxx@gmail.com")); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("xxx@gmail.com")); message.setSubject("Test Subject"); message.setText("Test message"); Transport.send(message); System.out.println("Sent"); } catch (MessagingException e) { throw new RuntimeException(e); } } } ================================================ FILE: quartz-jobs/src/test/java/org/quartz/jobs/SimpleValidator.java ================================================ package org.quartz.jobs; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import org.subethamail.smtp.auth.LoginFailedException; import org.subethamail.smtp.auth.UsernamePasswordValidator; class SimpleValidator implements UsernamePasswordValidator { public LoginFailedException error; @Override public void login(String username, String password) throws LoginFailedException { System.out.println("UsernamePasswordValidator: login username '" + username + "' password '" + password + "'"); try { assertThat(username, equalTo("realusername")); assertThat(password, equalTo("realpassword")); } catch (Throwable e) { error = new LoginFailedException(e.getMessage()); throw error; } } } ================================================ FILE: quartz-jobs/src/test/resources/log4j.properties ================================================ # Configure logging for testing log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n log4j.logger.org.quartz.jobs.ee.mail=DEBUG ================================================ FILE: quartz-jobs/src/test/resources/org/quartz/tests/QTZ225/log4j.xml ================================================ ================================================ FILE: quartz-jobs/src/test/resources/org/quartz/tests/QTZ225/quartz.properties ================================================ #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: TestScheduler org.quartz.scheduler.instanceId: AUTO org.quartz.scheduler.classLoadHelper.class: org.quartz.integrations.tests.DummyClassLoadHelper #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 3 org.quartz.threadPool.threadPriority: 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore #============================================================================ # Configure Plugins #============================================================================ org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingJobHistoryPlugin org.quartz.plugin.jobInitializer.class: org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin org.quartz.plugin.jobInitializer.fileNames: org/quartz/tests/QTZ225/quartz_data.xml org.quartz.plugin.jobInitializer.failOnFileNotFound: true org.quartz.plugin.jobInitializer.scanInterval: 120 org.quartz.plugin.jobInitializer.wrapInUserTransaction: false ================================================ FILE: quartz-jobs/src/test/resources/org/quartz/tests/QTZ225/quartz_data.xml ================================================ * * true false ImaginaryJob imaginary.class.OnlyVisibleByDummyClassLoadHelperJob TestSimpleTrigger1AtFiveSecondInterval ImaginaryJob -1 5000 ================================================ FILE: quartz-stubs/build.gradle ================================================ plugins { id 'java-library' } dependencies { implementation "org.slf4j:slf4j-api:$slf4jVersion" runtimeOnly "org.slf4j:slf4j-log4j12:$slf4jVersion" testImplementation "junit:junit:$junitVersion" } ================================================ FILE: quartz-stubs/src/main/java/oracle/sql/BLOB.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 oracle.sql; import java.io.InputStream; import java.io.OutputStream; import java.sql.Blob; import java.sql.SQLException; public class BLOB implements Blob { public long length() throws SQLException { return 0; } public byte[] getBytes(long pos, int length) throws SQLException { return null; } public InputStream getBinaryStream() throws SQLException { return null; } public long position(byte[] pattern, long start) throws SQLException { return 0; } public long position(Blob pattern, long start) throws SQLException { return 0; } public int setBytes(long pos, byte[] bytes) throws SQLException { return 0; } public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { return 0; } public OutputStream setBinaryStream(long pos) throws SQLException { return null; } public void truncate(long len) throws SQLException { // } public void free() throws SQLException { // } public InputStream getBinaryStream(long pos, long length) throws SQLException { return null; } public int putBytes(long pos, byte[] data) throws SQLException { return 0; } public void trim(long length) throws SQLException { // } } ================================================ FILE: quartz-stubs/src/main/java/org/quartz/jobs/DirectoryScanListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; import java.io.File; /** * Interface for objects wishing to receive a 'call-back' from a * DirectoryScanJob. * *

Instances should be stored in the org.quartz.SchedulerContext * such that the DirectoryScanJob can find it.

* * @author jhouse */ public interface DirectoryScanListener { /** * @param updatedFiles The set of files that were updated/added since the * last scan of the directory */ void filesUpdatedOrAdded(File[] updatedFiles); } ================================================ FILE: quartz-stubs/src/main/java/org/quartz/jobs/FileScanListener.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 org.quartz.jobs; /** * Interface for objects wishing to receive a 'call-back' from a * FileScanJob. * * @author jhouse */ public interface FileScanListener { void fileUpdated(String fileName); } ================================================ FILE: quartz-stubs/src/main/java/weblogic/jdbc/jts/Driver.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 weblogic.jdbc.jts; import java.util.logging.Logger; import java.sql.Connection; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.util.Properties; public class Driver implements java.sql.Driver { public Connection connect(String url, Properties info) throws SQLException { return null; } public boolean acceptsURL(String url) throws SQLException { return false; } public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return null; } public int getMajorVersion() { return 0; } public int getMinorVersion() { return 0; } public boolean jdbcCompliant() { return false; } public Logger getParentLogger() { return null; } } ================================================ FILE: quartz-stubs/src/main/java/weblogic/jdbc/vendor/oracle/OracleThinBlob.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * Copyright IBM Corp. 2024, 2025 * * 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 weblogic.jdbc.vendor.oracle; import java.io.OutputStream; import java.sql.SQLException; public interface OracleThinBlob { public abstract OutputStream getBinaryOutputStream() throws SQLException; public abstract int getBufferSize() throws SQLException; public abstract int getChunkSize() throws SQLException; public abstract int putBytes(long l, byte abyte0[]) throws SQLException; public abstract void truncate(long l) throws SQLException; public abstract void trim(long l) throws SQLException; } ================================================ FILE: readme.adoc ================================================ == Quartz Scheduler Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. See <> Status of the build: [link="https://dev.azure.com/TerracottaCI/quartz/_build/latest?definitionId=24"] image::https://dev.azure.com/TerracottaCI/quartz/_apis/build/status/quartz-scheduler.quartz[Build Status] == Quick Links https://www.quartz-scheduler.org/community/contribute.html ================================================ FILE: settings.gradle ================================================ pluginManagement { plugins { id("io.github.gradle-nexus.publish-plugin") version '2.0.0' id ("biz.aQute.bnd.builder") version '6.4.0' } } rootProject.name = 'quartz-scheduler' include 'quartz-stubs' include 'quartz-jobs' include 'quartz' include 'examples'