Skip to content

Commit

Permalink
Quartz + Scheduler: Allow to use config in Identity field. Fixes #14967
Browse files Browse the repository at this point in the history
  • Loading branch information
Sgitario committed Feb 15, 2021
1 parent fa941e8 commit 5168dfb
Show file tree
Hide file tree
Showing 14 changed files with 342 additions and 63 deletions.
9 changes: 9 additions & 0 deletions docs/src/main/asciidoc/scheduler-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ Sometimes a possibility to specify an explicit id may come in handy.
void myMethod() { }
----

If a value starts with `{` and ends with `}` then the scheduler attempts to find a corresponding config property and use the configured value instead.

.Interval Config Property Example
[source,java]
----
@Scheduled(identity = "{myMethod.identity.expr}")
void myMethod() { }
----

=== Delayed Execution

`@Scheduled` provides two ways to delay the time a trigger should start firing at.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.quartz.test;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class DuplicateIdentityExpressionTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(IllegalStateException.class)
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(DuplicateIdentityExpressionTest.InvalidBean.class)
.addAsResource(new StringAsset("my.identity=my_name"),
"application.properties"));

@Test
public void test() {
}

static class InvalidBean {

@Scheduled(every = "1s", identity = "{my.identity}")
@Scheduled(every = "1s", identity = "my_name")
void wrong() {
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.quartz.test;

import java.util.NoSuchElementException;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class MissingConfigIdentityExpressionTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(NoSuchElementException.class)
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(MissingConfigIdentityExpressionTest.InvalidBean.class));

@Test
public void test() {
}

static class InvalidBean {

@Scheduled(every = "1s", identity = "{my.identity}")
void wrong() {
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.quartz.test;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class SimpleIdentityTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Jobs.class)
.addAsResource(new StringAsset("jobs.identity=every_1s_another_name"),
"application.properties"));

@Test
public void testJobsWithIdentity() throws InterruptedException {
// Only assert that the scheduled method is working fine
assertTrue(Jobs.LATCH.await(5, TimeUnit.SECONDS));
}

public static class Jobs {

static final CountDownLatch LATCH = new CountDownLatch(2);

@Scheduled(every = "1s", identity = "every_1s_name")
@Scheduled(every = "1s", identity = "{jobs.identity}")
void ping() {
LATCH.countDown();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.eclipse.microprofile.config.Config;
import org.jboss.logging.Logger;
import org.quartz.CronScheduleBuilder;
import org.quartz.Job;
Expand Down Expand Up @@ -55,8 +54,8 @@
import io.quarkus.scheduler.runtime.ScheduledMethodMetadata;
import io.quarkus.scheduler.runtime.SchedulerContext;
import io.quarkus.scheduler.runtime.SchedulerRuntimeConfig;
import io.quarkus.scheduler.runtime.SimpleScheduler;
import io.quarkus.scheduler.runtime.SkipConcurrentExecutionInvoker;
import io.quarkus.scheduler.runtime.util.SchedulerUtils;

@Singleton
public class QuartzScheduler implements Scheduler {
Expand All @@ -68,9 +67,8 @@ public class QuartzScheduler implements Scheduler {
private final boolean enabled;
private final boolean startHalted;

public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Config config,
SchedulerRuntimeConfig schedulerRuntimeConfig, Event<SkippedExecution> skippedExecutionEvent, Instance<Job> jobs,
Instance<UserTransaction> userTransation) {
public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, SchedulerRuntimeConfig schedulerRuntimeConfig,
Event<SkippedExecution> skippedExecutionEvent, Instance<Job> jobs, Instance<UserTransaction> userTransation) {
enabled = schedulerRuntimeConfig.enabled;
final QuartzRuntimeConfig runtimeConfig = quartzSupport.getRuntimeConfig();
warnDeprecated(runtimeConfig);
Expand Down Expand Up @@ -118,7 +116,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Co
int nameSequence = 0;

for (Scheduled scheduled : method.getSchedules()) {
String identity = scheduled.identity().trim();
String identity = SchedulerUtils.lookUpPropertyValue(scheduled.identity());
if (identity.isEmpty()) {
identity = ++nameSequence + "_" + method.getInvokerClassName();
}
Expand All @@ -136,11 +134,8 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Co
.requestRecovery();
ScheduleBuilder<?> scheduleBuilder;

String cron = scheduled.cron().trim();
String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron());
if (!cron.isEmpty()) {
if (SchedulerContext.isConfigValue(cron)) {
cron = config.getValue(SchedulerContext.getConfigProperty(cron), String.class);
}
if (!CronType.QUARTZ.equals(cronType)) {
// Migrate the expression
Cron cronExpr = parser.parse(cron);
Expand All @@ -158,8 +153,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Co
scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
} else if (!scheduled.every().isEmpty()) {
scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(
SimpleScheduler.parseDuration(scheduled, scheduled.every(), "every").toMillis())
.withIntervalInMilliseconds(SchedulerUtils.parseEveryAsMillis(scheduled))
.repeatForever();
} else {
throw new IllegalArgumentException("Invalid schedule configuration: " + scheduled);
Expand All @@ -173,8 +167,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Co
if (scheduled.delay() > 0) {
millisToAdd = scheduled.delayUnit().toMillis(scheduled.delay());
} else if (!scheduled.delayed().isEmpty()) {
millisToAdd = Math
.abs(SimpleScheduler.parseDuration(scheduled, scheduled.delayed(), "delayed").toMillis());
millisToAdd = SchedulerUtils.parseDelayedAsMillis(scheduled);
}
if (millisToAdd != null) {
triggerBuilder.startAt(new Date(Instant.now()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import io.quarkus.scheduler.runtime.SchedulerRecorder;
import io.quarkus.scheduler.runtime.SimpleScheduler;
import io.quarkus.scheduler.runtime.devconsole.SchedulerDevConsoleRecorder;
import io.quarkus.scheduler.runtime.util.SchedulerUtils;

/**
* @author Martin Kouba
Expand Down Expand Up @@ -306,7 +307,7 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu
AnnotationValue everyValue = schedule.value("every");
if (cronValue != null && !cronValue.asString().trim().isEmpty()) {
String cron = cronValue.asString().trim();
if (SchedulerContext.isConfigValue(cron)) {
if (SchedulerUtils.isConfigValue(cron)) {
// Don't validate config property
return null;
}
Expand All @@ -323,7 +324,7 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu
} else {
if (everyValue != null && !everyValue.asString().trim().isEmpty()) {
String every = everyValue.asString().trim();
if (SchedulerContext.isConfigValue(every)) {
if (SchedulerUtils.isConfigValue(every)) {
return null;
}
if (Character.isDigit(every.charAt(0))) {
Expand All @@ -343,7 +344,7 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu
if (delay == null || delay.asLong() <= 0) {
if (delayedValue != null && !delayedValue.asString().trim().isEmpty()) {
String delayed = delayedValue.asString().trim();
if (SchedulerContext.isConfigValue(delayed)) {
if (SchedulerUtils.isConfigValue(delayed)) {
return null;
}
if (Character.isDigit(delayed.charAt(0))) {
Expand All @@ -365,7 +366,7 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu

AnnotationValue identityValue = schedule.value("identity");
if (identityValue != null) {
String identity = identityValue.asString().trim();
String identity = SchedulerUtils.lookUpPropertyValue(identityValue.asString());
AnnotationInstance previousInstanceWithSameIdentity = encounteredIdentities.get(identity);
if (previousInstanceWithSameIdentity != null) {
String message = String.format("The identity: \"%s\" on: %s is not unique and it has already bean used by : %s",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.scheduler.test;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class DuplicateIdentityExpressionTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(IllegalStateException.class)
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(DuplicateIdentityExpressionTest.InvalidBean.class)
.addAsResource(new StringAsset("my.identity=my_name"),
"application.properties"));

@Test
public void test() {
}

static class InvalidBean {

@Scheduled(every = "1s", identity = "{my.identity}")
@Scheduled(every = "1s", identity = "my_name")
void wrong() {
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.scheduler.test;

import java.util.NoSuchElementException;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class MissingConfigIdentityExpressionTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(NoSuchElementException.class)
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(MissingConfigIdentityExpressionTest.InvalidBean.class));

@Test
public void test() {
}

static class InvalidBean {

@Scheduled(every = "1s", identity = "{my.identity}")
void wrong() {
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.scheduler.test;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class SimpleIdentityTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Jobs.class)
.addAsResource(new StringAsset("jobs.identity=every_1s_another_name"),
"application.properties"));

@Test
public void testJobsWithIdentity() throws InterruptedException {
// Only assert that the scheduled method is working fine
assertTrue(Jobs.LATCH.await(5, TimeUnit.SECONDS));
}

public static class Jobs {

static final CountDownLatch LATCH = new CountDownLatch(2);

@Scheduled(every = "1s", identity = "every_1s_name")
@Scheduled(every = "1s", identity = "{jobs.identity}")
void ping() {
LATCH.countDown();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
/**
* Optionally defines a unique identifier for this job.
* <p>
* If the value starts with "&#123;" and ends with "&#125;" the scheduler attempts to find a corresponding config property
* and use the configured value instead: {@code &#64;Scheduled(identity = "{myservice.check.identity.expr}")}.
*
* <p>
* If the value is not given, Quarkus will generate a unique id.
* <p>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,4 @@ default ScheduledInvoker createInvoker(String invokerClassName) {
throw new IllegalStateException("Unable to create invoker: " + invokerClassName, e);
}
}

static String getConfigProperty(String val) {
return val.substring(1, val.length() - 1);
}

static boolean isConfigValue(String val) {
val = val.trim();
return val.startsWith("{") && val.endsWith("}");
}
}
Loading

0 comments on commit 5168dfb

Please sign in to comment.