diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MisfirePolicyTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MisfirePolicyTest.java new file mode 100644 index 00000000000000..60dababc817776 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MisfirePolicyTest.java @@ -0,0 +1,164 @@ +package io.quarkus.quartz.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import javax.inject.Inject; + +import org.jboss.logging.Logger; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.quartz.CronTrigger; +import org.quartz.SchedulerException; +import org.quartz.SimpleTrigger; +import org.quartz.Trigger; +import org.quartz.TriggerKey; + +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.Scheduler; +import io.quarkus.test.QuarkusUnitTest; + +public class MisfirePolicyTest { + + private static final Logger LOGGER = Logger.getLogger(MisfirePolicyTest.class.getName()); + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Jobs.class) + .addAsResource(new StringAsset( + "quarkus.quartz.\"simple_ignore_misfire_policy\".misfire-policy=IGNORE_MISFIRE_POLICY\n" + + "quarkus.quartz.\"cron_ignore_misfire_policy\".misfire-policy=IGNORE_MISFIRE_POLICY\n" + + "quarkus.quartz.\"simple_invalid_misfire_policy\".misfire-policy=CRON_TRIGGER_DO_NOTHING\n" + + + "quarkus.quartz.\"cron_invalid_misfire_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT\n" + + + "quarkus.quartz.\"simple_reschedule_now_existing_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT\n" + + + "quarkus.quartz.\"simple_reschedule_now_remaining_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT\n" + + + "quarkus.quartz.\"simple_reschedule_next_existing_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT\n" + + + "quarkus.quartz.\"simple_reschedule_next_remaining_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT\n" + + + "quarkus.quartz.\"cron_do_nothing_policy\".misfire-policy=CRON_TRIGGER_DO_NOTHING\n" + + ), "application.properties")); + + @Inject + org.quartz.Scheduler quartz; + + @Test + public void testDefaultMisfirePolicy() throws SchedulerException { + Trigger defaultMisfirePolicy = quartz + .getTrigger(new TriggerKey("default_misfire_policy", Scheduler.class.getName())); + assertNotNull(defaultMisfirePolicy); + assertEquals(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, defaultMisfirePolicy.getMisfireInstruction()); + + } + + @Test + public void testIgnoreMisfirePolicy() throws SchedulerException { + Trigger simpleIgnoreMisfirePolicyTrigger = quartz + .getTrigger(new TriggerKey("simple_ignore_misfire_policy", Scheduler.class.getName())); + assertNotNull(simpleIgnoreMisfirePolicyTrigger); + assertEquals(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, + simpleIgnoreMisfirePolicyTrigger.getMisfireInstruction()); + + Trigger cronIgnoreMisfirePolicyTrigger = quartz + .getTrigger(new TriggerKey("cron_ignore_misfire_policy", Scheduler.class.getName())); + assertNotNull(cronIgnoreMisfirePolicyTrigger); + assertEquals(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, cronIgnoreMisfirePolicyTrigger.getMisfireInstruction()); + } + + @Test + public void testInvalidMisfirePolicy() throws SchedulerException { + Trigger simpleInvalidMisfirePolicyTrigger = quartz + .getTrigger(new TriggerKey("simple_invalid_misfire_policy", Scheduler.class.getName())); + assertNotNull(simpleInvalidMisfirePolicyTrigger); + assertEquals(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, simpleInvalidMisfirePolicyTrigger.getMisfireInstruction()); + + Trigger cronInvalidMisfirePolicyTrigger = quartz + .getTrigger(new TriggerKey("cron_invalid_misfire_policy", Scheduler.class.getName())); + assertNotNull(cronInvalidMisfirePolicyTrigger); + assertEquals(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, cronInvalidMisfirePolicyTrigger.getMisfireInstruction()); + } + + @Test + public void testSimpleTriggerMisfirePolicy() throws SchedulerException { + Trigger simpleRescheduleNowExistingPolicy = quartz + .getTrigger(new TriggerKey("simple_reschedule_now_existing_policy", Scheduler.class.getName())); + assertNotNull(simpleRescheduleNowExistingPolicy); + assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT, + simpleRescheduleNowExistingPolicy.getMisfireInstruction()); + + Trigger simpleRescheduleNowRemainingPolicy = quartz + .getTrigger(new TriggerKey("simple_reschedule_now_remaining_policy", Scheduler.class.getName())); + assertNotNull(simpleRescheduleNowRemainingPolicy); + assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT, + simpleRescheduleNowRemainingPolicy.getMisfireInstruction()); + + Trigger simpleRescheduleNextExistingPolicy = quartz + .getTrigger(new TriggerKey("simple_reschedule_next_existing_policy", Scheduler.class.getName())); + assertNotNull(simpleRescheduleNextExistingPolicy); + assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT, + simpleRescheduleNextExistingPolicy.getMisfireInstruction()); + + Trigger simpleRescheduleNextRemainingPolicy = quartz + .getTrigger(new TriggerKey("simple_reschedule_next_remaining_policy", Scheduler.class.getName())); + assertNotNull(simpleRescheduleNextRemainingPolicy); + assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT, + simpleRescheduleNextRemainingPolicy.getMisfireInstruction()); + } + + @Test + public void testCronTriggerMisfirePolicy() throws SchedulerException { + Trigger cronDoNothingPolicy = quartz.getTrigger(new TriggerKey("cron_do_nothing_policy", Scheduler.class.getName())); + assertNotNull(cronDoNothingPolicy); + assertEquals(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, cronDoNothingPolicy.getMisfireInstruction()); + } + + static class Jobs { + + @Scheduled(identity = "default_misfire_policy", every = "1s") + void defaultMisfirePolicy() { + } + + @Scheduled(identity = "simple_ignore_misfire_policy", every = "1s") + void simpleIgnoreMisfirePolicy() { + } + + @Scheduled(identity = "cron_ignore_misfire_policy", cron = "0/1 * * * * ?") + void cronIgnoreMisfirePolicy() { + } + + @Scheduled(identity = "simple_invalid_misfire_policy", every = "1s") + void simpleInvalidMisfirePolicy() { + } + + @Scheduled(identity = "cron_invalid_misfire_policy", cron = "0/1 * * * * ?") + void cronInvalidMisfirePolicy() { + } + + @Scheduled(identity = "simple_reschedule_now_existing_policy", every = "1s") + void simpleRescheduleNowExistingPolicy() { + } + + @Scheduled(identity = "simple_reschedule_now_remaining_policy", every = "1s") + void simpleRescheduleNowRemainingPolicy() { + } + + @Scheduled(identity = "simple_reschedule_next_existing_policy", every = "1s") + void simpleRescheduleNextExistingPolicy() { + } + + @Scheduled(identity = "simple_reschedule_next_remaining_policy", every = "1s") + void simpleRescheduleNextRemainingPolicy() { + } + + @Scheduled(identity = "cron_do_nothing_policy", cron = "0/1 * * * * ?") + void cronDoNothingPolicy() { + } + } +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzMisfirePolicy.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzMisfirePolicy.java new file mode 100644 index 00000000000000..2ac280057a23fd --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzMisfirePolicy.java @@ -0,0 +1,12 @@ +package io.quarkus.quartz.runtime; + +public enum QuartzMisfirePolicy { + SMART_POLICY, + IGNORE_MISFIRE_POLICY, + FIRE_NOW, + SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT, + SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT, + SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT, + SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT, + CRON_TRIGGER_DO_NOTHING +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java index 76d76d096eae02..e1df0367b32347 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java @@ -1,7 +1,11 @@ package io.quarkus.quartz.runtime; import java.time.Duration; +import java.util.Map; +import io.quarkus.runtime.annotations.ConfigDocMapKey; +import io.quarkus.runtime.annotations.ConfigDocSection; +import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -45,4 +49,22 @@ public class QuartzRuntimeConfig { */ @ConfigItem(defaultValue = "normal") public QuartzStartMode startMode; + + /** + * per job configuration. + */ + @ConfigDocSection + @ConfigDocMapKey("job-identity") + @ConfigItem(name = ConfigItem.PARENT) + public Map namedJobs; + + @ConfigGroup + public static class QuartzNamedRuntimeConfig { + /** + * The quartz misfire policy for this job. + */ + @ConfigItem(defaultValue = "SMART_POLICY") + public QuartzMisfirePolicy misfirePolicy; + } + } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java index af33481f128cee..a9e74f587c8c65 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java @@ -1,5 +1,11 @@ package io.quarkus.quartz.runtime; +import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.CRON_TRIGGER_DO_NOTHING; +import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT; +import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT; +import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT; +import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT; + import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; @@ -11,6 +17,7 @@ import java.util.Objects; import java.util.OptionalLong; import java.util.Properties; +import java.util.Set; import javax.annotation.PreDestroy; import javax.annotation.Priority; @@ -154,7 +161,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc .usingJobData(INVOKER_KEY, method.getInvokerClassName()) .requestRecovery(); ScheduleBuilder scheduleBuilder; - + String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron()); if (!cron.isEmpty()) { if (SchedulerUtils.isOff(cron)) { @@ -174,15 +181,66 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc break; } } - scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron); + if (runtimeConfig.namedJobs.containsKey(identity)) { + switch (runtimeConfig.namedJobs.get(identity).misfirePolicy) { + case IGNORE_MISFIRE_POLICY: + cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + break; + case FIRE_NOW: + cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); + break; + case CRON_TRIGGER_DO_NOTHING: + cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); + } + if (Set.of( + SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT, + SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT, + SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT, + SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT) + .contains(runtimeConfig.namedJobs.get(identity).misfirePolicy)) { + LOGGER.warnf( + "configured cron job %s with invalid misfire policy. defaulting to SMART_POLICY", + identity); + } + } + scheduleBuilder = cronScheduleBuilder; } else if (!scheduled.every().isEmpty()) { OptionalLong everyMillis = SchedulerUtils.parseEveryAsMillis(scheduled); if (!everyMillis.isPresent()) { continue; } - scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() + SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInMilliseconds(everyMillis.getAsLong()) .repeatForever(); + if (runtimeConfig.namedJobs.containsKey(identity)) { + switch (runtimeConfig.namedJobs.get(identity).misfirePolicy) { + case IGNORE_MISFIRE_POLICY: + simpleScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + break; + case FIRE_NOW: + simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); + break; + case SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT: + simpleScheduleBuilder.withMisfireHandlingInstructionNowWithExistingCount(); + break; + case SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT: + simpleScheduleBuilder.withMisfireHandlingInstructionNowWithRemainingCount(); + break; + case SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT: + simpleScheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); + break; + case SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: + simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount(); + break; + } + if (CRON_TRIGGER_DO_NOTHING.equals(runtimeConfig.namedJobs.get(identity).misfirePolicy)) { + LOGGER.warnf( + "configured simple job %s with invalid misfire policy. defaulting to SMART_POLICY", + identity); + } + } + scheduleBuilder = simpleScheduleBuilder; } else { throw new IllegalArgumentException("Invalid schedule configuration: " + scheduled); }