Skip to content

Commit

Permalink
feat(#57): Add Trigger rule
Browse files Browse the repository at this point in the history
  • Loading branch information
zero88 committed Sep 30, 2023
1 parent bc2d58d commit 10c919d
Show file tree
Hide file tree
Showing 27 changed files with 917 additions and 21 deletions.
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ object JacksonLibs {

const val annotations = "com.fasterxml.jackson.core:jackson-annotations:${Version.jackson}"
const val databind = "com.fasterxml.jackson.core:jackson-databind:${Version.jackson}"
const val jsr310 = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${Version.jackson}"
}

object TestLibs {
Expand Down
1 change: 1 addition & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {

testImplementation(TestLibs.junit5Params)
testImplementation(JacksonLibs.databind)
testImplementation(JacksonLibs.jsr310)
testImplementation(LogLibs.logback)
testCompileOnly(UtilLibs.jetbrainsAnnotations)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ protected final TriggerContext shouldStop(@NotNull TriggerContext triggerContext
}

protected TriggerContext evaluateTrigger(@NotNull TriggerContext triggerContext) {
return trigger().shouldExecute(Objects.requireNonNull(triggerContext.triggerAt()))
final Instant triggerAt = Objects.requireNonNull(triggerContext.triggerAt());
if (trigger().rule().isExceeded(triggerAt)) {
return TriggerContextFactory.stop(triggerContext, ReasonCode.STOP_BY_CONFIG);
}
return trigger().shouldExecute(triggerAt)
? TriggerContextFactory.ready(triggerContext)
: TriggerContextFactory.skip(triggerContext, ReasonCode.CONDITION_IS_NOT_MATCHED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import org.jetbrains.annotations.NotNull;

import io.github.zero88.schedulerx.trigger.rule.TriggerRule;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

Expand Down Expand Up @@ -85,12 +87,18 @@ public long nextTriggerAfter(@NotNull Instant current) {
@Override
public @NotNull List<OffsetDateTime> preview(@NotNull PreviewParameter parameter) {
validate();
Instant next = parameter.getStartedAt();
final ZoneId zoneId = Optional.ofNullable(parameter.getTimeZone()).orElseGet(timeZone::toZoneId);
final List<OffsetDateTime> result = new ArrayList<>();
final TriggerRule rule = parameter.getRule();
final ZoneId zoneId = Optional.ofNullable(parameter.getTimeZone()).orElseGet(timeZone::toZoneId);
Instant next = parameter.getStartedAt();
do {
next = cronExpression.getNextValidTimeAfter(Date.from(next)).toInstant();
result.add(next.atZone(zoneId).toOffsetDateTime());
if (rule.isExceeded(next)) {
break;
}
if (rule.satisfy(next)) {
result.add(next.atZone(zoneId).toOffsetDateTime());
}
} while (result.size() != parameter.getTimes());
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.jetbrains.annotations.NotNull;

import io.github.zero88.schedulerx.impl.Utils;
import io.github.zero88.schedulerx.trigger.rule.TriggerRule;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

Expand Down Expand Up @@ -42,7 +43,7 @@ public final class IntervalTrigger implements Trigger {
*/
private final long repeat;
/**
* Get the initial delay time (in {@link #getInitialDelayTimeUnit()}) before firing trigger in first time.
* Get the initial delay time (in {@link #getInitialDelayTimeUnit()}) before emitting trigger in the first time.
*
* @apiNote Default is {@code 0}
*/
Expand Down Expand Up @@ -116,20 +117,26 @@ public boolean shouldStop(long round) {

@Override
public @NotNull List<OffsetDateTime> preview(@NotNull PreviewParameter parameter) {
final long count = Math.min(repeat, parameter.getTimes());
final List<OffsetDateTime> result = new ArrayList<>();
final long count = Math.min(repeat, parameter.getTimes());
final TriggerRule rule = parameter.getRule();
final ZoneId zoneId = Optional.ofNullable(parameter.getTimeZone()).orElse(ZoneOffset.UTC);
Instant next = parameter.getStartedAt();
next = next.plus(initialDelay, Utils.toChronoUnit(initialDelayTimeUnit));
do {
next = next.plus(interval, Utils.toChronoUnit(intervalTimeUnit));
result.add(next.atZone(zoneId).toOffsetDateTime());
if (rule.isExceeded(next)) {
break;
}
if (rule.satisfy(next)) {
result.add(next.atZone(zoneId).toOffsetDateTime());
}
} while (result.size() != count);
return result;
}

static void validate(long number, boolean allowZero, boolean allowIndefinitely, String msg) {
if (number > 0 || (allowZero && number == 0) || (allowIndefinitely && number == REPEAT_INDEFINITELY)) {
static void validate(long number, boolean allowZero, boolean allowInfinite, String msg) {
if (number > 0 || (allowZero && number == 0) || (allowInfinite && number == REPEAT_INDEFINITELY)) {
return;
}
throw new IllegalArgumentException("Invalid " + msg + " value");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import io.github.zero88.schedulerx.trigger.rule.TriggerRule;

/**
* The preview parameter to generate the next trigger time of the specific trigger.
*
Expand All @@ -19,9 +20,10 @@ public final class PreviewParameter {

public static final int MAX_TIMES = 30;

private Instant startedAt;
private Instant startedAt = Instant.now();
private int times;
private ZoneId timeZone;
private TriggerRule rule = TriggerRule.NOOP;

/**
* Create default the preview parameter with startedAt is now and times = 10
Expand All @@ -35,8 +37,8 @@ public static PreviewParameter byDefault() {
/**
* @return the started at time to generate the preview results
*/
public Instant getStartedAt() {
return Optional.ofNullable(startedAt).orElseGet(Instant::now);
public @NotNull Instant getStartedAt() {
return startedAt;
}

/**
Expand All @@ -45,8 +47,10 @@ public Instant getStartedAt() {
* @param startedAt started at time
* @return this for fluent API
*/
public PreviewParameter setStartedAt(Instant startedAt) {
this.startedAt = startedAt;
public @NotNull PreviewParameter setStartedAt(Instant startedAt) {
if (startedAt != null) {
this.startedAt = startedAt;
}
return this;
}

Expand All @@ -63,7 +67,7 @@ public int getTimes() {
* @param times the number of a preview item
* @return this for fluent API
*/
public PreviewParameter setTimes(int times) {
public @NotNull PreviewParameter setTimes(int times) {
this.times = times;
return this;
}
Expand Down Expand Up @@ -108,4 +112,25 @@ public PreviewParameter setTimeZone(ZoneOffset zoneOffset) {
return this;
}

/**
* @return the trigger rule
* @see TriggerRule
*/
public @NotNull TriggerRule getRule() {
return rule;
}

/**
* Set a trigger rule in the preview parameter
*
* @param rule trigger rule
* @return this for fluent API
*/
public @NotNull PreviewParameter setRule(TriggerRule rule) {
if (rule != null) {
this.rule = rule;
}
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import io.github.zero88.schedulerx.Task;
import io.github.zero88.schedulerx.spi.TriggerRepresentation;
import io.github.zero88.schedulerx.spi.TriggerRepresentationServiceLoader;
import io.github.zero88.schedulerx.trigger.rule.TriggerRule;

/**
* Represents for inspecting settings specific to a Trigger, which is used to fire a <code>{@link Task}</code> at given
* moments in time
* moments in time.
*
* @since 1.0.0
*/
Expand All @@ -28,6 +29,17 @@ public interface Trigger extends HasTriggerType, TriggerRepresentation {
*/
@NotNull Trigger validate();

/**
* Defines the trigger rule
*
* @return the trigger rule
* @see TriggerRule
* @since 2.0.0
*/
default @NotNull TriggerRule rule() {
return TriggerRule.NOOP;
}

/**
* Verify if the trigger time still appropriate to execute the task.
* <p/>
Expand All @@ -36,7 +48,7 @@ public interface Trigger extends HasTriggerType, TriggerRepresentation {
* @param triggerAt the trigger time
* @since 2.0.0
*/
default boolean shouldExecute(@NotNull Instant triggerAt) { return true; }
default boolean shouldExecute(@NotNull Instant triggerAt) { return rule().satisfy(triggerAt); }

/**
* Verify the execution should be stopped after the current execution round is out of the trigger rule.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.github.zero88.schedulerx.trigger.rule;

import java.util.Objects;

import org.jetbrains.annotations.NotNull;

abstract class BaseTimeframe<T> implements Timeframe<T>, TimeParser<T> {

private T from;
private T to;

protected BaseTimeframe() { }

protected final BaseTimeframe<T> setValues(Object from, Object to) {
final TimeframeValidator validator = validator();
final TimeParser<T> parser = parser();
this.from = validator.normalize(parser, from);
this.to = validator.normalize(parser, to);
validator.validate(this);
return this;
}

@Override
public final T from() { return from; }

@Override
public final T to() { return to; }

@Override
public final @NotNull TimeParser<T> parser() { return this; }

@Override
public int hashCode() {
int result = from.hashCode();
result = 31 * result + to.hashCode();
result = 31 * result + type().hashCode();
return result;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Timeframe<?> that = (Timeframe<?>) o;
return Objects.equals(type(), that.type()) && Objects.equals(from, that.from()) &&
Objects.equals(to, that.to());
}

@Override
public String toString() {
return "TimeFrame{type=" + type().getName() + ", (" + from + ", " + to + ")}";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.github.zero88.schedulerx.trigger.rule;

import java.time.Instant;

import org.jetbrains.annotations.NotNull;

public class InstantTimeframe extends BaseTimeframe<Instant> implements TimeRangeConstraint {

@Override
public final @NotNull Class<Instant> type() { return Instant.class; }

@Override
public boolean check(@NotNull Instant instant) {
return (from() == null || instant.isAfter(from())) && (to() == null || instant.isBefore(to()));
}

@Override
public Instant parse(Object value) {
if (value instanceof CharSequence) {
return Instant.parse((CharSequence) value);
}
return (Instant) value;
}

@Override
public @NotNull TimeframeValidator validator() {
return super.validator().and(this);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.zero88.schedulerx.trigger.rule;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;

import org.jetbrains.annotations.NotNull;

public class LocalDateTimeFrame extends BaseTimeframe<LocalDate> implements TimeRangeConstraint {

@Override
public final @NotNull Class<LocalDate> type() { return LocalDate.class; }

@Override
public boolean check(@NotNull Instant instant) {
final LocalDate given = instant.atZone(ZoneId.systemDefault()).toLocalDate();
return (from() == null || given.isAfter(from())) && (to() == null || given.isBefore(to()));
}

@Override
public LocalDate parse(Object value) {
if (value instanceof CharSequence) {
return LocalDate.parse((CharSequence) value);
}
return (LocalDate) value;
}

@Override
public @NotNull TimeframeValidator validator() {
return super.validator().and(this);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.zero88.schedulerx.trigger.rule;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

import org.jetbrains.annotations.NotNull;

public class LocalDateTimeTimeframe extends BaseTimeframe<LocalDateTime> implements TimeRangeConstraint {

@Override
public final @NotNull Class<LocalDateTime> type() { return LocalDateTime.class; }

@Override
public boolean check(@NotNull Instant instant) {
final LocalDateTime given = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
return (from() == null || given.isAfter(from())) && (to() == null || given.isBefore(to()));
}

@Override
public LocalDateTime parse(Object value) {
if (value instanceof CharSequence) {
return LocalDateTime.parse((CharSequence) value);
}
return (LocalDateTime) value;
}

@Override
public @NotNull TimeframeValidator validator() {
return super.validator().and(this);
}

}
Loading

0 comments on commit 10c919d

Please sign in to comment.