Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quartz: add a test for programmatic job metadata #41381

Merged
merged 1 commit into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +41,9 @@ public class ProgrammaticJobsTest {
.withApplicationRoot((jar) -> jar
.addClasses(Jobs.class));

@Inject
org.quartz.Scheduler quartzScheduler;

@Inject
Scheduler scheduler;

Expand Down Expand Up @@ -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());
Expand All @@ -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"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1128,12 +1128,12 @@ public void handle(Void event) {

static class QuartzTrigger implements Trigger {

final org.quartz.TriggerKey triggerKey;
final Function<TriggerKey, org.quartz.Trigger> triggerFunction;
final ScheduledInvoker invoker;
final Duration gracePeriod;
final boolean isProgrammatic;
final String methodDescription;
private final org.quartz.TriggerKey triggerKey;
private final Function<TriggerKey, org.quartz.Trigger> triggerFunction;
private final ScheduledInvoker invoker;
private final Duration gracePeriod;
private final boolean isProgrammatic;
private final String methodDescription;

final boolean runBlockingMethodOnQuartzThread;

Expand All @@ -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;
}

Expand All @@ -1173,18 +1173,18 @@ 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
public String getMethodDescription() {
return methodDescription;
}

private org.quartz.Trigger trigger() {
return triggerFunction.apply(triggerKey);
}

}

static class QuartzScheduledExecution implements ScheduledExecution {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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")
Expand All @@ -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 -> {
Expand All @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down