From 948fe21d1c40b2154307dd06bc18f5be27a27cd7 Mon Sep 17 00:00:00 2001 From: Elena Mokeeva Date: Tue, 29 Aug 2023 10:03:48 +0200 Subject: [PATCH] Closes #2366 - add TaskEndstatePreprocessor --- .../task/complete/CancelTaskAccTest.java | 35 ++++++++ .../complete/CompleteTaskWithSpiAccTest.java | 40 +++++++++ .../task/complete/TerminateTaskAccTest.java | 36 ++++++++ .../internal/InternalTaskanaEngine.java | 8 ++ .../common/internal/TaskanaEngineImpl.java | 9 ++ .../task/api/TaskEndstatePreprocessor.java | 34 ++++++++ .../TaskEndstatePreprocessorManager.java | 39 +++++++++ .../task/internal/TaskServiceImpl.java | 84 ++++++++++++------- .../util/ServiceProviderExtractor.java | 4 +- 9 files changed, 259 insertions(+), 30 deletions(-) create mode 100644 lib/taskana-core/src/main/java/pro/taskana/spi/task/api/TaskEndstatePreprocessor.java create mode 100644 lib/taskana-core/src/main/java/pro/taskana/spi/task/internal/TaskEndstatePreprocessorManager.java diff --git a/lib/taskana-core-test/src/test/java/acceptance/task/complete/CancelTaskAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/task/complete/CancelTaskAccTest.java index eab92363c..30e8780a0 100644 --- a/lib/taskana-core-test/src/test/java/acceptance/task/complete/CancelTaskAccTest.java +++ b/lib/taskana-core-test/src/test/java/acceptance/task/complete/CancelTaskAccTest.java @@ -6,18 +6,23 @@ import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification; import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference; import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket; +import acceptance.task.complete.CompleteTaskWithSpiAccTest.SetCustomAttributeToEndstate; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.function.ThrowingConsumer; import pro.taskana.classification.api.ClassificationService; import pro.taskana.classification.api.models.ClassificationSummary; import pro.taskana.common.internal.util.Triplet; +import pro.taskana.spi.task.api.TaskEndstatePreprocessor; import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskState; import pro.taskana.task.api.exceptions.InvalidTaskStateException; @@ -26,6 +31,7 @@ import pro.taskana.task.api.models.Task; import pro.taskana.testapi.DefaultTestEntities; import pro.taskana.testapi.TaskanaInject; import pro.taskana.testapi.TaskanaIntegrationTest; +import pro.taskana.testapi.WithServiceProvider; import pro.taskana.testapi.builder.TaskBuilder; import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder; import pro.taskana.testapi.security.WithAccessId; @@ -182,4 +188,33 @@ class CancelTaskAccTest { }; return DynamicTest.stream(list.iterator(), Triplet::getLeft, testCancelTask); } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + @WithServiceProvider( + serviceProviderInterface = TaskEndstatePreprocessor.class, + serviceProviders = SetCustomAttributeToEndstate.class) + class ServiceProviderSetsCustomAttributeToCancelled { + + @TaskanaInject TaskService taskService; + + @WithAccessId(user = "user-1-2") + @Test + void should_SetCustomAttribute_When_UserCancelsTask() throws Exception { + Task task = + TaskBuilder.newTask() + .classificationSummary(defaultClassificationSummary) + .workbasketSummary(defaultWorkbasketSummary) + .state(TaskState.CLAIMED) + .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build()) + .buildAndStore(taskService); + Task processedTask = taskService.cancelTask(task.getId()); + assertThat(processedTask.getState()).isEqualTo(TaskState.CANCELLED); + assertThat(processedTask.getCustomAttributeMap()) + .containsEntry( + "camunda:attribute1", + "{\"valueInfo\":{\"objectTypeName\":\"java.lang.String\"}," + + "\"type\":\"String\",\"value\":\"CANCELLED\"}"); + } + } } diff --git a/lib/taskana-core-test/src/test/java/acceptance/task/complete/CompleteTaskWithSpiAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/task/complete/CompleteTaskWithSpiAccTest.java index 864b78f25..740811c34 100644 --- a/lib/taskana-core-test/src/test/java/acceptance/task/complete/CompleteTaskWithSpiAccTest.java +++ b/lib/taskana-core-test/src/test/java/acceptance/task/complete/CompleteTaskWithSpiAccTest.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -17,6 +18,7 @@ import org.junit.jupiter.api.function.ThrowingConsumer; import pro.taskana.classification.api.ClassificationService; import pro.taskana.classification.api.models.ClassificationSummary; import pro.taskana.spi.task.api.ReviewRequiredProvider; +import pro.taskana.spi.task.api.TaskEndstatePreprocessor; import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskState; import pro.taskana.task.api.models.ObjectReference; @@ -91,6 +93,21 @@ class CompleteTaskWithSpiAccTest { } } + static class SetCustomAttributeToEndstate implements TaskEndstatePreprocessor { + @Override + public Task processTaskBeforeEndstate(Task task) { + String endstate = task.getState().toString(); + task.getCustomAttributeMap() + .put( + "camunda:attribute1", + "{\"valueInfo\":{\"objectTypeName\":\"java.lang.String\"}," + + "\"type\":\"String\",\"value\":\"" + + endstate + + "\"}"); + return task; + } + } + @Nested @TestInstance(Lifecycle.PER_CLASS) @WithServiceProvider( @@ -171,4 +188,27 @@ class CompleteTaskWithSpiAccTest { tasks.iterator(), t -> "Try to complete " + t.getState().name() + " Task", test); } } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + @WithServiceProvider( + serviceProviderInterface = TaskEndstatePreprocessor.class, + serviceProviders = SetCustomAttributeToEndstate.class) + class ServiceProviderSetsCustomAttributeToCompleted { + + @TaskanaInject TaskService taskService; + + @WithAccessId(user = "user-1-1") + @Test + void should_SetCustomAttribute_When_UserCompletesTask() throws Exception { + Task task = createTaskClaimedByUser("user-1-1").buildAndStore(taskService); + Task processedTask = taskService.completeTask(task.getId()); + assertThat(processedTask.getState()).isEqualTo(TaskState.COMPLETED); + assertThat(processedTask.getCustomAttributeMap()) + .containsEntry( + "camunda:attribute1", + "{\"valueInfo\":{\"objectTypeName\":\"java.lang.String\"}," + + "\"type\":\"String\",\"value\":\"COMPLETED\"}"); + } + } } diff --git a/lib/taskana-core-test/src/test/java/acceptance/task/complete/TerminateTaskAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/task/complete/TerminateTaskAccTest.java index c951a85e1..8895e8078 100644 --- a/lib/taskana-core-test/src/test/java/acceptance/task/complete/TerminateTaskAccTest.java +++ b/lib/taskana-core-test/src/test/java/acceptance/task/complete/TerminateTaskAccTest.java @@ -5,12 +5,17 @@ import static org.assertj.core.api.Assertions.catchThrowableOfType; import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification; import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket; +import acceptance.task.complete.CompleteTaskWithSpiAccTest.SetCustomAttributeToEndstate; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.function.ThrowingConsumer; import pro.taskana.classification.api.ClassificationService; @@ -19,6 +24,7 @@ import pro.taskana.common.api.TaskanaRole; import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.security.CurrentUserContext; import pro.taskana.common.internal.util.Triplet; +import pro.taskana.spi.task.api.TaskEndstatePreprocessor; import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskState; import pro.taskana.task.api.exceptions.InvalidTaskStateException; @@ -26,6 +32,7 @@ import pro.taskana.task.api.models.Task; import pro.taskana.testapi.DefaultTestEntities; import pro.taskana.testapi.TaskanaInject; import pro.taskana.testapi.TaskanaIntegrationTest; +import pro.taskana.testapi.WithServiceProvider; import pro.taskana.testapi.builder.TaskBuilder; import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder; import pro.taskana.testapi.security.WithAccessId; @@ -157,4 +164,33 @@ class TerminateTaskAccTest { return DynamicTest.stream(testValues.iterator(), Triplet::getLeft, test); } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + @WithServiceProvider( + serviceProviderInterface = TaskEndstatePreprocessor.class, + serviceProviders = SetCustomAttributeToEndstate.class) + class ServiceProviderSetsCustomAttributeToTerminated { + + @TaskanaInject TaskService taskService; + + @WithAccessId(user = "admin") + @Test + void should_SetCustomAttribute_When_UserTerminatesTask() throws Exception { + Task task = + TaskBuilder.newTask() + .classificationSummary(defaultClassificationSummary) + .workbasketSummary(defaultWorkbasketSummary) + .state(TaskState.READY) + .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build()) + .buildAndStore(taskService); + Task processedTask = taskService.terminateTask(task.getId()); + assertThat(processedTask.getState()).isEqualTo(TaskState.TERMINATED); + assertThat(processedTask.getCustomAttributeMap()) + .containsEntry( + "camunda:attribute1", + "{\"valueInfo\":{\"objectTypeName\":\"java.lang.String\"}," + + "\"type\":\"String\",\"value\":\"TERMINATED\"}"); + } + } } diff --git a/lib/taskana-core/src/main/java/pro/taskana/common/internal/InternalTaskanaEngine.java b/lib/taskana-core/src/main/java/pro/taskana/common/internal/InternalTaskanaEngine.java index 3f67f91d5..dda149d16 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/common/internal/InternalTaskanaEngine.java +++ b/lib/taskana-core/src/main/java/pro/taskana/common/internal/InternalTaskanaEngine.java @@ -12,6 +12,7 @@ import pro.taskana.spi.task.internal.BeforeRequestChangesManager; import pro.taskana.spi.task.internal.BeforeRequestReviewManager; import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager; import pro.taskana.spi.task.internal.ReviewRequiredManager; +import pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager; /** * FOR INTERNAL USE ONLY. @@ -144,4 +145,11 @@ public interface InternalTaskanaEngine { * @return the {@linkplain AfterRequestChangesManager} instance */ AfterRequestChangesManager getAfterRequestChangesManager(); + + /** + * Retrieves the {@linkplain pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager}. + * + * @return the {@linkplain pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager} instance + */ + TaskEndstatePreprocessorManager getTaskEndstatePreprocessorManager(); } diff --git a/lib/taskana-core/src/main/java/pro/taskana/common/internal/TaskanaEngineImpl.java b/lib/taskana-core/src/main/java/pro/taskana/common/internal/TaskanaEngineImpl.java index f59863ad6..73d5da160 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/common/internal/TaskanaEngineImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/common/internal/TaskanaEngineImpl.java @@ -65,6 +65,7 @@ import pro.taskana.spi.task.internal.BeforeRequestChangesManager; import pro.taskana.spi.task.internal.BeforeRequestReviewManager; import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager; import pro.taskana.spi.task.internal.ReviewRequiredManager; +import pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager; import pro.taskana.task.api.TaskService; import pro.taskana.task.internal.AttachmentMapper; import pro.taskana.task.internal.ObjectReferenceMapper; @@ -98,6 +99,8 @@ public class TaskanaEngineImpl implements TaskanaEngine { private final AfterRequestReviewManager afterRequestReviewManager; private final BeforeRequestChangesManager beforeRequestChangesManager; private final AfterRequestChangesManager afterRequestChangesManager; + private final TaskEndstatePreprocessorManager taskEndstatePreprocessorManager; + private final InternalTaskanaEngineImpl internalTaskanaEngineImpl; private final WorkingTimeCalculator workingTimeCalculator; private final HistoryEventManager historyEventManager; @@ -176,6 +179,7 @@ public class TaskanaEngineImpl implements TaskanaEngine { afterRequestReviewManager = new AfterRequestReviewManager(this); beforeRequestChangesManager = new BeforeRequestChangesManager(this); afterRequestChangesManager = new AfterRequestChangesManager(this); + taskEndstatePreprocessorManager = new TaskEndstatePreprocessorManager(); // don't remove, to reset possible explicit mode this.mode = connectionManagementMode; @@ -624,5 +628,10 @@ public class TaskanaEngineImpl implements TaskanaEngine { public AfterRequestChangesManager getAfterRequestChangesManager() { return afterRequestChangesManager; } + + @Override + public TaskEndstatePreprocessorManager getTaskEndstatePreprocessorManager() { + return taskEndstatePreprocessorManager; + } } } diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/task/api/TaskEndstatePreprocessor.java b/lib/taskana-core/src/main/java/pro/taskana/spi/task/api/TaskEndstatePreprocessor.java new file mode 100644 index 000000000..66657dc4b --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/spi/task/api/TaskEndstatePreprocessor.java @@ -0,0 +1,34 @@ +package pro.taskana.spi.task.api; + +import pro.taskana.task.api.models.Task; + +/** + * The TaskEndstatePreprocessor allows to implement customized behaviour before the given + * {@linkplain Task} goes into an {@linkplain pro.taskana.task.api.TaskState#END_STATES end state} + * (cancelled, terminated or completed). + */ +public interface TaskEndstatePreprocessor { + + /** + * Perform any action before a {@linkplain Task} goes into an {@linkplain + * pro.taskana.task.api.TaskState#END_STATES end state}. A {@linkplain Task} goes into an end + * state at the end of the following methods: {@linkplain + * pro.taskana.task.api.TaskService#completeTask(String)}, {@linkplain + * pro.taskana.task.api.TaskService#cancelTask(String)}, {@linkplain + * pro.taskana.task.api.TaskService#terminateTask(String)}. + * + *

This SPI is executed within the same transaction staple as {@linkplain + * pro.taskana.task.api.TaskService#completeTask(String)}, {@linkplain + * pro.taskana.task.api.TaskService#cancelTask(String)}, {@linkplain + * pro.taskana.task.api.TaskService#terminateTask(String)}. + * + *

This SPI is executed with the same {@linkplain + * pro.taskana.common.api.security.UserPrincipal} and {@linkplain + * pro.taskana.common.api.security.GroupPrincipal} as in the methods mentioned above. + * + * @param taskToProcess the {@linkplain Task} to preprocess + * @return the modified {@linkplain Task}. IMPORTANT: persistent changes to the {@linkplain + * Task} have to be managed by the service provider + */ + Task processTaskBeforeEndstate(Task taskToProcess); +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/task/internal/TaskEndstatePreprocessorManager.java b/lib/taskana-core/src/main/java/pro/taskana/spi/task/internal/TaskEndstatePreprocessorManager.java new file mode 100644 index 000000000..5873e68ba --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/spi/task/internal/TaskEndstatePreprocessorManager.java @@ -0,0 +1,39 @@ +package pro.taskana.spi.task.internal; + +import static pro.taskana.common.internal.util.CheckedConsumer.wrap; + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import pro.taskana.common.internal.util.SpiLoader; +import pro.taskana.spi.task.api.TaskEndstatePreprocessor; +import pro.taskana.task.api.models.Task; + +public class TaskEndstatePreprocessorManager { + + private static final Logger LOGGER = + LoggerFactory.getLogger(TaskEndstatePreprocessorManager.class); + private final List taskEndstatePreprocessors; + + public TaskEndstatePreprocessorManager() { + taskEndstatePreprocessors = SpiLoader.load(TaskEndstatePreprocessor.class); + for (TaskEndstatePreprocessor preprocessor : taskEndstatePreprocessors) { + LOGGER.info( + "Registered TaskEndstatePreprocessor provider: {}", preprocessor.getClass().getName()); + } + if (taskEndstatePreprocessors.isEmpty()) { + LOGGER.info("No TaskEndstatePreprocessor found. Running without TaskEndstatePreprocessor."); + } + } + + public Task processTaskBeforeEndstate(Task taskToProcess) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Sending task to TaskEndstatePreprocessor providers: {}", taskToProcess); + } + taskEndstatePreprocessors.forEach( + wrap( + taskEndstatePreprocessor -> + taskEndstatePreprocessor.processTaskBeforeEndstate(taskToProcess))); + return taskToProcess; + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java index 3d76f8bfb..07a98dcd9 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java @@ -58,6 +58,7 @@ import pro.taskana.spi.task.internal.BeforeRequestChangesManager; import pro.taskana.spi.task.internal.BeforeRequestReviewManager; import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager; import pro.taskana.spi.task.internal.ReviewRequiredManager; +import pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager; import pro.taskana.task.api.CallbackState; import pro.taskana.task.api.TaskCommentQuery; import pro.taskana.task.api.TaskCustomField; @@ -123,6 +124,7 @@ public class TaskServiceImpl implements TaskService { private final AfterRequestReviewManager afterRequestReviewManager; private final BeforeRequestChangesManager beforeRequestChangesManager; private final AfterRequestChangesManager afterRequestChangesManager; + private final TaskEndstatePreprocessorManager taskEndstatePreprocessorManager; public TaskServiceImpl( InternalTaskanaEngine taskanaEngine, @@ -146,6 +148,7 @@ public class TaskServiceImpl implements TaskService { this.afterRequestReviewManager = taskanaEngine.getAfterRequestReviewManager(); this.beforeRequestChangesManager = taskanaEngine.getBeforeRequestChangesManager(); this.afterRequestChangesManager = taskanaEngine.getAfterRequestChangesManager(); + this.taskEndstatePreprocessorManager = taskanaEngine.getTaskEndstatePreprocessorManager(); this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this); this.taskCommentService = new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, userMapper, this); @@ -874,12 +877,35 @@ public class TaskServiceImpl implements TaskService { public Task cancelTask(String taskId) throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException { - Task cancelledTask; + TaskImpl cancelledTask; try { taskanaEngine.openConnection(); - cancelledTask = terminateCancelCommonActions(taskId, TaskState.CANCELLED); + if (taskId == null || taskId.isEmpty()) { + throw new TaskNotFoundException(taskId); + } + cancelledTask = (TaskImpl) getTask(taskId); + TaskState state = cancelledTask.getState(); + if (state.isEndState()) { + throw new InvalidTaskStateException( + taskId, + state, + TaskState.READY, + TaskState.CLAIMED, + TaskState.READY_FOR_REVIEW, + TaskState.IN_REVIEW); + } + terminateCancelCommonActions(cancelledTask, TaskState.CANCELLED); + cancelledTask = + (TaskImpl) taskEndstatePreprocessorManager.processTaskBeforeEndstate(cancelledTask); + taskMapper.update(cancelledTask); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Task '{}' cancelled by user '{}'.", + taskId, + taskanaEngine.getEngine().getCurrentUserContext().getUserid()); + } if (historyEventManager.isEnabled()) { historyEventManager.createEvent( new TaskCancelledEvent( @@ -901,12 +927,35 @@ public class TaskServiceImpl implements TaskService { taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN); - Task terminatedTask; + TaskImpl terminatedTask; try { taskanaEngine.openConnection(); - terminatedTask = terminateCancelCommonActions(taskId, TaskState.TERMINATED); + if (taskId == null || taskId.isEmpty()) { + throw new TaskNotFoundException(taskId); + } + terminatedTask = (TaskImpl) getTask(taskId); + TaskState state = terminatedTask.getState(); + if (state.isEndState()) { + throw new InvalidTaskStateException( + taskId, + state, + TaskState.READY, + TaskState.CLAIMED, + TaskState.READY_FOR_REVIEW, + TaskState.IN_REVIEW); + } + terminateCancelCommonActions(terminatedTask, TaskState.TERMINATED); + terminatedTask = + (TaskImpl) taskEndstatePreprocessorManager.processTaskBeforeEndstate(terminatedTask); + taskMapper.update(terminatedTask); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Task '{}' cancelled by user '{}'.", + taskId, + taskanaEngine.getEngine().getCurrentUserContext().getUserid()); + } if (historyEventManager.isEnabled()) { historyEventManager.createEvent( new TaskTerminatedEvent( @@ -1208,35 +1257,11 @@ public class TaskServiceImpl implements TaskService { newTaskImpl.setModified(Instant.now()); } - private TaskImpl terminateCancelCommonActions(String taskId, TaskState targetState) - throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException { - if (taskId == null || taskId.isEmpty()) { - throw new TaskNotFoundException(taskId); - } - TaskImpl task = (TaskImpl) getTask(taskId); - TaskState state = task.getState(); - if (state.isEndState()) { - throw new InvalidTaskStateException( - taskId, - state, - TaskState.READY, - TaskState.CLAIMED, - TaskState.READY_FOR_REVIEW, - TaskState.IN_REVIEW); - } - + private static void terminateCancelCommonActions(TaskImpl task, TaskState targetState) { Instant now = Instant.now(); task.setModified(now); task.setCompleted(now); task.setState(targetState); - taskMapper.update(task); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "Task '{}' cancelled by user '{}'.", - taskId, - taskanaEngine.getEngine().getCurrentUserContext().getUserid()); - } - return task; } private Task claim(String taskId, boolean forceClaim) @@ -1552,6 +1577,7 @@ public class TaskServiceImpl implements TaskService { Instant now = Instant.now(); completeActionsOnTask(task, userId, now); + task = (TaskImpl) taskEndstatePreprocessorManager.processTaskBeforeEndstate(task); taskMapper.update(task); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId); diff --git a/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java b/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java index c2f65b0ba..730782d6c 100644 --- a/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java +++ b/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java @@ -20,6 +20,7 @@ import pro.taskana.spi.task.api.BeforeRequestChangesProvider; import pro.taskana.spi.task.api.BeforeRequestReviewProvider; import pro.taskana.spi.task.api.CreateTaskPreprocessor; import pro.taskana.spi.task.api.ReviewRequiredProvider; +import pro.taskana.spi.task.api.TaskEndstatePreprocessor; import pro.taskana.testapi.WithServiceProvider; public class ServiceProviderExtractor { @@ -34,7 +35,8 @@ public class ServiceProviderExtractor { BeforeRequestReviewProvider.class, AfterRequestReviewProvider.class, BeforeRequestChangesProvider.class, - AfterRequestChangesProvider.class); + AfterRequestChangesProvider.class, + TaskEndstatePreprocessor.class); private ServiceProviderExtractor() { throw new IllegalStateException("utility class");