diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ffdde1ce..cfed45f8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## v1.5.5 - (2025-01-19)
+
+- Added MdcTriggerInterceptor
+
## v1.5.4 - (2025-01-14)
- adjusted trigger cols that the UI does not break
diff --git a/README.md b/README.md
index 06abcd5ce..105ab26ee 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,11 @@ Focus is the usage with spring boot and JPA.
Secondary goal is to support [Poor mans Workflow](https://github.com/sterlp/pmw)
+# Documentation
+
+Use for more advanced doc the [WIKI](https://github.com/sterlp/spring-persistent-tasks/wiki).
+The README contains a shorter how to use.
+
# DBs for storage
## Tested in the pipeline
@@ -27,14 +32,17 @@ Secondary goal is to support [Poor mans Workflow](https://github.com/sterlp/pmw)
- mySQL: sequences are not supported
-# Setup and Run a Task
+# JavaDoc
- [JavaDoc](https://sterlp.github.io/spring-persistent-tasks/javadoc-core/org/sterl/spring/persistent_tasks/PersistentTaskService.html)
-## Maven
+# Quickstart
- [Maven Central spring-persistent-tasks-core](https://central.sonatype.com/artifact/org.sterl.spring/spring-persistent-tasks-core/versions)
+## Setup with Maven
+
+
```xml
org.sterl.spring
@@ -51,61 +59,7 @@ Secondary goal is to support [Poor mans Workflow](https://github.com/sterlp/pmw)
public class ExampleApplication {
```
-## Setup a spring persistent task
-
-### As a class
-
-```java
-@Component(BuildVehicleTask.NAME)
-@RequiredArgsConstructor
-@Slf4j
-public class BuildVehicleTask implements PersistentTask {
-
- private static final String NAME = "buildVehicleTask";
- public static final TaskId ID = new TaskId<>(NAME);
-
- private final VehicleRepository vehicleRepository;
-
- @Override
- public void accept(Vehicle vehicle) {
- // do stuff
- // save
- vehicleRepository.save(vehicle);
- }
- // OPTIONAL
- @Override
- public RetryStrategy retryStrategy() {
- // run 5 times, multiply the execution count with 4, add the result in HOURS to now.
- return new MultiplicativeRetryStrategy(5, ChronoUnit.HOURS, 4)
- }
- // OPTIONAL
- // if the task in accept requires a DB transaction, join them together with the framework
- // if true the TransactionTemplate is used. Set here any timeouts.
- @Override
- public boolean isTransactional() {
- return true;
- }
-}
-```
-
-Consider setting a timeout to the `TransactionTemplate`:
-
-```java
-@Bean
-TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
- TransactionTemplate template = new TransactionTemplate(transactionManager);
- template.setTimeout(10);
- return template;
-}
-```
-
-### As a closure
-
-Simple task will use defaults:
-
-- Not a transactional task, e.g. HTTP calls
-- 4 executions, one regular and 3 retries, linear
-- using minutes with an offset of 1 which is added to now
+## Create a Task
```java
@Bean
@@ -114,59 +68,15 @@ PersistentTask task1(VehicleHttpConnector vehicleHttpConnector) {
}
```
-### Task Transaction Management
-
-[Transaction-Management Task](https://github.com/sterlp/spring-persistent-tasks/wiki/Transaction-Management)
-
-## Queue a task execution
-
-### Direct usage of the `TriggerService` or `PersistentTaskService`.
-
-```java
-private final TriggerService triggerService;
-private final PersistentTaskService persistentTaskService;
-
-public void buildVehicle() {
- // Vehicle has to be Serializable
- final var v = new Vehicle();
- // set any data to v ...
-
- // EITHER: queue it, will run later
- triggerService.queue(BuildVehicleTask.ID.newUniqueTrigger(v));
-
- // OR: will queue it and run it if possible.
- // if the scheduler service is missing it is same as using the TriggerService
- persistentTaskService.runOrQueue(BuildVehicleTask.ID.newUniqueTrigger(v));
-}
-```
-
-### Build complex Trigger
-
-```java
-private final PersistentTaskService persistentTaskService;
-
-public void buildVehicle() {
- var trigger = TaskTriggerBuilder
- .newTrigger("task2")
- .id("my-id") // will overwrite existing triggers
- .state(new Vehicle("funny"))
- .runAfter(Duration.ofHours(2))
- .build();
-
- persistentTaskService.runOrQueue(trigger);
-}
-```
-
-### Use a Spring Event
+## Trigger a task
```java
-private final ApplicationEventPublisher eventPublisher;
+@Autowired
+PersistentTaskService persistentTaskService;
-public void buildVehicle() {
- // Vehicle has to be Serializable
- final var v = new Vehicle();
- // send an event with the trigger inside - same as calling the PersistentTaskService
- eventPublisher.publishEvent(TriggerTaskCommand.of(BuildVehicleTask.ID.newUniqueTrigger(v)));
+public void triggerTask1(Vehicle vehicle) {
+ persistentTaskService.runOrQueue(
+ TaskTriggerBuilder.newTrigger("task1").state(vehicle).build());
}
```
@@ -174,19 +84,6 @@ public void buildVehicle() {
- [Persistent Task and Testing](https://github.com/sterlp/spring-persistent-tasks/wiki/Triggers-and-Tasks-in-JUnit-Tests)
-
-### Spring configuration options
-
-| Property | Type | Description | Default Value |
-| ---------------------------------------------- | -------------------- | ------------------------------------------------------------------------ | ------------------ |
-| `spring.persistent-tasks.poll-rate` | `java.lang.Integer` | The interval at which the scheduler checks for new tasks, in seconds. | `30` |
-| `spring.persistent-tasks.max-threads` | `java.lang.Integer` | The number of threads to use; set to 0 to disable task processing. | `10` |
-| `spring.persistent-tasks.task-timeout` | `java.time.Duration` | The maximum time allowed for a task and scheduler to complete a task. | `PT5M` (5 minutes) |
-| `spring.persistent-tasks.poll-task-timeout` | `java.lang.Integer` | The interval at which the system checks for abandoned tasks, in seconds. | `300` (5 minutes) |
-| `spring.persistent-tasks.scheduler-enabled` | `java.lang.Boolean` | Indicates whether this node should handle triggers. | `true` |
-| `spring.persistent-tasks.history.delete-after` | `java.time.Duration` | The max age for a trigger in the hstory. | `PT72H` (30 days) |
-| `spring.persistent-tasks.history.delete-rate` | `java.time.Integer` | The interval at which old triggers are deleted, in hours. | `24` (24 hours) |
-
# Setup DB with Liquibase
Liquibase is supported. Either import all or just the required versions.
@@ -246,25 +143,6 @@ public class ExampleApplication {

-## Spring Boot CSRF config for the UI
-
-Axios should work with the following spring config out of the box with csrf:
-
-```java
-@Bean
-SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- http
- .httpBasic(org.springframework.security.config.Customizer.withDefaults())
- .csrf(c ->
- c.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
- .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
- );
- return http.build();
-}
-```
-
-more informations: https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html
-
# Alternatives
- quartz
diff --git a/RUN_AND_BUILD.md b/RUN_AND_BUILD.md
index b2f5d3ee1..9a6b68018 100644
--- a/RUN_AND_BUILD.md
+++ b/RUN_AND_BUILD.md
@@ -1,5 +1,5 @@
mvn versions:display-dependency-updates
-mvn versions:set -DnewVersion=1.5.3 -DgenerateBackupPoms=false
+mvn versions:set -DnewVersion=1.5.5 -DgenerateBackupPoms=false
git tag -a v1.5.3 -m "v1.5.3 release"
mvn versions:set -DnewVersion=1.5.4-SNAPSHOT -DgenerateBackupPoms=false
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerKey.java b/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerKey.java
index 53b45bf42..6fe4e83c1 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerKey.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerKey.java
@@ -10,6 +10,7 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import lombok.ToString;
/**
* Unique key of a trigger during it's execution. But it after that the same key
@@ -17,7 +18,8 @@
* is currently scheduled for execution.
*/
@Data
-@Builder(toBuilder = true)
+@ToString
+@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TriggerKey implements Serializable {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/exception/SpringPersistentTaskException.java b/core/src/main/java/org/sterl/spring/persistent_tasks/exception/SpringPersistentTaskException.java
new file mode 100644
index 000000000..3469aa430
--- /dev/null
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/exception/SpringPersistentTaskException.java
@@ -0,0 +1,19 @@
+package org.sterl.spring.persistent_tasks.exception;
+
+import lombok.Getter;
+
+public class SpringPersistentTaskException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ @Getter
+ protected final Object state;
+
+ public SpringPersistentTaskException(String message, Object state, Throwable cause) {
+ super(message, cause);
+ this.state = state;
+ }
+
+ public SpringPersistentTaskException(String message, Object state) {
+ super(message);
+ this.state = state;
+ }
+}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerService.java b/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerService.java
index ecb7aeb63..75787eff6 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerService.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerService.java
@@ -10,6 +10,7 @@
import java.util.concurrent.Future;
import org.springframework.lang.NonNull;
+import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@@ -105,6 +106,7 @@ public List> triggerNextTasks() {
* This method should not be called in a transaction!
*
*/
+ @Transactional(propagation = Propagation.NEVER)
@NonNull
public List> triggerNextTasks(OffsetDateTime timeDue) {
if (taskExecutor.getFreeThreads() > 0) {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/EditTriggerComponent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/EditTriggerComponent.java
index f03c9cbc5..13ef05534 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/EditTriggerComponent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/EditTriggerComponent.java
@@ -10,6 +10,7 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.sterl.spring.persistent_tasks.api.AddTriggerRequest;
import org.sterl.spring.persistent_tasks.api.TriggerKey;
@@ -18,6 +19,7 @@
import org.sterl.spring.persistent_tasks.trigger.event.TriggerAddedEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerCanceledEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerFailedEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerRunningEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent;
import org.sterl.spring.persistent_tasks.trigger.model.TriggerEntity;
import org.sterl.spring.persistent_tasks.trigger.repository.TriggerRepository;
@@ -40,9 +42,9 @@ public Optional completeTaskWithSuccess(TriggerKey key, Serializa
result.ifPresent(t -> {
t.complete(null);
+ log.debug("{} set to status={}", key, t.getData().getStatus());
publisher.publishEvent(new TriggerSuccessEvent(
t.getId(), t.copyData(), state));
- log.debug("Setting {} to status={}", key, t.getData().getStatus());
triggerRepository.delete(t);
});
return result;
@@ -146,4 +148,11 @@ public int markTriggersAsRunning(Collection keys, String runOn) {
return triggerRepository.markTriggersAsRunning(keys, runOn,
OffsetDateTime.now(), TriggerStatus.RUNNING);
}
+
+ @Transactional(propagation = Propagation.SUPPORTS)
+ public void triggerIsNowRunning(TriggerEntity trigger, Serializable state) {
+ if (!trigger.isRunning()) trigger.runOn(trigger.getRunningOn());
+ publisher.publishEvent(new TriggerRunningEvent(
+ trigger.getId(), trigger.copyData(), state, trigger.getRunningOn()));
+ }
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java
index a2d080966..d0cfeabba 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java
@@ -1,18 +1,14 @@
package org.sterl.spring.persistent_tasks.trigger.component;
-import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.Optional;
-import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionTemplate;
-import org.sterl.spring.persistent_tasks.api.PersistentTask;
import org.sterl.spring.persistent_tasks.task.TaskService;
-import org.sterl.spring.persistent_tasks.trigger.event.TriggerRunningEvent;
+import org.sterl.spring.persistent_tasks.trigger.model.RunTaskWithStateCommand;
import org.sterl.spring.persistent_tasks.trigger.model.TriggerEntity;
import lombok.RequiredArgsConstructor;
@@ -25,7 +21,6 @@ public class RunTriggerComponent {
private final TaskService taskService;
private final EditTriggerComponent editTrigger;
- private final ApplicationEventPublisher eventPublisher;
private final StateSerializer serializer = new StateSerializer();
/**
@@ -36,35 +31,35 @@ public Optional execute(TriggerEntity trigger) {
if (trigger == null) {
return Optional.empty();
}
- final var taskAndState = getTastAndState(trigger);
+
+ final var runTaskWithState = buildTaskWithStateFor(trigger);
// something went really wrong this trigger is crap
- if (taskAndState == null) return Optional.of(trigger);
+ if (runTaskWithState == null) return Optional.of(trigger);
try {
- return taskAndState.call();
+ return runTaskWithState.execute(editTrigger);
} catch (Exception e) {
- return failTaskAndState(taskAndState, e);
+ return failTaskAndState(runTaskWithState, e);
}
}
@Nullable
- private TaskAndState getTastAndState(TriggerEntity trigger) {
+ private RunTaskWithStateCommand buildTaskWithStateFor(TriggerEntity trigger) {
try {
- var task = taskService.assertIsKnown(trigger.newTaskId());
- var trx = taskService.getTransactionTemplate(task);
- var state = serializer.deserialize(trigger.getData().getState());
- return new TaskAndState(task, trx, state, trigger);
+ final var task = taskService.assertIsKnown(trigger.newTaskId());
+ final var trx = taskService.getTransactionTemplate(task);
+ final var state = serializer.deserialize(trigger.getData().getState());
+ return new RunTaskWithStateCommand(task, trx, state, trigger);
} catch (Exception e) {
- // this trigger is somehow crap, no retry and done.
- failTaskAndState(new TaskAndState(null, Optional.empty(), null, trigger), e);
+ failTaskAndState(new RunTaskWithStateCommand(null, Optional.empty(), null, trigger), e);
return null;
}
}
- private Optional failTaskAndState(TaskAndState taskAndState, Exception e) {
+ private Optional failTaskAndState(RunTaskWithStateCommand runTaskWithStateCommand, Exception e) {
- var trigger = taskAndState.trigger;
- var task = taskAndState.persistentTask;
+ var trigger = runTaskWithStateCommand.trigger();
+ var task = runTaskWithStateCommand.task();
Optional result;
if (task != null
@@ -72,43 +67,14 @@ private Optional failTaskAndState(TaskAndState taskAndState, Exce
final OffsetDateTime retryAt = task.retryStrategy().retryAt(trigger.getData().getExecutionCount(), e);
- result = editTrigger.failTrigger(trigger.getKey(), taskAndState.state, e, retryAt);
+ result = editTrigger.failTrigger(trigger.getKey(), runTaskWithStateCommand.state(), e, retryAt);
} else {
log.error("{} failed, no more retries! {}", trigger.getKey(),
e == null ? "No exception given." : e.getMessage(), e);
- result = editTrigger.failTrigger(trigger.getKey(), taskAndState.state, e, null);
+ result = editTrigger.failTrigger(trigger.getKey(), runTaskWithStateCommand.state(), e, null);
}
return result;
}
-
- @RequiredArgsConstructor
- class TaskAndState {
- final PersistentTask persistentTask;
- final Optional trx;
- final Serializable state;
- final TriggerEntity trigger;
-
- Optional call() {
- if (trx.isPresent()) {
- return trx.get().execute(t -> runTask());
- } else {
- return runTask();
- }
- }
-
- private Optional runTask() {
- if (!trigger.isRunning()) trigger.runOn(trigger.getRunningOn());
- eventPublisher.publishEvent(new TriggerRunningEvent(
- trigger.getId(), trigger.copyData(), state, trigger.getRunningOn()));
-
- persistentTask.accept(state);
-
- var result = editTrigger.completeTaskWithSuccess(trigger.getKey(), state);
- editTrigger.deleteTrigger(trigger);
-
- return result;
- }
- }
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/StateSerializer.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/StateSerializer.java
index ad1d61e92..410d70986 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/StateSerializer.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/StateSerializer.java
@@ -7,10 +7,27 @@
import java.io.ObjectOutputStream;
import java.io.Serializable;
+import org.sterl.spring.persistent_tasks.exception.SpringPersistentTaskException;
+
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class StateSerializer {
+ public static class DeSerializationFailedException extends SpringPersistentTaskException {
+ private static final long serialVersionUID = 1L;
+
+ public DeSerializationFailedException(byte[] bytes, Exception e) {
+ super("Failed to deserialize state of length " + bytes.length, bytes, e);
+ }
+ }
+
+ public static class SerializationFailedException extends SpringPersistentTaskException {
+ private static final long serialVersionUID = 1L;
+
+ public SerializationFailedException(Serializable obj, Exception e) {
+ super("Failed to serialize state " + obj.getClass(), obj, e);
+ }
+ }
public byte[] serialize(final Serializable obj) {
if (obj == null) {
@@ -23,7 +40,7 @@ public byte[] serialize(final Serializable obj) {
out.writeObject(obj);
return bos.toByteArray();
} catch (Exception ex) {
- throw new RuntimeException(ex);
+ throw new SerializationFailedException(obj, ex);
}
}
@@ -36,7 +53,7 @@ public Serializable deserialize(byte[] bytes) {
try (ObjectInput in = new ObjectInputStream(bis)) {
return (Serializable)in.readObject();
} catch (Exception ex) {
- throw new RuntimeException("Failed to deserialize state of length " + bytes.length, ex);
+ throw new DeSerializationFailedException(bytes, ex);
}
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/MdcTriggerInterceptor.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/MdcTriggerInterceptor.java
new file mode 100644
index 000000000..2beb9172f
--- /dev/null
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/MdcTriggerInterceptor.java
@@ -0,0 +1,34 @@
+package org.sterl.spring.persistent_tasks.trigger.interceptor;
+
+import org.slf4j.MDC;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerFailedEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerRunningEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent;
+
+/**
+ * Adds task name and id to the {@link MDC} context.
+ */
+@Component
+public class MdcTriggerInterceptor {
+
+ public static final String TASK_NAME = "taskName";
+ public static final String TASK_ID = "taskId";
+
+ @EventListener
+ public void beforeRun(TriggerRunningEvent data) {
+ MDC.put(TASK_NAME, data.key().getTaskName());
+ MDC.put(TASK_ID, data.key().getId());
+ }
+ @EventListener
+ public void onFailed(TriggerFailedEvent data) {
+ MDC.remove(TASK_NAME);
+ MDC.remove(TASK_ID);
+ }
+ @EventListener
+ public void onSuccess(TriggerSuccessEvent data) {
+ MDC.remove(TASK_NAME);
+ MDC.remove(TASK_ID);
+ }
+}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java
new file mode 100644
index 000000000..b1f29fbb8
--- /dev/null
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java
@@ -0,0 +1,41 @@
+package org.sterl.spring.persistent_tasks.trigger.model;
+
+import java.io.Serializable;
+import java.util.Optional;
+
+import org.springframework.transaction.support.TransactionTemplate;
+import org.sterl.spring.persistent_tasks.api.PersistentTask;
+import org.sterl.spring.persistent_tasks.shared.model.HasTriggerData;
+import org.sterl.spring.persistent_tasks.shared.model.TriggerData;
+import org.sterl.spring.persistent_tasks.trigger.component.EditTriggerComponent;
+
+public record RunTaskWithStateCommand (
+ PersistentTask task,
+ Optional trx,
+ Serializable state,
+ TriggerEntity trigger) implements HasTriggerData {
+
+ public Optional execute(EditTriggerComponent editTrigger) {
+ if (trx.isPresent()) {
+ return trx.get().execute(t -> runTask(editTrigger));
+ } else {
+ return runTask(editTrigger);
+ }
+ }
+
+ private Optional runTask(EditTriggerComponent editTrigger) {
+ editTrigger.triggerIsNowRunning(trigger, state);
+
+ task.accept(state);
+
+ var result = editTrigger.completeTaskWithSuccess(trigger.getKey(), state);
+ editTrigger.deleteTrigger(trigger);
+
+ return result;
+ }
+
+ @Override
+ public TriggerData getData() {
+ return trigger.getData();
+ }
+}
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/AbstractSpringTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/AbstractSpringTest.java
index f70df765d..b5807f675 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/AbstractSpringTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/AbstractSpringTest.java
@@ -16,6 +16,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
+import org.springframework.test.context.event.RecordApplicationEvents;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.sterl.spring.persistent_tasks.api.PersistentTask;
@@ -40,6 +41,7 @@
// @ActiveProfiles("mssql") // postgres mssql mariadb mysql
@SpringBootTest(classes = SampleApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
+@RecordApplicationEvents
public class AbstractSpringTest {
@Autowired
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java
index c57463b07..66121c174 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java
@@ -17,7 +17,8 @@
class HistoryServiceTest extends AbstractSpringTest {
- @Autowired HistoryService subject;
+ @Autowired
+ private HistoryService subject;
@Test
void testReQueueTrigger() {
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java
index f130430ca..f5822bd1e 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java
@@ -12,6 +12,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.event.ApplicationEvents;
import org.sterl.spring.persistent_tasks.AbstractSpringTest;
import org.sterl.spring.persistent_tasks.AbstractSpringTest.TaskConfig.Task3;
import org.sterl.spring.persistent_tasks.api.AddTriggerRequest;
@@ -21,6 +22,11 @@
import org.sterl.spring.persistent_tasks.api.TriggerStatus;
import org.sterl.spring.persistent_tasks.history.repository.TriggerHistoryLastStateRepository;
import org.sterl.spring.persistent_tasks.task.repository.TaskRepository;
+import org.sterl.spring.persistent_tasks.trigger.component.StateSerializer.DeSerializationFailedException;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerAddedEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerCanceledEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerFailedEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent;
import org.sterl.spring.persistent_tasks.trigger.model.TriggerEntity;
import org.sterl.spring.persistent_tasks.trigger.repository.TriggerRepository;
@@ -34,6 +40,9 @@ class TriggerServiceTest extends AbstractSpringTest {
private TaskRepository taskRepository;
@Autowired
private TriggerHistoryLastStateRepository triggerHistoryLastStateRepository;
+
+ @Autowired
+ private ApplicationEvents events;
// ensure persistentTask in the spring context
@Autowired
@@ -66,6 +75,8 @@ void testAddTrigger() throws Exception {
// AND
assertThat(triggerHistoryLastStateRepository.count()).isZero();
// AND
+ assertThat(events.stream(TriggerAddedEvent.class).count()).isOne();
+ // AND
final var e = subject.get(triggerId);
assertThat(e).isPresent();
assertThat(e.get().getData().getRunAt().toEpochSecond()).isEqualTo(triggerTime.toEpochSecond());
@@ -87,6 +98,8 @@ void testCreateTrigger() {
// THEN
assertThat(subject.countTriggers(taskId)).isEqualTo(2);
+ // AND
+ assertThat(events.stream(TriggerAddedEvent.class).count()).isEqualTo(2);
}
@Test
@@ -106,6 +119,9 @@ void testCancelTrigger() {
assertThat(subject.get(key1)).isEmpty();
assertThat(subject.get(key2)).isPresent();
+
+ // AND
+ assertThat(events.stream(TriggerCanceledEvent.class).count()).isOne();
}
@Test
@@ -119,6 +135,9 @@ void testTriggerSpringSimpleTask() throws Exception {
// THEN
assertThat(taskRepository.contains(Task3.NAME)).isTrue();
asserts.awaitValue(Task3.NAME + "::trigger3");
+ // AND
+ assertThat(events.stream(TriggerSuccessEvent.class).count()).isOne();
+ assertThat(events.stream(TriggerFailedEvent.class).count()).isZero();
}
@Test
@@ -163,7 +182,7 @@ void testTriggerChainTask() throws Exception {
assertThat(e.get().getData().getEnd()).isNotNull();
assertThat(e.get().getData().getExecutionCount()).isOne();
}
-
+
@Test
void testFailedIsOnRetry() throws Exception {
// GIVEN
@@ -179,6 +198,9 @@ void testFailedIsOnRetry() throws Exception {
trigger = triggerService.get(trigger.getKey()).get();
assertThat(trigger.getData().getRunAt()).isAfter(OffsetDateTime.now());
assertThat(trigger.getData().getStatus()).isEqualTo(TriggerStatus.WAITING);
+ // AND
+ assertThat(events.stream(TriggerSuccessEvent.class).count()).isZero();
+ assertThat(events.stream(TriggerFailedEvent.class).count()).isOne();
}
@Test
@@ -390,6 +412,9 @@ void testBadStateNoRetry() {
// WHEN
var triggerData = persistentTaskService.getLastTriggerData(t.getKey()).get();
assertThat(triggerData.getStatus()).isEqualTo(TriggerStatus.FAILED);
- assertThat(triggerData.getExceptionName()).isEqualTo(RuntimeException.class.getName());
+ assertThat(triggerData.getExceptionName()).isEqualTo(DeSerializationFailedException.class.getName());
+ // AND
+ assertThat(events.stream(TriggerSuccessEvent.class).count()).isZero();
+ assertThat(events.stream(TriggerFailedEvent.class).count()).isOne();
}
}
diff --git a/release.sh b/release.sh
new file mode 100755
index 000000000..5c8addba5
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# Fetch the current version from the POM
+MVN_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+echo "Current Maven version: $MVN_VERSION"
+
+# Release version
+RELEASE_VERSION=${MVN_VERSION%-SNAPSHOT}
+echo "Releasing version: $RELEASE_VERSION"
+
+# Set the new release version and tag it in Git
+mvn versions:set -DnewVersion="$RELEASE_VERSION" -DgenerateBackupPoms=false
+# Deploy the project
+mvn clean source:jar javadoc:jar deploy -Prelease
+# update git
+git add '**/pom.xml'
+git commit -am "$RELEASE_VERSION release"
+git tag -a "v$RELEASE_VERSION" -m "v$RELEASE_VERSION release"
+
+# Extract the current version number components
+IFS='.' read -r -a VERSION_PARTS <<< "$MVN_VERSION"
+MAJOR="${VERSION_PARTS[0]}"
+MINOR="${VERSION_PARTS[1]}"
+PATCH="${VERSION_PARTS[2]}"
+# Increment the patch version for the next snapshot
+PATCH=$((PATCH + 1))
+NEXT_VERSION="$MAJOR.$MINOR.$PATCH-SNAPSHOT"
+echo "Next development version: $NEXT_VERSION"
+
+# Set the next snapshot version and commit it in Git
+mvn versions:set -DnewVersion="$NEXT_VERSION" -DgenerateBackupPoms=false
+git add '**/pom.xml'
+git commit -am "Next development version $NEXT_VERSION"
+
+git push
+git push --tags
\ No newline at end of file