From 985f582da8381f754f6e31497a39af4e1d4860d7 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 24 Jun 2024 10:58:48 +0200 Subject: [PATCH] Quartz: add a test for programmatic job metadata - this is a follow-up of https://github.com/quarkusio/quarkus/pull/41370 --- .../programmatic/ProgrammaticJobsTest.java | 16 +++++++- .../quartz/runtime/QuartzSchedulerImpl.java | 26 ++++++------- .../it/quartz/ProgrammaticJobResource.java | 38 ++++++++++++++++++- .../io/quarkus/it/quartz/QuartzTestCase.java | 4 +- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/ProgrammaticJobsTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/ProgrammaticJobsTest.java index 53dfe4dedb466..d2f5e62a5a55e 100644 --- a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/ProgrammaticJobsTest.java +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/ProgrammaticJobsTest.java @@ -18,6 +18,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.SchedulerException; import io.quarkus.arc.Arc; import io.quarkus.arc.Unremovable; @@ -38,6 +41,9 @@ public class ProgrammaticJobsTest { .withApplicationRoot((jar) -> jar .addClasses(Jobs.class)); + @Inject + org.quartz.Scheduler quartzScheduler; + @Inject Scheduler scheduler; @@ -109,8 +115,9 @@ public void testJobs() throws InterruptedException { } @Test - public void testAsyncJob() throws InterruptedException { - JobDefinition asyncJob = scheduler.newJob("fooAsync") + public void testAsyncJob() throws InterruptedException, SchedulerException { + String identity = "fooAsync"; + JobDefinition asyncJob = scheduler.newJob(identity) .setInterval("1s") .setAsyncTask(ec -> { assertTrue(Context.isOnEventLoopThread() && VertxContext.isOnDuplicatedContext()); @@ -125,6 +132,11 @@ public void testAsyncJob() throws InterruptedException { Trigger trigger = asyncJob.schedule(); assertNotNull(trigger); + // JobKey is always built using the identity and "io.quarkus.scheduler.Scheduler" as the group name + JobDetail jobDetail = quartzScheduler.getJobDetail(new JobKey(identity, Scheduler.class.getName())); + assertNotNull(jobDetail); + // We only store metadata for DB store type + assertNull(jobDetail.getJobDataMap().get("scheduled_metadata")); assertTrue(ProgrammaticJobsTest.ASYNC_LATCH.await(5, TimeUnit.SECONDS)); assertNotNull(scheduler.unscheduleJob("fooAsync")); diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java index 0d07ff3dd934d..7e08b2a596de5 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java @@ -1128,12 +1128,12 @@ public void handle(Void event) { static class QuartzTrigger implements Trigger { - final org.quartz.TriggerKey triggerKey; - final Function triggerFunction; - final ScheduledInvoker invoker; - final Duration gracePeriod; - final boolean isProgrammatic; - final String methodDescription; + private final org.quartz.TriggerKey triggerKey; + private final Function triggerFunction; + private final ScheduledInvoker invoker; + private final Duration gracePeriod; + private final boolean isProgrammatic; + private final String methodDescription; final boolean runBlockingMethodOnQuartzThread; @@ -1151,13 +1151,13 @@ static class QuartzTrigger implements Trigger { @Override public Instant getNextFireTime() { - Date nextFireTime = getTrigger().getNextFireTime(); + Date nextFireTime = trigger().getNextFireTime(); return nextFireTime != null ? nextFireTime.toInstant() : null; } @Override public Instant getPreviousFireTime() { - Date previousFireTime = getTrigger().getPreviousFireTime(); + Date previousFireTime = trigger().getPreviousFireTime(); return previousFireTime != null ? previousFireTime.toInstant() : null; } @@ -1173,11 +1173,7 @@ public boolean isOverdue() { @Override public String getId() { - return getTrigger().getKey().getName(); - } - - private org.quartz.Trigger getTrigger() { - return triggerFunction.apply(triggerKey); + return trigger().getKey().getName(); } @Override @@ -1185,6 +1181,10 @@ public String getMethodDescription() { return methodDescription; } + private org.quartz.Trigger trigger() { + return triggerFunction.apply(triggerKey); + } + } static class QuartzScheduledExecution implements ScheduledExecution { diff --git a/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/ProgrammaticJobResource.java b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/ProgrammaticJobResource.java index a335af6e6c882..26b7adb157b83 100644 --- a/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/ProgrammaticJobResource.java +++ b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/ProgrammaticJobResource.java @@ -11,6 +11,8 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import org.quartz.JobDetail; +import org.quartz.JobKey; import org.quartz.SchedulerException; import org.quartz.TriggerKey; @@ -20,6 +22,7 @@ import io.quarkus.scheduler.Scheduler; import io.quarkus.scheduler.Scheduler.JobDefinition; import io.quarkus.scheduler.Trigger; +import io.quarkus.scheduler.common.runtime.SyntheticScheduled; import io.smallrye.mutiny.Uni; @Path("/scheduler/programmatic") @@ -33,7 +36,8 @@ public class ProgrammaticJobResource { @POST @Path("register") - public void register() { + @Produces(MediaType.TEXT_PLAIN) + public String register() throws SchedulerException { JobDefinition syncJob = scheduler.newJob("sync").setInterval("1s"); try { syncJob.setTask(se -> { @@ -53,6 +57,21 @@ public void register() { } catch (IllegalStateException expected) { } syncJob.setTask(SyncJob.class).schedule(); + // Verify the serialized metadata + // JobKey is always built using the identity and "io.quarkus.scheduler.Scheduler" as the group name + JobDetail syncJobDetail = scheduler.getScheduler().getJobDetail(new JobKey("sync", Scheduler.class.getName())); + if (syncJobDetail == null) { + return "Syn job detail not found"; + } + SyntheticScheduled syncMetadata = SyntheticScheduled + .fromJson(syncJobDetail.getJobDataMap().get("scheduled_metadata").toString()); + if (!syncMetadata.every().equals("1s")) { + return "Sync interval not set"; + } + if (!SyncJob.class.getName() + .equals(syncJobDetail.getJobDataMap().getOrDefault("execution_metadata_task_class", "").toString())) { + return "execution_metadata_task_class not set"; + } JobDefinition asyncJob = scheduler.newJob("async").setInterval("1s"); try { @@ -61,6 +80,23 @@ public void register() { } catch (IllegalStateException expected) { } asyncJob.setAsyncTask(AsyncJob.class).schedule(); + + // Verify the serialized metadata + // JobKey is always built using the identity and "io.quarkus.scheduler.Scheduler" as the group name + JobDetail asynJobDetail = scheduler.getScheduler().getJobDetail(new JobKey("async", Scheduler.class.getName())); + if (asynJobDetail == null) { + return "Job detail not found"; + } + SyntheticScheduled asyncMetadata = SyntheticScheduled + .fromJson(asynJobDetail.getJobDataMap().get("scheduled_metadata").toString()); + if (!asyncMetadata.every().equals("1s")) { + return "Interval not set"; + } + if (!AsyncJob.class.getName() + .equals(asynJobDetail.getJobDataMap().getOrDefault("execution_metadata_async_task_class", "").toString())) { + return "execution_metadata_async_task_class not set"; + } + return "OK"; } @GET diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java index b5b44ec139f97..ee28275859e08 100644 --- a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java @@ -38,7 +38,9 @@ public void testFixedInstanceIdGenerator() { @Test public void testProgrammaticJobs() { - given().when().post("/scheduler/programmatic/register").then().statusCode(204); + Response response = given().when().post("/scheduler/programmatic/register"); + assertEquals(200, response.statusCode()); + assertEquals("OK", response.asString()); assertCounter("/scheduler/programmatic/sync", 1, Duration.ofSeconds(3)); assertCounter("/scheduler/programmatic/async", 1, Duration.ofSeconds(3)); }