Skip to content

Commit 57e759c

Browse files
committed
feat(#93): TriggerEvaluator#beforeRun
1 parent 5253452 commit 57e759c

File tree

10 files changed

+231
-85
lines changed

10 files changed

+231
-85
lines changed

core/src/main/java/io/github/zero88/schedulerx/SchedulerBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.jetbrains.annotations.NotNull;
44

55
import io.github.zero88.schedulerx.trigger.Trigger;
6+
import io.github.zero88.schedulerx.trigger.TriggerEvaluator;
67
import io.vertx.core.Vertx;
78

89
/**
@@ -21,10 +22,14 @@ public interface SchedulerBuilder<IN, OUT, TRIGGER extends Trigger, SCHEDULER ex
2122
SELF extends SchedulerBuilder<IN, OUT, TRIGGER, SCHEDULER, SELF>>
2223
extends JobExecutorContext<IN, OUT>, SchedulerContext<TRIGGER, OUT> {
2324

25+
@NotNull TriggerEvaluator triggerEvaluator();
26+
2427
@NotNull SELF setVertx(@NotNull Vertx vertx);
2528

2629
@NotNull SELF setTrigger(@NotNull TRIGGER trigger);
2730

31+
@NotNull SELF setTriggerEvaluator(@NotNull TriggerEvaluator evaluator);
32+
2833
@NotNull SELF setMonitor(@NotNull SchedulingMonitor<OUT> monitor);
2934

3035
@NotNull SELF setJob(@NotNull Job<IN, OUT> job);

core/src/main/java/io/github/zero88/schedulerx/impl/AbstractScheduler.java

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import io.github.zero88.schedulerx.trigger.TriggerCondition.ReasonCode;
3131
import io.github.zero88.schedulerx.trigger.TriggerCondition.TriggerStatus;
3232
import io.github.zero88.schedulerx.trigger.TriggerContext;
33+
import io.github.zero88.schedulerx.trigger.TriggerEvaluator;
34+
import io.github.zero88.schedulerx.trigger.rule.TriggerRule;
3335
import io.vertx.core.Future;
3436
import io.vertx.core.Promise;
3537
import io.vertx.core.Vertx;
@@ -56,21 +58,23 @@ public abstract class AbstractScheduler<IN, OUT, T extends Trigger> implements S
5658
private final @NotNull JobData<IN> jobData;
5759
private final @NotNull Job<IN, OUT> job;
5860
private final @NotNull T trigger;
61+
private final @NotNull TriggerEvaluator evaluator;
5962
private final @NotNull TimeoutPolicy timeoutPolicy;
6063
private final Lock lock = new ReentrantLock();
6164
private boolean didStart = false;
6265
private boolean didTriggerValidation = false;
6366
private IllegalArgumentException invalidTrigger;
6467

65-
protected AbstractScheduler(@NotNull Vertx vertx, @NotNull SchedulingMonitor<OUT> monitor,
66-
@NotNull JobData<IN> jobData, @NotNull Job<IN, OUT> job, @NotNull T trigger,
67-
@NotNull TimeoutPolicy timeoutPolicy) {
68+
protected AbstractScheduler(@NotNull Job<IN, OUT> job, @NotNull JobData<IN> jobData,
69+
@NotNull TimeoutPolicy timeoutPolicy, @NotNull SchedulingMonitor<OUT> monitor,
70+
@NotNull T trigger, @NotNull TriggerEvaluator evaluator, @NotNull Vertx vertx) {
6871
this.job = job;
6972
this.jobData = jobData;
7073
this.timeoutPolicy = timeoutPolicy;
7174
this.vertx = vertx;
7275
this.trigger = trigger;
7376
this.monitor = monitor;
77+
this.evaluator = new InternalTriggerEvaluator(this).andThen(evaluator);
7478
this.state = new SchedulerStateImpl<>();
7579
}
7680

@@ -170,23 +174,6 @@ protected final void doStop(long timerId, TriggerContext context) {
170174
*/
171175
protected abstract void unregisterTimer(long timerId);
172176

173-
/**
174-
* Check a trigger kickoff context whether to be able to run new execution or not
175-
*/
176-
protected final TriggerContext shouldRun(@NotNull TriggerContext kickOffContext) {
177-
log(Instant.now(), "On evaluate");
178-
if (state.pending()) {
179-
return TriggerContextFactory.skip(kickOffContext, ReasonCode.NOT_YET_SCHEDULED);
180-
}
181-
if (state.completed()) {
182-
return TriggerContextFactory.skip(kickOffContext, ReasonCode.ALREADY_STOPPED);
183-
}
184-
if (state.executing()) {
185-
return TriggerContextFactory.skip(kickOffContext, ReasonCode.JOB_IS_RUNNING);
186-
}
187-
return evaluateTriggerRule(kickOffContext);
188-
}
189-
190177
/**
191178
* Check a trigger context whether to be able to stop by configuration or force stop
192179
*/
@@ -199,23 +186,6 @@ protected final TriggerContext shouldStop(@NotNull TriggerContext triggerContext
199186
: triggerContext;
200187
}
201188

202-
/**
203-
* Evaluate a trigger kickoff context on trigger rule
204-
*/
205-
protected TriggerContext evaluateTriggerRule(@NotNull TriggerContext triggerContext) {
206-
if (!triggerContext.isKickoff()) {
207-
throw new IllegalStateException("Trigger condition status must be " + TriggerStatus.KICKOFF);
208-
}
209-
final Instant firedAt = Objects.requireNonNull(triggerContext.firedAt(),
210-
"Kickoff context is missing a fired at time");
211-
if (trigger().rule().isExceeded(firedAt)) {
212-
return TriggerContextFactory.stop(triggerContext, ReasonCode.STOP_BY_CONFIG);
213-
}
214-
return trigger().shouldExecute(firedAt)
215-
? TriggerContextFactory.ready(triggerContext)
216-
: TriggerContextFactory.skip(triggerContext, ReasonCode.CONDITION_IS_NOT_MATCHED);
217-
}
218-
219189
/**
220190
* Register a timer id in internal state and increase tick time when the system timer fires
221191
*
@@ -232,9 +202,9 @@ protected final long onFire(long timerId) {
232202
protected final void onProcess(WorkerExecutor workerExecutor, TriggerContext ctx) {
233203
log(Objects.requireNonNull(ctx.firedAt()), "On fire");
234204
final Duration timeout = timeoutPolicy().evaluationTimeout();
235-
this.<TriggerContext>executeBlocking(workerExecutor,
236-
p -> wrapTimeout(timeoutPolicy().evaluationTimeout(), p).complete(
237-
shouldRun(ctx)))
205+
this.<TriggerContext>executeBlocking(workerExecutor, p -> this.wrapTimeout(timeout, p)
206+
.handle(evaluator.beforeRun(trigger, ctx,
207+
jobData.externalId())))
238208
.onSuccess(context -> onTrigger(workerExecutor, context))
239209
.onFailure(t -> onMisfire(TriggerContextFactory.skip(ctx, t instanceof TimeoutException
240210
? ReasonCode.EVALUATION_TIMEOUT
@@ -387,4 +357,44 @@ private <R> Promise<R> wrapTimeout(Duration timeout, Promise<R> promise) {
387357
return new TimeoutBlock(vertx, timeout).wrap(promise);
388358
}
389359

360+
@SuppressWarnings("rawtypes")
361+
private static class InternalTriggerEvaluator extends AbstractTriggerEvaluator {
362+
363+
private final AbstractScheduler scheduler;
364+
365+
private InternalTriggerEvaluator(AbstractScheduler scheduler) { this.scheduler = scheduler; }
366+
367+
@Override
368+
protected Future<TriggerContext> internalCheck(@NotNull Trigger trigger, @NotNull TriggerContext ctx,
369+
@Nullable Object externalId) {
370+
if (!ctx.isKickoff()) {
371+
throw new IllegalStateException("Trigger condition status must be " + TriggerStatus.KICKOFF);
372+
}
373+
return Future.succeededFuture(doCheck(ctx));
374+
}
375+
376+
@NotNull
377+
private TriggerContext doCheck(TriggerContext ctx) {
378+
scheduler.log(Instant.now(), "On evaluate");
379+
if (scheduler.state.pending()) {
380+
return TriggerContextFactory.skip(ctx, ReasonCode.NOT_YET_SCHEDULED);
381+
}
382+
if (scheduler.state.completed()) {
383+
return TriggerContextFactory.skip(ctx, ReasonCode.ALREADY_STOPPED);
384+
}
385+
if (scheduler.state.executing()) {
386+
return TriggerContextFactory.skip(ctx, ReasonCode.JOB_IS_RUNNING);
387+
}
388+
final Instant firedAt = Objects.requireNonNull(ctx.firedAt());
389+
final TriggerRule rule = scheduler.trigger().rule();
390+
if (rule.isExceeded(firedAt)) {
391+
return TriggerContextFactory.stop(ctx, ReasonCode.STOP_BY_CONFIG);
392+
}
393+
return rule.satisfy(firedAt)
394+
? TriggerContextFactory.ready(ctx)
395+
: TriggerContextFactory.skip(ctx, ReasonCode.CONDITION_IS_NOT_MATCHED);
396+
}
397+
398+
}
399+
390400
}

core/src/main/java/io/github/zero88/schedulerx/impl/AbstractSchedulerBuilder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.github.zero88.schedulerx.SchedulingMonitor;
1515
import io.github.zero88.schedulerx.TimeoutPolicy;
1616
import io.github.zero88.schedulerx.trigger.Trigger;
17+
import io.github.zero88.schedulerx.trigger.TriggerEvaluator;
1718
import io.vertx.core.Vertx;
1819

1920
/**
@@ -30,6 +31,7 @@ public abstract class AbstractSchedulerBuilder<IN, OUT, T extends Trigger, S ext
3031
private JobData<IN> jobData;
3132
private Job<IN, OUT> job;
3233
private T trigger;
34+
private TriggerEvaluator evaluator;
3335
private TimeoutPolicy timeoutPolicy;
3436

3537
@Override
@@ -45,6 +47,11 @@ public abstract class AbstractSchedulerBuilder<IN, OUT, T extends Trigger, S ext
4547
@Override
4648
public @NotNull T trigger() { return Objects.requireNonNull(trigger, "Trigger is required"); }
4749

50+
@Override
51+
public @NotNull TriggerEvaluator triggerEvaluator() {
52+
return Optional.ofNullable(evaluator).orElseGet(AbstractTriggerEvaluator::noop);
53+
}
54+
4855
@Override
4956
public @NotNull Job<IN, OUT> job() { return Objects.requireNonNull(job, "Job is required"); }
5057

@@ -71,6 +78,12 @@ public abstract class AbstractSchedulerBuilder<IN, OUT, T extends Trigger, S ext
7178
return (B) this;
7279
}
7380

81+
@Override
82+
public @NotNull B setTriggerEvaluator(@NotNull TriggerEvaluator evaluator) {
83+
this.evaluator = evaluator;
84+
return (B) this;
85+
}
86+
7487
public @NotNull B setMonitor(@NotNull SchedulingMonitor<OUT> monitor) {
7588
this.monitor = monitor;
7689
return (B) this;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.github.zero88.schedulerx.impl;
2+
3+
import org.jetbrains.annotations.ApiStatus.Internal;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import io.github.zero88.schedulerx.trigger.Trigger;
8+
import io.github.zero88.schedulerx.trigger.TriggerContext;
9+
import io.github.zero88.schedulerx.trigger.TriggerEvaluator;
10+
import io.vertx.core.Future;
11+
12+
@Internal
13+
public abstract class AbstractTriggerEvaluator implements TriggerEvaluator {
14+
15+
static TriggerEvaluator noop() {
16+
return new AbstractTriggerEvaluator() {
17+
@Override
18+
protected Future<TriggerContext> internalCheck(@NotNull Trigger trigger,
19+
@NotNull TriggerContext triggerContext,
20+
@Nullable Object externalId) {
21+
return Future.succeededFuture(triggerContext);
22+
}
23+
};
24+
}
25+
26+
private TriggerEvaluator next;
27+
28+
@Override
29+
public @NotNull Future<TriggerContext> beforeRun(@NotNull Trigger trigger, @NotNull TriggerContext triggerContext,
30+
@Nullable Object externalId) {
31+
return this.internalCheck(trigger, triggerContext, externalId)
32+
.flatMap(ctx -> next == null ? Future.succeededFuture(ctx) : next.beforeRun(trigger, ctx, externalId));
33+
}
34+
35+
@Override
36+
public @NotNull TriggerEvaluator andThen(@Nullable TriggerEvaluator another) {
37+
this.next = another;
38+
return this;
39+
}
40+
41+
protected abstract Future<TriggerContext> internalCheck(@NotNull Trigger trigger,
42+
@NotNull TriggerContext triggerContext,
43+
@Nullable Object externalId);
44+
45+
}

core/src/main/java/io/github/zero88/schedulerx/trigger/CronSchedulerImpl.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ final class CronSchedulerImpl<IN, OUT> extends AbstractScheduler<IN, OUT, CronTr
2323

2424
private long nextTimerId;
2525

26-
CronSchedulerImpl(@NotNull Vertx vertx, @NotNull SchedulingMonitor<OUT> monitor, @NotNull JobData<IN> jobData,
27-
@NotNull Job<IN, OUT> job, @NotNull CronTrigger trigger, @NotNull TimeoutPolicy timeoutPolicy) {
28-
super(vertx, monitor, jobData, job, trigger, timeoutPolicy);
26+
CronSchedulerImpl(@NotNull Job<IN, OUT> job, @NotNull JobData<IN> jobData, @NotNull TimeoutPolicy timeoutPolicy,
27+
@NotNull SchedulingMonitor<OUT> monitor, @NotNull CronTrigger trigger,
28+
@NotNull TriggerEvaluator evaluator, @NotNull Vertx vertx) {
29+
super(job, jobData, timeoutPolicy, monitor, trigger, evaluator, vertx);
2930
}
3031

3132
@Override
@@ -56,7 +57,8 @@ static final class CronSchedulerBuilderImpl<IN, OUT>
5657
implements CronSchedulerBuilder<IN, OUT> {
5758

5859
public @NotNull CronScheduler<IN, OUT> build() {
59-
return new CronSchedulerImpl<>(vertx(), monitor(), jobData(), job(), trigger(), timeoutPolicy());
60+
return new CronSchedulerImpl<>(job(), jobData(), timeoutPolicy(), monitor(), trigger(), triggerEvaluator(),
61+
vertx());
6062
}
6163

6264
}

core/src/main/java/io/github/zero88/schedulerx/trigger/EventSchedulerImpl.java

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
import java.util.Objects;
77

88
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
910

1011
import io.github.zero88.schedulerx.Job;
1112
import io.github.zero88.schedulerx.JobData;
1213
import io.github.zero88.schedulerx.SchedulingMonitor;
1314
import io.github.zero88.schedulerx.TimeoutPolicy;
1415
import io.github.zero88.schedulerx.impl.AbstractScheduler;
1516
import io.github.zero88.schedulerx.impl.AbstractSchedulerBuilder;
17+
import io.github.zero88.schedulerx.impl.AbstractTriggerEvaluator;
1618
import io.github.zero88.schedulerx.impl.TriggerContextFactory;
1719
import io.github.zero88.schedulerx.trigger.TriggerCondition.ReasonCode;
1820
import io.github.zero88.schedulerx.trigger.predicate.EventTriggerPredicate.EventTriggerPredicateException;
@@ -28,10 +30,10 @@ final class EventSchedulerImpl<IN, OUT, T> extends AbstractScheduler<IN, OUT, Ev
2830

2931
private MessageConsumer<Object> consumer;
3032

31-
EventSchedulerImpl(@NotNull Vertx vertx, @NotNull SchedulingMonitor<OUT> monitor, @NotNull JobData<IN> jobData,
32-
@NotNull Job<IN, OUT> job, @NotNull EventTrigger<T> trigger,
33-
@NotNull TimeoutPolicy timeoutPolicy) {
34-
super(vertx, monitor, jobData, job, trigger, timeoutPolicy);
33+
EventSchedulerImpl(@NotNull Job<IN, OUT> job, @NotNull JobData<IN> jobData, @NotNull TimeoutPolicy timeoutPolicy,
34+
@NotNull SchedulingMonitor<OUT> monitor, @NotNull EventTrigger<T> trigger,
35+
@NotNull TriggerEvaluator evaluator, @NotNull Vertx vertx) {
36+
super(job, jobData, timeoutPolicy, monitor, trigger, new EventTriggerEvaluator<>().andThen(evaluator), vertx);
3537
}
3638

3739
@Override
@@ -65,21 +67,6 @@ protected void unregisterTimer(long timerId) {
6567
}
6668
}
6769

68-
@Override
69-
@SuppressWarnings("unchecked")
70-
protected TriggerContext evaluateTriggerRule(@NotNull TriggerContext triggerContext) {
71-
final TriggerContext ctx = super.evaluateTriggerRule(triggerContext);
72-
try {
73-
if (ctx.condition().status() == TriggerCondition.TriggerStatus.READY &&
74-
!trigger().getPredicate().test((T) triggerContext.info())) {
75-
return TriggerContextFactory.skip(ctx, ReasonCode.CONDITION_IS_NOT_MATCHED);
76-
}
77-
} catch (Exception ex) {
78-
return handleException(ctx, ex);
79-
}
80-
return ctx;
81-
}
82-
8370
private TriggerContext createKickoffContext(Message<Object> msg, long tick) {
8471
try {
8572
T eventMsg = trigger().getPredicate().convert(msg.headers(), msg.body());
@@ -89,7 +76,7 @@ private TriggerContext createKickoffContext(Message<Object> msg, long tick) {
8976
}
9077
}
9178

92-
private TriggerContext handleException(TriggerContext context, Exception cause) {
79+
static TriggerContext handleException(TriggerContext context, Exception cause) {
9380
String reason = cause instanceof ClassCastException || cause instanceof EventTriggerPredicateException
9481
? ReasonCode.CONDITION_IS_NOT_MATCHED
9582
: ReasonCode.UNEXPECTED_ERROR;
@@ -103,7 +90,28 @@ static final class EventSchedulerBuilderImpl<IN, OUT, T>
10390
// @formatter:on
10491

10592
public @NotNull EventScheduler<IN, OUT, T> build() {
106-
return new EventSchedulerImpl<>(vertx(), monitor(), jobData(), job(), trigger(), timeoutPolicy());
93+
return new EventSchedulerImpl<>(job(), jobData(), timeoutPolicy(), monitor(), trigger(), triggerEvaluator(),
94+
vertx());
95+
}
96+
97+
}
98+
99+
100+
static final class EventTriggerEvaluator<T> extends AbstractTriggerEvaluator {
101+
102+
@Override
103+
@SuppressWarnings("unchecked")
104+
protected Future<TriggerContext> internalCheck(@NotNull Trigger trigger, @NotNull TriggerContext ctx,
105+
@Nullable Object externalId) {
106+
try {
107+
if (ctx.condition().status() == TriggerCondition.TriggerStatus.READY &&
108+
!((EventTrigger<T>) trigger).getPredicate().test((T) ctx.info())) {
109+
return Future.succeededFuture(TriggerContextFactory.skip(ctx, ReasonCode.CONDITION_IS_NOT_MATCHED));
110+
}
111+
} catch (Exception ex) {
112+
return Future.succeededFuture(handleException(ctx, ex));
113+
}
114+
return Future.succeededFuture(ctx);
107115
}
108116

109117
}

0 commit comments

Comments
 (0)