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 bb806b18e1f69..7c78c6fa341e5 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 @@ -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; @@ -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); @@ -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); 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 d7123be59bacb..275b73c432fc5 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 @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.OptionalLong; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; @@ -82,12 +83,15 @@ public void run() { int nameSequence = 0; for (Scheduled scheduled : method.getSchedules()) { nameSequence++; - SimpleTrigger trigger = createTrigger(method.getInvokerClassName(), parser, scheduled, nameSequence); - ScheduledInvoker invoker = context.createInvoker(method.getInvokerClassName()); - if (scheduled.concurrentExecution() == ConcurrentExecution.SKIP) { - invoker = new SkipConcurrentExecutionInvoker(invoker, skippedExecutionEvent); + Optional trigger = createTrigger(method.getInvokerClassName(), parser, scheduled, + nameSequence); + if (trigger.isPresent()) { + ScheduledInvoker invoker = context.createInvoker(method.getInvokerClassName()); + if (scheduled.concurrentExecution() == ConcurrentExecution.SKIP) { + invoker = new SkipConcurrentExecutionInvoker(invoker, skippedExecutionEvent); + } + scheduledTasks.add(new ScheduledTask(trigger.get(), invoker)); } - scheduledTasks.add(new ScheduledTask(trigger, invoker)); } } } @@ -151,7 +155,7 @@ public boolean isRunning() { return enabled && running; } - SimpleTrigger createTrigger(String invokerClass, CronParser parser, Scheduled scheduled, int nameSequence) { + Optional createTrigger(String invokerClass, CronParser parser, Scheduled scheduled, int nameSequence) { String id = SchedulerUtils.lookUpPropertyValue(scheduled.identity()); if (id.isEmpty()) { id = nameSequence + "_" + invokerClass; @@ -169,15 +173,22 @@ SimpleTrigger createTrigger(String invokerClass, CronParser parser, Scheduled sc String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron()); if (!cron.isEmpty()) { + if (SchedulerUtils.isOff(cron)) { + return Optional.empty(); + } Cron cronExpr; try { cronExpr = parser.parse(cron); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Cannot parse cron expression: " + cron, e); } - return new CronTrigger(id, start, cronExpr); + return Optional.of(new CronTrigger(id, start, cronExpr)); } else if (!scheduled.every().isEmpty()) { - return new IntervalTrigger(id, start, SchedulerUtils.parseEveryAsMillis(scheduled)); + final OptionalLong everyMillis = SchedulerUtils.parseEveryAsMillis(scheduled); + if (!everyMillis.isPresent()) { + return Optional.empty(); + } + return Optional.of(new IntervalTrigger(id, start, everyMillis.getAsLong())); } else { throw new IllegalArgumentException("Invalid schedule configuration: " + scheduled); } diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/util/SchedulerUtils.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/util/SchedulerUtils.java index ab954b47a378f..9eafc1d0830f9 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/util/SchedulerUtils.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/util/SchedulerUtils.java @@ -1,10 +1,18 @@ package io.quarkus.scheduler.runtime.util; +import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX; +import static io.smallrye.common.expression.Expression.Flag.NO_TRIM; + import java.time.Duration; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.OptionalLong; +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import io.quarkus.scheduler.Scheduled; +import io.smallrye.common.expression.Expression; /** * Utilities class for scheduler extensions. @@ -26,17 +34,28 @@ private SchedulerUtils() { * @return returns the duration in milliseconds. */ public static long parseDelayedAsMillis(Scheduled scheduled) { - return parseDurationAsMillis(scheduled, scheduled.delayed(), DELAYED); + String value = lookUpPropertyValue(scheduled.delayed()); + return parseDurationAsMillis(scheduled, value, DELAYED); } /** * Parse the `@Scheduled(every = "")` field into milliseconds. * * @param scheduled annotation - * @return returns the duration in milliseconds. + * @return returns the duration in milliseconds or {@link OptionalLong#empty()} if the expression evaluates to "off" or + * "disabled". */ - public static long parseEveryAsMillis(Scheduled scheduled) { - return parseDurationAsMillis(scheduled, scheduled.every(), EVERY); + public static OptionalLong parseEveryAsMillis(Scheduled scheduled) { + String value = lookUpPropertyValue(scheduled.every()); + OptionalLong optionalMillis = OptionalLong.empty(); + if (!isOff(value)) { + optionalMillis = OptionalLong.of(parseDurationAsMillis(scheduled, value, EVERY)); + } + return optionalMillis; + } + + public static boolean isOff(String value) { + return value != null && (value.equalsIgnoreCase("off") || value.equalsIgnoreCase("disabled")); } /** @@ -47,28 +66,63 @@ public static long parseEveryAsMillis(Scheduled scheduled) { */ public static String lookUpPropertyValue(String propertyValue) { String value = propertyValue.trim(); - if (!value.isEmpty() && isConfigValue(value)) { - value = ConfigProviderResolver.instance().getConfig().getValue(getConfigProperty(value), String.class); + if (!value.isEmpty()) { + // for backwards compatibility + if (isSimpleConfigValue(value)) { + value = ConfigProviderResolver.instance().getConfig().getValue(unwrapSimpleConfigProperty(value), String.class); + } else { + value = resolvePropertyExpression(value); + } } - return value; } public static boolean isConfigValue(String val) { + return isSimpleConfigValue(val) || isConfigExpression(val); + } + + private static boolean isSimpleConfigValue(String val) { val = val.trim(); return val.startsWith("{") && val.endsWith("}"); } - private static String getConfigProperty(String val) { + private static String unwrapSimpleConfigProperty(String val) { return val.substring(1, val.length() - 1); } + /** + * Adapted from {@link io.smallrye.config.ExpressionConfigSourceInterceptor} + */ + private static String resolvePropertyExpression(String expr) { + final Config config = ConfigProviderResolver.instance().getConfig(); + final Expression expression = compileExpression(expr); + final String expanded = expression.evaluate((resolveContext, stringBuilder) -> { + final Optional resolve = config.getOptionalValue(resolveContext.getKey(), String.class); + if (resolve.isPresent()) { + stringBuilder.append(resolve.get()); + } else if (resolveContext.hasDefault()) { + resolveContext.expandDefault(); + } else { + throw new NoSuchElementException(String.format("Could not expand value %s in property %s", + resolveContext.getKey(), expr)); + } + }); + return expanded; + } + + private static Expression compileExpression(String expr) { + return Expression.compile(expr, LENIENT_SYNTAX, NO_TRIM); + } + + private static boolean isConfigExpression(String val) { + return val != null && compileExpression(val).getReferencedStrings().size() > 0; + } + private static long parseDurationAsMillis(Scheduled scheduled, String value, String memberName) { return Math.abs(parseDuration(scheduled, value, memberName).toMillis()); } private static Duration parseDuration(Scheduled scheduled, String value, String memberName) { - value = lookUpPropertyValue(value); if (Character.isDigit(value.charAt(0))) { value = "PT" + value; }