Skip to content

Commit d00feb7

Browse files
committed
feat(#60): Implement EventTrigger
1 parent a4380a8 commit d00feb7

33 files changed

+812
-207
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
version: ${{ needs.context.outputs.version }}
4444
semanticVersion: ${{ needs.context.outputs.semanticVersion }}
4545
hashVersion: ${{ needs.context.outputs.commitId }}
46+
buildArgs: 'clean build -x test'
4647

4748
docs:
4849
uses: zero88/shared-ghactions/.github/workflows/antora-docs.yml@main

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.jetbrains.annotations.NotNull;
66
import org.jetbrains.annotations.Nullable;
77

8+
import io.github.zero88.schedulerx.trigger.TriggerContext;
89
import io.vertx.core.Vertx;
910

1011
/**
@@ -43,6 +44,13 @@ public interface TaskExecutionContext<OUTPUT> {
4344
*/
4445
@NotNull Instant executedAt();
4546

47+
/**
48+
* Runtime trigger context
49+
*
50+
* @since 2.0.0
51+
*/
52+
@NotNull TriggerContext triggerContext();
53+
4654
/**
4755
* Check whether force stop execution or not
4856
*

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package io.github.zero88.schedulerx;
22

3-
import org.jetbrains.annotations.NotNull;
4-
5-
import io.vertx.codegen.annotations.GenIgnore;
63
import io.vertx.codegen.annotations.VertxGen;
74
import io.vertx.core.WorkerExecutor;
85

@@ -29,14 +26,6 @@ public interface TaskExecutor<INPUT, OUTPUT> extends TaskExecutorProperties<INPU
2926
*/
3027
void start(WorkerExecutor workerExecutor);
3128

32-
/**
33-
* Execute task
34-
*
35-
* @param executionContext execution context
36-
*/
37-
@GenIgnore(GenIgnore.PERMITTED_TYPE)
38-
void executeTask(@NotNull TaskExecutionContext<OUTPUT> executionContext);
39-
4029
/**
4130
* Cancel executor
4231
*/

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
import org.jetbrains.annotations.NotNull;
44

55
import io.github.zero88.schedulerx.trigger.Trigger;
6+
import io.github.zero88.schedulerx.trigger.TriggerContext;
7+
import io.vertx.codegen.annotations.GenIgnore;
68

79
/**
810
* Represents for an executor run task based on particular trigger
911
*
1012
* @param <INPUT> Type of Input Job data
1113
* @param <OUTPUT> Type of Result data
1214
* @param <TRIGGER> Type of Trigger
13-
* @see Trigger
1415
* @see TaskExecutor
16+
* @see Trigger
17+
* @see TriggerContext
1518
* @since 1.0.0
1619
*/
1720
public interface TriggerTaskExecutor<INPUT, OUTPUT, TRIGGER extends Trigger> extends TaskExecutor<INPUT, OUTPUT> {
@@ -23,4 +26,12 @@ public interface TriggerTaskExecutor<INPUT, OUTPUT, TRIGGER extends Trigger> ext
2326
*/
2427
@NotNull TRIGGER trigger();
2528

29+
/**
30+
* Execute task
31+
*
32+
* @param executionContext execution context
33+
*/
34+
@GenIgnore(GenIgnore.PERMITTED_TYPE)
35+
void executeTask(TaskExecutionContext<OUTPUT> executionContext);
36+
2637
}

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

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.concurrent.locks.Lock;
55
import java.util.concurrent.locks.ReentrantLock;
66

7+
import org.jetbrains.annotations.ApiStatus.Internal;
78
import org.jetbrains.annotations.NotNull;
89
import org.jetbrains.annotations.Nullable;
910

@@ -15,6 +16,7 @@
1516
import io.github.zero88.schedulerx.TaskResult;
1617
import io.github.zero88.schedulerx.TriggerTaskExecutor;
1718
import io.github.zero88.schedulerx.trigger.Trigger;
19+
import io.github.zero88.schedulerx.trigger.TriggerContext;
1820
import io.vertx.core.AsyncResult;
1921
import io.vertx.core.Future;
2022
import io.vertx.core.Promise;
@@ -30,6 +32,7 @@
3032
* @param <OUT> Type of output data
3133
* @param <T> Type of trigger
3234
*/
35+
@Internal
3336
public abstract class AbstractTaskExecutor<IN, OUT, T extends Trigger> implements TriggerTaskExecutor<IN, OUT, T> {
3437

3538
@SuppressWarnings("java:S3416")
@@ -112,7 +115,7 @@ public final void start(WorkerExecutor workerExecutor) {
112115
@Override
113116
public final void executeTask(@NotNull TaskExecutionContext<OUT> executionContext) {
114117
try {
115-
trace(state.tick(), state.round(), executionContext.executedAt(), "Start to execute the task");
118+
trace(executionContext.executedAt(), "Start to execute the task");
116119
task.execute(jobData(), executionContext);
117120
if (!task.isAsync()) {
118121
((TaskExecutionContextInternal<OUT>) executionContext).internalComplete();
@@ -125,59 +128,66 @@ public final void executeTask(@NotNull TaskExecutionContext<OUT> executionContex
125128
@Override
126129
public final void cancel() {
127130
if (!state.completed()) {
128-
trace(state.tick(), state.round(), Instant.now(), "Canceling the task");
131+
trace(Instant.now(), "Canceling the task");
129132
doStop(state.timerId());
130-
onCompleted();
131133
}
132134
}
133135

134-
protected abstract @NotNull Future<Long> addTimer(@NotNull Promise<Long> promise, WorkerExecutor workerExecutor);
136+
protected final void doStart(WorkerExecutor workerExecutor) {
137+
this.registerTimer(Promise.promise(), workerExecutor)
138+
.onSuccess(this::onSchedule)
139+
.onFailure(this::onUnableSchedule);
140+
}
141+
142+
protected final void doStop(long timerId) {
143+
unregisterTimer(timerId);
144+
onCompleted();
145+
}
146+
147+
protected abstract @NotNull Future<Long> registerTimer(@NotNull Promise<Long> promise,
148+
@Nullable WorkerExecutor workerExecutor);
135149

136-
protected final boolean shouldRun(@NotNull Instant triggerAt) {
137-
final long tick = state.increaseTick();
150+
protected void unregisterTimer(long timerId) { vertx.cancelTimer(timerId); }
151+
152+
protected InternalTriggerContext shouldRun(@NotNull Instant triggerAt, @NotNull TriggerContext triggerContext) {
153+
state.increaseTick();
138154
if (state.completed()) {
139-
trace(tick, state.round(), triggerAt, "The task execution is already completed");
155+
trace(triggerAt, "The task execution is already completed");
140156
}
141157
if (state.executing()) {
142-
trace(tick, state.round(), triggerAt, "Skip the execution due to the task is still running");
143-
monitor.onMisfire(TaskResultImpl.<OUT>builder()
144-
.setExternalId(jobData.externalId())
145-
.setAvailableAt(state.availableAt())
146-
.setTick(state.tick())
147-
.setTriggeredAt(triggerAt)
148-
.build());
158+
trace(triggerAt, "Skip the execution due to the task is still running");
159+
onMisfire(triggerAt);
149160
}
150-
return state.idle() && trigger().shouldExecute(triggerAt);
161+
return InternalTriggerContext.create(state.idle() && trigger().shouldExecute(triggerAt), triggerContext);
151162
}
152163

153164
protected final boolean shouldStop(@Nullable TaskExecutionContext<OUT> executionContext, long round) {
154165
return (executionContext != null && executionContext.isForceStop()) || trigger().shouldStop(round);
155166
}
156167

157-
protected final void doStart(WorkerExecutor workerExecutor) {
158-
this.addTimer(Promise.promise(), workerExecutor)
159-
.onSuccess(this::onReceiveTimer)
160-
.onFailure(t -> monitor.onUnableSchedule(TaskResultImpl.<OUT>builder()
161-
.setExternalId(jobData.externalId())
162-
.setTick(state.tick())
163-
.setRound(state.round())
164-
.setAvailableAt(state.availableAt())
165-
.setUnscheduledAt(Instant.now())
166-
.setError(t)
167-
.build()));
168-
}
169-
170-
protected void doStop(long timerId) {
171-
vertx.cancelTimer(timerId);
168+
protected final void run(WorkerExecutor workerExecutor, TriggerContext triggerContext) {
169+
final Instant triggerAt = Instant.now();
170+
final InternalTriggerContext internalContext = shouldRun(triggerAt, triggerContext);
171+
if (internalContext.shouldRun()) {
172+
final TriggerContext triggerCtx = TriggerContext.create(internalContext.type(), internalContext.info());
173+
final TaskExecutionContextInternal<OUT> ctx = new TaskExecutionContextImpl<>(vertx, state.increaseRound(),
174+
triggerAt, triggerCtx);
175+
trace(triggerAt, "Trigger the task execution");
176+
if (workerExecutor != null) {
177+
workerExecutor.executeBlocking(promise -> executeTask(onExecute(promise, ctx)), this::onResult);
178+
} else {
179+
vertx.executeBlocking(promise -> executeTask(onExecute(promise, ctx)), this::onResult);
180+
}
181+
}
172182
}
173183

174-
protected final void trace(long tick, long round, @NotNull Instant at, @NotNull String event) {
184+
protected final void trace(@NotNull Instant at, @NotNull String event) {
175185
if (LOGGER.isTraceEnabled()) {
176-
LOGGER.trace(genMsg(tick, round, at, event));
186+
LOGGER.trace(genMsg(state.tick(), state.round(), at, event));
177187
}
178188
}
179189

180-
protected final void onReceiveTimer(long timerId) {
190+
protected final void onSchedule(long timerId) {
181191
TaskResult<OUT> result;
182192
if (state.pending()) {
183193
result = TaskResultImpl.<OUT>builder()
@@ -196,37 +206,44 @@ protected final void onReceiveTimer(long timerId) {
196206
monitor.onSchedule(result);
197207
}
198208

199-
protected final void run(WorkerExecutor workerExecutor) {
200-
final Instant triggerAt = Instant.now();
201-
if (shouldRun(triggerAt)) {
202-
TaskExecutionContextInternal<OUT> ctx = new TaskExecutionContextImpl<>(vertx, state.increaseRound(),
203-
triggerAt);
204-
trace(state.tick(), ctx.round(), triggerAt, "Trigger the task execution");
205-
if (workerExecutor != null) {
206-
workerExecutor.executeBlocking(promise -> executeTask(onExecute(promise, ctx)), this::onResult);
207-
} else {
208-
vertx.executeBlocking(promise -> executeTask(onExecute(promise, ctx)), this::onResult);
209-
}
210-
}
209+
protected final void onUnableSchedule(Throwable t) {
210+
monitor.onUnableSchedule(TaskResultImpl.<OUT>builder()
211+
.setExternalId(jobData.externalId())
212+
.setTick(state.tick())
213+
.setRound(state.round())
214+
.setUnscheduledAt(Instant.now())
215+
.setError(t)
216+
.build());
211217
}
212218

213-
private TaskExecutionContextInternal<OUT> onExecute(@NotNull Promise<Object> promise,
214-
@NotNull TaskExecutionContextInternal<OUT> executionContext) {
215-
state.markExecuting();
216-
return executionContext.setup(promise, Instant.now());
219+
protected final void onMisfire(@NotNull Instant triggerAt) {
220+
monitor.onMisfire(TaskResultImpl.<OUT>builder()
221+
.setExternalId(jobData.externalId())
222+
.setTick(state.tick())
223+
.setAvailableAt(state.availableAt())
224+
.setTriggeredAt(triggerAt)
225+
.build());
217226
}
218227

219228
@SuppressWarnings("unchecked")
220229
protected final void onResult(@NotNull AsyncResult<Object> asyncResult) {
221230
state.markIdle();
222231
final Instant finishedAt = Instant.now();
223232
if (asyncResult.failed()) {
224-
LOGGER.warn(genMsg(state.tick(), state.round(), finishedAt, "Internal execution error"),
225-
asyncResult.cause());
233+
final Throwable cause = asyncResult.cause();
234+
LOGGER.warn(genMsg(state.tick(), state.round(), finishedAt, "Internal execution error"), cause);
235+
monitor.onEach(TaskResultImpl.<OUT>builder()
236+
.setExternalId(jobData.externalId())
237+
.setAvailableAt(state.availableAt())
238+
.setTick(state.tick())
239+
.setRound(state.round())
240+
.setFinishedAt(finishedAt)
241+
.setError(state.addError(state.round(), cause))
242+
.build());
226243
}
227244
TaskExecutionContextInternal<OUT> executionContext = (TaskExecutionContextInternal<OUT>) asyncResult.result();
228245
if (asyncResult.succeeded()) {
229-
trace(state.tick(), executionContext.round(), finishedAt, "Received the task result");
246+
trace(finishedAt, "Received the task result");
230247
monitor.onEach(TaskResultImpl.<OUT>builder()
231248
.setExternalId(jobData.externalId())
232249
.setAvailableAt(state.availableAt())
@@ -242,14 +259,13 @@ protected final void onResult(@NotNull AsyncResult<Object> asyncResult) {
242259
}
243260
if (shouldStop(executionContext, state.round())) {
244261
doStop(state.timerId());
245-
onCompleted();
246262
}
247263
}
248264

249265
protected final void onCompleted() {
250266
state.markCompleted();
251267
final Instant completedAt = Instant.now();
252-
trace(state.tick(), state.round(), completedAt, "The task execution is completed");
268+
trace(completedAt, "The task execution is completed");
253269
monitor.onCompleted(TaskResultImpl.<OUT>builder()
254270
.setExternalId(jobData.externalId())
255271
.setAvailableAt(state.availableAt())
@@ -262,6 +278,12 @@ protected final void onCompleted() {
262278
.build());
263279
}
264280

281+
private TaskExecutionContextInternal<OUT> onExecute(@NotNull Promise<Object> promise,
282+
@NotNull TaskExecutionContextInternal<OUT> executionContext) {
283+
state.markExecuting();
284+
return executionContext.setup(promise, Instant.now());
285+
}
286+
265287
private String genMsg(long tick, long round, @NotNull Instant at, @NotNull String event) {
266288
return "TaskExecutor[" + tick + "][" + round + "][" + at + "]::[" + jobData.externalId() + "] - " + event;
267289
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Objects;
44
import java.util.Optional;
55

6+
import org.jetbrains.annotations.ApiStatus.Internal;
67
import org.jetbrains.annotations.NotNull;
78

89
import io.github.zero88.schedulerx.JobData;
@@ -18,10 +19,13 @@
1819
* The base task executor builder
1920
*/
2021
@SuppressWarnings("unchecked")
21-
public abstract class AbstractTaskExecutorBuilder<IN, OUT, T extends Trigger, E extends TriggerTaskExecutor<IN, OUT,
22-
T>,
22+
@Internal
23+
// @formatter:off
24+
public abstract class AbstractTaskExecutorBuilder<IN, OUT, T extends Trigger,
25+
E extends TriggerTaskExecutor<IN, OUT, T>,
2326
B extends TriggerTaskExecutorBuilder<IN, OUT, T, E, B>>
2427
implements TriggerTaskExecutorBuilder<IN, OUT, T, E, B> {
28+
// @formatter:on
2529

2630
private Vertx vertx;
2731
private TaskExecutorMonitor<OUT> monitor;
@@ -46,9 +50,7 @@ public abstract class AbstractTaskExecutorBuilder<IN, OUT, T extends Trigger, E
4650
public @NotNull Task<IN, OUT> task() { return Objects.requireNonNull(task, "Task is required"); }
4751

4852
@Override
49-
public @NotNull JobData<IN> jobData() {
50-
return Optional.ofNullable(jobData).orElseGet(JobData::empty);
51-
}
53+
public @NotNull JobData<IN> jobData() { return Optional.ofNullable(jobData).orElseGet(JobData::empty); }
5254

5355
public @NotNull B setVertx(@NotNull Vertx vertx) {
5456
this.vertx = vertx;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.TriggerContext;
8+
9+
@Internal
10+
public interface InternalTriggerContext extends TriggerContext {
11+
12+
boolean shouldRun();
13+
14+
static InternalTriggerContext create(boolean shouldRun, TriggerContext ctx) {
15+
return new InternalTriggerContext() {
16+
@Override
17+
public boolean shouldRun() { return shouldRun; }
18+
19+
@Override
20+
public @Nullable Object info() { return ctx.info(); }
21+
22+
@Override
23+
public @NotNull String type() { return ctx.type(); }
24+
};
25+
}
26+
27+
}

0 commit comments

Comments
 (0)