From cebbf9b550b49520af691cdcc1a2ca3ceb17fb41 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 11 Feb 2021 11:32:08 +0100 Subject: [PATCH] Quartz + Scheduler: Allow to use config in Identity field. Fixes #14967 --- .../main/asciidoc/scheduler-reference.adoc | 9 ++++ .../test/DuplicateIdentityExpressionTest.java | 35 +++++++++++++++ .../MissingConfigIdentityExpressionTest.java | 33 ++++++++++++++ .../quartz/test/SimpleIdentityTest.java | 44 +++++++++++++++++++ .../quartz/runtime/QuartzScheduler.java | 7 +-- .../deployment/SchedulerProcessor.java | 2 +- .../test/DuplicateIdentityExpressionTest.java | 35 +++++++++++++++ .../MissingConfigIdentityExpressionTest.java | 33 ++++++++++++++ .../scheduler/test/PausedSchedulerTest.java | 7 ++- .../scheduler/test/SimpleIdentityTest.java | 44 +++++++++++++++++++ .../java/io/quarkus/scheduler/Scheduled.java | 4 ++ .../scheduler/runtime/SimpleScheduler.java | 24 +++++----- 12 files changed, 260 insertions(+), 17 deletions(-) create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DuplicateIdentityExpressionTest.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingConfigIdentityExpressionTest.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/SimpleIdentityTest.java create mode 100644 extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/DuplicateIdentityExpressionTest.java create mode 100644 extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/MissingConfigIdentityExpressionTest.java create mode 100644 extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/SimpleIdentityTest.java diff --git a/docs/src/main/asciidoc/scheduler-reference.adoc b/docs/src/main/asciidoc/scheduler-reference.adoc index a2d40a3c89f271..3d4bb82372c726 100644 --- a/docs/src/main/asciidoc/scheduler-reference.adoc +++ b/docs/src/main/asciidoc/scheduler-reference.adoc @@ -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. diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DuplicateIdentityExpressionTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DuplicateIdentityExpressionTest.java new file mode 100644 index 00000000000000..d2a91f2451cdc6 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DuplicateIdentityExpressionTest.java @@ -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() { + } + + } + +} diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingConfigIdentityExpressionTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingConfigIdentityExpressionTest.java new file mode 100644 index 00000000000000..9c97aeb525d0f5 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingConfigIdentityExpressionTest.java @@ -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() { + } + + } + +} diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/SimpleIdentityTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/SimpleIdentityTest.java new file mode 100644 index 00000000000000..c572869511aefc --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/SimpleIdentityTest.java @@ -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(); + } + + } + +} 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 b92e52f4ab0d06..873a7db5ff1813 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 @@ -118,7 +118,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Co int nameSequence = 0; for (Scheduled scheduled : method.getSchedules()) { - String identity = scheduled.identity().trim(); + String identity = SimpleScheduler.lookUpPropertyValue(scheduled.identity()); if (identity.isEmpty()) { identity = ++nameSequence + "_" + method.getInvokerClassName(); } @@ -136,11 +136,8 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Co .requestRecovery(); ScheduleBuilder scheduleBuilder; - String cron = scheduled.cron().trim(); + String cron = SimpleScheduler.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); diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java index c3bece68081c23..3c4afd0ffdef9f 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java @@ -365,7 +365,7 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu AnnotationValue identityValue = schedule.value("identity"); if (identityValue != null) { - String identity = identityValue.asString().trim(); + String identity = SimpleScheduler.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", diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/DuplicateIdentityExpressionTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/DuplicateIdentityExpressionTest.java new file mode 100644 index 00000000000000..02304039ba5d7c --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/DuplicateIdentityExpressionTest.java @@ -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() { + } + + } + +} diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/MissingConfigIdentityExpressionTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/MissingConfigIdentityExpressionTest.java new file mode 100644 index 00000000000000..c11bf8ce1e2e2a --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/MissingConfigIdentityExpressionTest.java @@ -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() { + } + + } + +} diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/PausedSchedulerTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/PausedSchedulerTest.java index 0a2fd629bdb3d9..f0304b76950f48 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/PausedSchedulerTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/PausedSchedulerTest.java @@ -4,6 +4,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; @@ -33,15 +34,19 @@ public class PausedSchedulerTest { public void testSchedulerPauseMethod() throws InterruptedException { scheduler.pause(); assertFalse(scheduler.isRunning()); + Jobs.IS_WATCHING.set(true); assertFalse(Jobs.LATCH.await(3, TimeUnit.SECONDS)); } static class Jobs { + static final AtomicBoolean IS_WATCHING = new AtomicBoolean(false); static final CountDownLatch LATCH = new CountDownLatch(2); @Scheduled(every = "1s") void countDownSecond() { - LATCH.countDown(); + if (IS_WATCHING.get()) { + LATCH.countDown(); + } } } diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/SimpleIdentityTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/SimpleIdentityTest.java new file mode 100644 index 00000000000000..ddea8fe6a8ab11 --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/SimpleIdentityTest.java @@ -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(); + } + + } + +} diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/Scheduled.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/Scheduled.java index 9cf0f03661520a..1bfff45c53732c 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/Scheduled.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/Scheduled.java @@ -43,6 +43,10 @@ /** * Optionally defines a unique identifier for this job. *

+ * If the value starts with "{" and ends with "}" the scheduler attempts to find a corresponding config property + * and use the configured value instead: {@code @Scheduled(identity = "{myservice.check.identity.expr}")}. + * + *

* If the value is not given, Quarkus will generate a unique id. *

* diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java index d80c32208f48fd..89ff7004ddf821 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java @@ -154,7 +154,7 @@ public boolean isRunning() { } SimpleTrigger createTrigger(String invokerClass, CronParser parser, Scheduled scheduled, int nameSequence, Config config) { - String id = scheduled.identity().trim(); + String id = lookUpPropertyValue(scheduled.identity()); if (id.isEmpty()) { id = nameSequence + "_" + invokerClass; } @@ -169,11 +169,8 @@ SimpleTrigger createTrigger(String invokerClass, CronParser parser, Scheduled sc start = start.toInstant().plusMillis(millisToAdd).atZone(start.getZone()); } - String cron = scheduled.cron().trim(); + String cron = lookUpPropertyValue(scheduled.cron()); if (!cron.isEmpty()) { - if (SchedulerContext.isConfigValue(cron)) { - cron = config.getValue(SchedulerContext.getConfigProperty(cron), String.class); - } Cron cronExpr; try { cronExpr = parser.parse(cron); @@ -190,11 +187,7 @@ SimpleTrigger createTrigger(String invokerClass, CronParser parser, Scheduled sc // Keep it public so that we can reuse the logic in the quartz extension public static Duration parseDuration(Scheduled scheduled, String value, String memberName) { - value = value.trim(); - if (SchedulerContext.isConfigValue(value)) { - value = ConfigProviderResolver.instance().getConfig().getValue(SchedulerContext.getConfigProperty(value), - String.class); - } + value = lookUpPropertyValue(value); if (Character.isDigit(value.charAt(0))) { value = "PT" + value; } @@ -206,6 +199,17 @@ public static Duration parseDuration(Scheduled scheduled, String value, String m } } + // Keep it public so that we can reuse the logic in the quartz extension + public static String lookUpPropertyValue(String propertyValue) { + String value = propertyValue.trim(); + if (!value.isEmpty() && SchedulerContext.isConfigValue(value)) { + value = ConfigProviderResolver.instance().getConfig().getValue(SchedulerContext.getConfigProperty(value), + String.class); + } + + return value; + } + static class ScheduledTask { final SimpleTrigger trigger;