Skip to content

Commit

Permalink
Dev UI - scheduler improvements
Browse files Browse the repository at this point in the history
- add option to pause/resume individual jobs with identity
- add Scheduler#isPaused(identity)
- show the configured values in the schedules table; e.g. "{cron.expr} configured as */5 * * * * ?"
- fix links to config expressions in the docs
  • Loading branch information
mkouba committed Jul 23, 2021
1 parent d7f7573 commit 6c0873d
Show file tree
Hide file tree
Showing 17 changed files with 194 additions and 34 deletions.
8 changes: 4 additions & 4 deletions docs/src/main/asciidoc/scheduler-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ 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.

The `cron` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
The `cron` attribute supports <<config-reference#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.)


Expand Down Expand Up @@ -98,7 +98,7 @@ So for example, `15m` can be used instead of `PT15M` and is parsed as "15 minute
void every15Mins() { }
----

The `every` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
The `every` attribute supports <<config-reference#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
Expand Down Expand Up @@ -132,7 +132,7 @@ Sometimes a possibility to specify an explicit id may come in handy.
void myMethod() { }
----

The `identity` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
The `identity` attribute supports <<config-reference#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
Expand Down Expand Up @@ -171,7 +171,7 @@ void everyTwoSeconds() { }
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.
The `delay` attribute supports <<config-reference#using_property_expressions,Property Expressions>> including default values and nested
The `delay` attribute supports <<config-reference#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.)


Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.quarkus.quartz.test;

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

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

import javax.annotation.Priority;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.interceptor.Interceptor;

import org.jboss.shrinkwrap.api.ShrinkWrap;
Expand All @@ -28,9 +30,13 @@ public class PausedMethodTest {

private static final String IDENTITY = "myScheduled";

@Inject
Scheduler scheduler;

@Test
public void testPause() throws InterruptedException {
assertFalse(Jobs.LATCH.await(3, TimeUnit.SECONDS));
assertTrue(scheduler.isPaused(IDENTITY));
}

static class Jobs {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.quartz.test;

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

import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -34,8 +35,10 @@ public class PausedResumedMethodTest {

@Test
public void testPause() throws InterruptedException {
assertTrue(scheduler.isPaused(IDENTITY));
scheduler.resume(IDENTITY);
assertTrue(Jobs.LATCH.await(3, TimeUnit.SECONDS));
assertFalse(scheduler.isPaused(IDENTITY));
}

static class Jobs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalLong;
Expand Down Expand Up @@ -34,6 +35,7 @@
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.simpl.InitThreadContextClassLoadHelper;
Expand Down Expand Up @@ -254,6 +256,35 @@ public void pause(String identity) {
}
}

@Override
public boolean isPaused(String identity) {
Objects.requireNonNull(identity);
if (identity.isEmpty()) {
return false;
}
try {
List<? extends org.quartz.Trigger> triggers = scheduler
.getTriggersOfJob(new JobKey(SchedulerUtils.lookUpPropertyValue(identity), Scheduler.class.getName()));
if (triggers.isEmpty()) {
return false;
}
for (org.quartz.Trigger trigger : triggers) {
try {
if (scheduler.getTriggerState(trigger.getKey()) != TriggerState.PAUSED) {
return false;
}
} catch (SchedulerException e) {
LOGGER.warnf("Cannot obtain the trigger state for %s", trigger.getKey());
return false;
}
}
return true;
} catch (SchedulerException e1) {
LOGGER.warnf(e1, "Cannot obtain triggers for job with identity %s", identity);
return false;
}
}

@Override
public void resume() {
if (!enabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ public String apply(String name) {
schedules.add(annotationProxy.builder(scheduled, Scheduled.class).build(classOutput));
}
metadata.setSchedules(schedules);
metadata.setMethodDescription(
scheduledMethod.getMethod().declaringClass() + "#" + scheduledMethod.getMethod().name());
metadata.setDeclaringClassName(scheduledMethod.getMethod().declaringClass().toString());
metadata.setMethodName(scheduledMethod.getMethod().name());
scheduledMetadata.add(metadata);
}

Expand All @@ -251,16 +251,15 @@ public String apply(String name) {
}

@BuildStep
public void devConsoleInfo(BuildProducer<DevConsoleRuntimeTemplateInfoBuildItem> infos) {
@Record(value = STATIC_INIT, optional = true)
public DevConsoleRouteBuildItem devConsole(BuildProducer<DevConsoleRuntimeTemplateInfoBuildItem> infos,
SchedulerDevConsoleRecorder recorder) {
infos.produce(new DevConsoleRuntimeTemplateInfoBuildItem("schedulerContext",
new BeanLookupSupplier(SchedulerContext.class)));
infos.produce(new DevConsoleRuntimeTemplateInfoBuildItem("scheduler",
new BeanLookupSupplier(Scheduler.class)));
}

@BuildStep
@Record(value = STATIC_INIT, optional = true)
DevConsoleRouteBuildItem invokeEndpoint(SchedulerDevConsoleRecorder recorder) {
infos.produce(new DevConsoleRuntimeTemplateInfoBuildItem("configLookup",
recorder.getConfigLookup()));
return new DevConsoleRouteBuildItem("schedules", "POST", recorder.invokeHandler());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<a href="{urlbase}/schedules" class="badge badge-light">
<i class="fa fa-clock fa-fw"></i>
Schedules <span class="badge badge-light">{info:schedulerContext.scheduledMethods.size()}</span></a>
Scheduled Methods <span class="badge badge-light">{info:schedulerContext.scheduledMethods.size()}</span></a>
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
{#include main}
{#title}Schedules{/title}
{#include main fluid=true}
{#title}Scheduled Methods{/title}
{#style}
span.app-class {
cursor:pointer;
color:blue;
text-decoration:underline;
}
{/style}
{#script}
$(document).ready(function(){
if (!ideKnown()) {
return;
}
$(".class-candidate").each(function() {
var className = $(this).text();
if (appClassLang(className)) {
$(this).addClass("app-class");
}
});

$(".app-class").on("click", function() {
openInIDE($(this).text());
});
});
{/script}
{#body}
{#if info:scheduler.running}
<form method="post" enctype="application/x-www-form-urlencoded">
Expand Down Expand Up @@ -40,7 +64,7 @@
{/if}
</td>
<td>
{scheduledMethod.methodDescription}
<span class="class-candidate">{scheduledMethod.declaringClassName}</span>#{scheduledMethod.methodName}()
</td>
<td>
<form method="post" enctype="application/x-www-form-urlencoded">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{#set val=info:configLookup.apply(it)}{#if val == it}<code>{it}</code>{#else}<code>{it}</code> configured as <code>{val}</code>{/if}{/set}
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
{#if it.cron}
<code>{it.cron}</code>
{#configVal it.cron /}
{#else}
Every <code>{it.every}</code>
Every {#configVal it.every /}
{/if}
{#if it.identity}
with identity {#configVal it.identity /}
{#if info:scheduler.running}
{#if info:scheduler.isPaused(it.identity)}
<form method="post" enctype="application/x-www-form-urlencoded" style="float:right;">
<input type="hidden" name="action" value="resumeJob">
<input type="hidden" name="identity" value="{it.identity}">
<button type="submit" class="btn btn-primary" type="submit"><i class="far fa-play-circle"></i> </button>
</form>
{#else}
<form method="post" enctype="application/x-www-form-urlencoded" style="float:right;">
<input type="hidden" name="action" value="pauseJob">
<input type="hidden" name="identity" value="{it.identity}">
<button type="submit" class="btn btn-primary" type="submit"><i class="far fa-pause-circle"></i> </button>
</form>
{/if}
{/if}
{/if}
{#if it.delay > 0}
(with delay {it.delay} {it.delayUnit.toString.toLowerCase})
{#else if !it.delayed.empty}
(delayed for {it.delayed})
{/if}
(delayed for {#configVal it.delayed /})
{/if}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.quarkus.scheduler.test;

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

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

import javax.annotation.Priority;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.interceptor.Interceptor;

import org.jboss.shrinkwrap.api.ShrinkWrap;
Expand All @@ -28,9 +30,13 @@ public class PausedMethodTest {

private static final String IDENTITY = "myScheduled";

@Inject
Scheduler scheduler;

@Test
public void testPause() throws InterruptedException {
assertFalse(Jobs.LATCH.await(3, TimeUnit.SECONDS));
assertTrue(scheduler.isPaused(IDENTITY));
}

static class Jobs {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.scheduler.test;

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

import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -34,8 +35,10 @@ public class PausedResumedMethodTest {

@Test
public void testPause() throws InterruptedException {
assertTrue(scheduler.isPaused(IDENTITY));
scheduler.resume(IDENTITY);
assertTrue(Jobs.LATCH.await(3, TimeUnit.SECONDS));
assertFalse(scheduler.isPaused(IDENTITY));
}

static class Jobs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
/**
* The container provides a built-in bean with bean type {@link Scheduler} and qualifier
* {@link javax.enterprise.inject.Default}.
*
* @author Martin Kouba
*/
public interface Scheduler {

Expand All @@ -14,9 +12,10 @@ public interface Scheduler {
void pause();

/**
* Pause a specific trigger. Identity must not be null and non-existent identity results in no-op.
* Pause a specific job. Identity must not be null and non-existent identity results in no-op.
*
* @param identity see {@link Scheduled#identity()}
* @param identity
* @see Scheduled#identity()
*/
void pause(String identity);

Expand All @@ -26,14 +25,24 @@ public interface Scheduler {
void resume();

/**
* Resume a specific trigger. Identity must not be null and non-existent identity results in no-op.
* Resume a specific job. Identity must not be null and non-existent identity results in no-op.
*
* @param identity see {@link Scheduled#identity()}
* @param identity
* @see Scheduled#identity()
*/
void resume(String identity);

/**
* @return if a scheduler is running the triggers are fired and jobs are executed.
* Identity must not be null and {@code false} is returned for non-existent identity.
*
* @param identity
* @return {@code true} if the job with the given identity is paused, {@code false} otherwise
* @see Scheduled#identity()
*/
boolean isPaused(String identity);

/**
* @return {@code true} if a scheduler is running the triggers are fired and jobs are executed, {@code false} otherwise
*/
boolean isRunning();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
public class ScheduledMethodMetadata {

private String invokerClassName;
private String methodDescription;
private String declaringClassName;
private String methodName;
private List<Scheduled> schedules;

public String getInvokerClassName() {
Expand All @@ -19,11 +20,23 @@ public void setInvokerClassName(String invokerClassName) {
}

public String getMethodDescription() {
return methodDescription;
return declaringClassName + "#" + methodName;
}

public void setMethodDescription(String description) {
this.methodDescription = description;
public String getDeclaringClassName() {
return declaringClassName;
}

public void setDeclaringClassName(String declaringClassName) {
this.declaringClassName = declaringClassName;
}

public String getMethodName() {
return methodName;
}

public void setMethodName(String methodName) {
this.methodName = methodName;
}

public List<Scheduled> getSchedules() {
Expand Down
Loading

0 comments on commit 6c0873d

Please sign in to comment.