Skip to content

Commit

Permalink
Merge pull request #16073 from renegrob/schedule-default-values
Browse files Browse the repository at this point in the history
@scheduled - add Support for Default Value and for Switching Timer Off
  • Loading branch information
mkouba authored Apr 7, 2021
2 parents 2ab99f6 + adad98f commit 41cbf43
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 27 deletions.
54 changes: 45 additions & 9 deletions docs/src/main/asciidoc/scheduler-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,36 @@ The syntax used in CRON expressions is controlled by the `quarkus.scheduler.cron
The values can be `cron4j`, `quartz`, `unix` and `spring`.
`quartz` is used by default.

If a CRON expression starts with `{` and ends with `}` then the scheduler attempts to find a corresponding config property and use the configured value instead.
The `cron` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
Property Expressions. (Note that "{property.path}" style expressions are still supported but don't offer the full functionality of Property Expressions.)


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

If you wish to disable a specific scheduled method, you can set its cron expression to `"off"` or `"disabled"`.

.application.properties
[source,properties]
----
myMethod.cron.expr=disabled
----

Property Expressions allow you to define a default value that is used, if the property is not configured.

.CRON Config Property Example with default `0 0 15 ? * MON *`
[source,java]
----
@Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
void myMethod() { }
----

If the property `myMethod.cron.expr` is undefined or `null`, the default value (`0 0 15 ? * MON *`) will be used.

==== Intervals

An interval trigger defines a period between invocations.
Expand All @@ -77,15 +98,27 @@ So for example, `15m` can be used instead of `PT15M` and is parsed as "15 minute
void every15Mins() { }
----

If a value starts with `{` and ends with `}` then the scheduler attempts to find a corresponding config property and use the configured value instead.
The `every` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don't offer the full functionality of Property Expressions.)

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

Intervals can be disabled by setting their value to `"off"` or `"disabled"`.
So for example a Property Expression with the default value `"off"` can be used to disable the trigger if its Config Property has not been set.

.Interval Config Property Example with a Default Value
[source,java]
----
@Scheduled(every = "${myMethod.every.expr:off}")
void myMethod() { }
----


=== Identity

By default, a unique id is generated for each scheduled method.
Expand All @@ -99,12 +132,13 @@ 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.
The `identity` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don't offer the full functionality of Property Expressions.)

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

Expand Down Expand Up @@ -134,14 +168,16 @@ So for example, `15s` can be used instead of `PT15S` and is parsed as "15 second
void everyTwoSeconds() { }
----

NOTE: If `@Scheduled#delay()` is set to a value greater then zero the value of `@Scheduled#delayed()` is ignored.
NOTE: If `@Scheduled#delay()` is set to a value greater than zero the value of `@Scheduled#delayed()` is ignored.

The main advantage over `@Scheduled#delay()` is that the value is configurable.
If the value starts with `{` and ends with `}` then the scheduler attempts to find a corresponding config property and use the configured value instead:
The `delay` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don't offer the full functionality of Property Expressions.)


[source,java]
----
@Scheduled(every = "2s", delayed = "{myMethod.delay.expr}") <1>
@Scheduled(every = "2s", delayed = "${myMethod.delay.expr}") <1>
void everyTwoSeconds() { }
----
<1> The config property `myMethod.delay.expr` is used to set the delay.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.quarkus.quartz.test;

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

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 DisabledScheduledMethodTest {

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

@Test
public void testNoSchedulerInvocations() throws InterruptedException {
Thread.sleep(100);
Jobs.LATCH.await(500, TimeUnit.MILLISECONDS);
assertEquals(0, Jobs.executionCounter);
}

static class Jobs {

static final CountDownLatch LATCH = new CountDownLatch(1);

static volatile int executionCounter = 0;

@Scheduled(every = "{DisabledScheduledMethodTest.interval}")
void disabledByConfigValue() {
executionCounter++;
}

@Scheduled(every = "{non.existent.property:disabled}")
void disabledByDefault() {
executionCounter++;
}

@Scheduled(every = "0.001s")
void enabled() {
LATCH.countDown();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.quartz.test;

import static org.junit.jupiter.api.Assertions.assertNotNull;
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.scheduler.ScheduledExecution;
import io.quarkus.test.QuarkusUnitTest;

public class PropertyDefaultValueSchedulerTest {

private static final String EXPECTED_IDENTITY = "TestIdentity";

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Jobs.class)
.addAsResource(new StringAsset("PropertyDefaultValueSchedulerTest.default=" + EXPECTED_IDENTITY),
"application.properties"));

@Test
public void testDefaultIdentity() throws InterruptedException {
assertTrue(Jobs.LATCH.await(500, TimeUnit.MILLISECONDS), "Scheduler was not triggered");
assertNotNull(Jobs.execution);
final String actualIdentity = Jobs.execution.getTrigger().getId();
assertTrue(actualIdentity.contains(EXPECTED_IDENTITY),
"<" + actualIdentity + "> did not contain: " + EXPECTED_IDENTITY);
}

static class Jobs {

static final CountDownLatch LATCH = new CountDownLatch(1);

static ScheduledExecution execution;

@Scheduled(every = "0.001s", identity = "{nonexistent:${PropertyDefaultValueSchedulerTest.default}}")
void trigger(ScheduledExecution execution) {
if (this.execution == null) {
this.execution = execution;
}
LATCH.countDown();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Properties;

import javax.annotation.PreDestroy;
Expand Down Expand Up @@ -136,6 +137,9 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc

String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron());
if (!cron.isEmpty()) {
if (SchedulerUtils.isOff(cron)) {
continue;
}
if (!CronType.QUARTZ.equals(cronType)) {
// Migrate the expression
Cron cronExpr = parser.parse(cron);
Expand All @@ -152,8 +156,12 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
}
scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
} else if (!scheduled.every().isEmpty()) {
OptionalLong everyMillis = SchedulerUtils.parseEveryAsMillis(scheduled);
if (!everyMillis.isPresent()) {
continue;
}
scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(SchedulerUtils.parseEveryAsMillis(scheduled))
.withIntervalInMilliseconds(everyMillis.getAsLong())
.repeatForever();
} else {
throw new IllegalArgumentException("Invalid schedule configuration: " + scheduled);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.quarkus.scheduler.test;

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

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 DisabledScheduledMethodTest {

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

@Test
public void testNoSchedulerInvocations() throws InterruptedException {
Thread.sleep(100);
Jobs.LATCH.await(500, TimeUnit.MILLISECONDS);
assertEquals(0, Jobs.executionCounter);
}

static class Jobs {

static final CountDownLatch LATCH = new CountDownLatch(1);

static volatile int executionCounter = 0;

@Scheduled(every = "{DisabledScheduledMethodTest.interval}")
void disabledByConfigValue() {
executionCounter++;
}

@Scheduled(every = "{non.existent.property:disabled}")
void disabledByDefault() {
executionCounter++;
}

@Scheduled(every = "0.001s")
void enabled() {
LATCH.countDown();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkus.scheduler.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
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.scheduler.ScheduledExecution;
import io.quarkus.test.QuarkusUnitTest;

public class PropertyDefaultValueSchedulerTest {

private static final String EXPECTED_IDENTITY = "TestIdentity";

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Jobs.class)
.addAsResource(new StringAsset("PropertyDefaultValueSchedulerTest.default=" + EXPECTED_IDENTITY),
"application.properties"));

@Test
public void testDefaultIdentity() throws InterruptedException {
assertTrue(Jobs.LATCH.await(500, TimeUnit.MILLISECONDS), "Scheduler was not triggered");
assertNotNull(Jobs.execution);
assertEquals(EXPECTED_IDENTITY, Jobs.execution.getTrigger().getId());
}

static class Jobs {

static final CountDownLatch LATCH = new CountDownLatch(1);

static ScheduledExecution execution;

@Scheduled(every = "0.001s", identity = "{nonexistent:${PropertyDefaultValueSchedulerTest.default}}")
void trigger(ScheduledExecution execution) {
if (this.execution == null) {
this.execution = execution;
}
LATCH.countDown();
}
}
}
6 changes: 6 additions & 0 deletions extensions/scheduler/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
<artifactId>quarkus-vertx-http</artifactId>
<optional>true</optional>
</dependency>
<!-- TEST dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Loading

0 comments on commit 41cbf43

Please sign in to comment.