diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/api/task/PersistentTask.java b/core/src/main/java/org/sterl/spring/persistent_tasks/api/task/PersistentTask.java index e62257eda..342c10163 100644 --- a/core/src/main/java/org/sterl/spring/persistent_tasks/api/task/PersistentTask.java +++ b/core/src/main/java/org/sterl/spring/persistent_tasks/api/task/PersistentTask.java @@ -66,6 +66,5 @@ default boolean isTransactional() { * @param e the exception reason - could also be a {@link FailTaskNoRetryException} * @see Failed trigger */ - default void afterTriggerFailed(@Nullable T state, Exception e) { - } + default void afterTriggerFailed(@Nullable T state, Exception e) {} } diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java index fa2a5775b..541bc328c 100644 --- a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java +++ b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java @@ -7,6 +7,9 @@ import org.sterl.spring.persistent_tasks.shared.model.TriggerEntity; import org.sterl.spring.persistent_tasks.trigger.component.StateSerializer; +import lombok.extern.slf4j.Slf4j; + +@Slf4j public enum ToTrigger implements ExtendetConvert { INSTANCE; @@ -29,8 +32,25 @@ public Trigger convert(@NonNull HasTrigger hasData) { result.setRunAt(source.getRunAt()); result.setRunningDurationInMs(source.getRunningDurationInMs()); result.setStart(source.getStart()); - result.setState(SERIALIZER.deserialize(source.getState())); + try { + result.setState(SERIALIZER.deserialize(source.getState())); + } catch (Exception e) { + var info = """ + Failed to deserialize state + This is most likely due to an incompatible code change. + Old states in the DB cannot be read anymore/deserialized and cast to the given class. + """; + result.setState(new FailedToReadStateInfo(e.getMessage(), info)); + log.warn(""" + Failed to deserialize state of {}. + This is most likely due to an incompatible code change. + Old states in the DB cannot be read anymore/deserialized and cast to the given class. + {}""", + source.getKey(), e.getMessage()); + } result.setStatus(source.getStatus()); return result; } + + record FailedToReadStateInfo(String message, String info) {} } \ No newline at end of file diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java index bd5f3dbb3..6ec08ba2f 100644 --- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java +++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java @@ -77,9 +77,9 @@ public Page searchTriggers(@Nullable TriggerSearch search, return result; } else { log.debug("Empty search={}, selecting all triggers for page={}", search, page); - return triggerRepository.findAll(page); + var result = triggerRepository.findAll(page); + return result; } - } public Page searchGroupedTriggers(@Nullable TriggerSearch search, Pageable page) { 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 410d70986..f2d7940fb 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 @@ -2,9 +2,11 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.ObjectInput; +import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; import java.io.Serializable; import org.sterl.spring.persistent_tasks.exception.SpringPersistentTaskException; @@ -50,7 +52,7 @@ public Serializable deserialize(byte[] bytes) { } var bis = new ByteArrayInputStream(bytes); - try (ObjectInput in = new ObjectInputStream(bis)) { + try (var in = new ContextClassLoaderObjectInputStream(bis)) { return (Serializable)in.readObject(); } catch (Exception ex) { throw new DeSerializationFailedException(bytes, ex); @@ -65,4 +67,18 @@ public Serializable deserializeOrNull(byte[] bytes) { return null; } } + + // needed for spring boot developer tools + // https://github.com/sterlp/spring-persistent-tasks/issues/19 + static class ContextClassLoaderObjectInputStream extends ObjectInputStream { + ContextClassLoaderObjectInputStream(InputStream in) throws IOException { + super(in); + } + @Override + protected Class resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException { + return Class.forName(desc.getName(), false, + Thread.currentThread().getContextClassLoader()); + } + } } diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/api/TriggerResourceTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/api/TriggerResourceTest.java index e4299f307..82e854b65 100644 --- a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/api/TriggerResourceTest.java +++ b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/api/TriggerResourceTest.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; +import com.github.f4b6a3.uuid.UuidCreator; class TriggerResourceTest extends AbstractSpringTest { @@ -217,6 +218,27 @@ void testGroupSearch() throws JsonMappingException, JsonProcessingException { assertThat(result.getBody()).contains("\"groupByValue\":\"a1\""); } + @Test + void testReadIncompatibleTriggerState() { + // GIVEN we have a bad state we cannot read in the DB + var key = UuidCreator.getTimeOrdered().toString(); + trx.execute(t -> { + return triggerRepository.save(new RunningTriggerEntity( + new TriggerKey(key, "slowTask") + ).withState(new byte[] {12, 54, 33}) + ); + }); + + // WHEN + assertThat(triggerRepository.count()).isGreaterThanOrEqualTo(1L); + var response = template.getForEntity(baseUrl, String.class); + + // THEN + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains(key); + assertThat(response.getBody()).contains("Failed to deserialize state of length"); + } + private RunningTriggerEntity createStatus(TriggerKey key, TriggerStatus status) { final var now = OffsetDateTime.now(); final var isCancel = status == TriggerStatus.CANCELED; diff --git a/example/pom.xml b/example/pom.xml index 40a804def..11b756a74 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -17,7 +17,7 @@ - 2.2.3-SNAPSHOT + 2.2.4-SNAPSHOT @@ -48,12 +48,6 @@ org.springframework.boot spring-boot-starter-web - - - org.springframework.data - spring-data-envers - - org.springframework.boot @@ -69,7 +63,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.6.0 + 2.8.13 @@ -98,5 +92,24 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + \ No newline at end of file