Closes #2366 - add TaskEndstatePreprocessor

This commit is contained in:
Elena Mokeeva 2023-08-29 10:03:48 +02:00 committed by Elena Mokeeva
parent 24fb36a7df
commit 948fe21d1c
9 changed files with 259 additions and 30 deletions

View File

@ -6,18 +6,23 @@ import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference; import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket; import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket;
import acceptance.task.complete.CompleteTaskWithSpiAccTest.SetCustomAttributeToEndstate;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory; 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.TestTemplate;
import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.function.ThrowingConsumer;
import pro.taskana.classification.api.ClassificationService; import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.models.ClassificationSummary; import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.common.internal.util.Triplet; 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.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.InvalidTaskStateException; 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.DefaultTestEntities;
import pro.taskana.testapi.TaskanaInject; import pro.taskana.testapi.TaskanaInject;
import pro.taskana.testapi.TaskanaIntegrationTest; import pro.taskana.testapi.TaskanaIntegrationTest;
import pro.taskana.testapi.WithServiceProvider;
import pro.taskana.testapi.builder.TaskBuilder; import pro.taskana.testapi.builder.TaskBuilder;
import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder; import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder;
import pro.taskana.testapi.security.WithAccessId; import pro.taskana.testapi.security.WithAccessId;
@ -182,4 +188,33 @@ class CancelTaskAccTest {
}; };
return DynamicTest.stream(list.iterator(), Triplet::getLeft, testCancelTask); 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\"}");
}
}
} }

View File

@ -10,6 +10,7 @@ import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle; 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.ClassificationService;
import pro.taskana.classification.api.models.ClassificationSummary; import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.spi.task.api.ReviewRequiredProvider; 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.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.models.ObjectReference; 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 @Nested
@TestInstance(Lifecycle.PER_CLASS) @TestInstance(Lifecycle.PER_CLASS)
@WithServiceProvider( @WithServiceProvider(
@ -171,4 +188,27 @@ class CompleteTaskWithSpiAccTest {
tasks.iterator(), t -> "Try to complete " + t.getState().name() + " Task", test); 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\"}");
}
}
} }

View File

@ -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.defaultTestClassification;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket; import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket;
import acceptance.task.complete.CompleteTaskWithSpiAccTest.SetCustomAttributeToEndstate;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DynamicTest; 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.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.TestTemplate;
import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.function.ThrowingConsumer;
import pro.taskana.classification.api.ClassificationService; 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.exceptions.NotAuthorizedException;
import pro.taskana.common.api.security.CurrentUserContext; import pro.taskana.common.api.security.CurrentUserContext;
import pro.taskana.common.internal.util.Triplet; 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.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.InvalidTaskStateException; 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.DefaultTestEntities;
import pro.taskana.testapi.TaskanaInject; import pro.taskana.testapi.TaskanaInject;
import pro.taskana.testapi.TaskanaIntegrationTest; import pro.taskana.testapi.TaskanaIntegrationTest;
import pro.taskana.testapi.WithServiceProvider;
import pro.taskana.testapi.builder.TaskBuilder; import pro.taskana.testapi.builder.TaskBuilder;
import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder; import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder;
import pro.taskana.testapi.security.WithAccessId; import pro.taskana.testapi.security.WithAccessId;
@ -157,4 +164,33 @@ class TerminateTaskAccTest {
return DynamicTest.stream(testValues.iterator(), Triplet::getLeft, test); 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\"}");
}
}
} }

View File

@ -12,6 +12,7 @@ import pro.taskana.spi.task.internal.BeforeRequestChangesManager;
import pro.taskana.spi.task.internal.BeforeRequestReviewManager; import pro.taskana.spi.task.internal.BeforeRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager; import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
import pro.taskana.spi.task.internal.ReviewRequiredManager; import pro.taskana.spi.task.internal.ReviewRequiredManager;
import pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager;
/** /**
* FOR INTERNAL USE ONLY. * FOR INTERNAL USE ONLY.
@ -144,4 +145,11 @@ public interface InternalTaskanaEngine {
* @return the {@linkplain AfterRequestChangesManager} instance * @return the {@linkplain AfterRequestChangesManager} instance
*/ */
AfterRequestChangesManager getAfterRequestChangesManager(); AfterRequestChangesManager getAfterRequestChangesManager();
/**
* Retrieves the {@linkplain pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager}.
*
* @return the {@linkplain pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager} instance
*/
TaskEndstatePreprocessorManager getTaskEndstatePreprocessorManager();
} }

View File

@ -65,6 +65,7 @@ import pro.taskana.spi.task.internal.BeforeRequestChangesManager;
import pro.taskana.spi.task.internal.BeforeRequestReviewManager; import pro.taskana.spi.task.internal.BeforeRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager; import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
import pro.taskana.spi.task.internal.ReviewRequiredManager; import pro.taskana.spi.task.internal.ReviewRequiredManager;
import pro.taskana.spi.task.internal.TaskEndstatePreprocessorManager;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
import pro.taskana.task.internal.AttachmentMapper; import pro.taskana.task.internal.AttachmentMapper;
import pro.taskana.task.internal.ObjectReferenceMapper; import pro.taskana.task.internal.ObjectReferenceMapper;
@ -98,6 +99,8 @@ public class TaskanaEngineImpl implements TaskanaEngine {
private final AfterRequestReviewManager afterRequestReviewManager; private final AfterRequestReviewManager afterRequestReviewManager;
private final BeforeRequestChangesManager beforeRequestChangesManager; private final BeforeRequestChangesManager beforeRequestChangesManager;
private final AfterRequestChangesManager afterRequestChangesManager; private final AfterRequestChangesManager afterRequestChangesManager;
private final TaskEndstatePreprocessorManager taskEndstatePreprocessorManager;
private final InternalTaskanaEngineImpl internalTaskanaEngineImpl; private final InternalTaskanaEngineImpl internalTaskanaEngineImpl;
private final WorkingTimeCalculator workingTimeCalculator; private final WorkingTimeCalculator workingTimeCalculator;
private final HistoryEventManager historyEventManager; private final HistoryEventManager historyEventManager;
@ -176,6 +179,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
afterRequestReviewManager = new AfterRequestReviewManager(this); afterRequestReviewManager = new AfterRequestReviewManager(this);
beforeRequestChangesManager = new BeforeRequestChangesManager(this); beforeRequestChangesManager = new BeforeRequestChangesManager(this);
afterRequestChangesManager = new AfterRequestChangesManager(this); afterRequestChangesManager = new AfterRequestChangesManager(this);
taskEndstatePreprocessorManager = new TaskEndstatePreprocessorManager();
// don't remove, to reset possible explicit mode // don't remove, to reset possible explicit mode
this.mode = connectionManagementMode; this.mode = connectionManagementMode;
@ -624,5 +628,10 @@ public class TaskanaEngineImpl implements TaskanaEngine {
public AfterRequestChangesManager getAfterRequestChangesManager() { public AfterRequestChangesManager getAfterRequestChangesManager() {
return afterRequestChangesManager; return afterRequestChangesManager;
} }
@Override
public TaskEndstatePreprocessorManager getTaskEndstatePreprocessorManager() {
return taskEndstatePreprocessorManager;
}
} }
} }

View File

@ -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)}.
*
* <p>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)}.
*
* <p>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}. <b>IMPORTANT:</b> persistent changes to the {@linkplain
* Task} have to be managed by the service provider
*/
Task processTaskBeforeEndstate(Task taskToProcess);
}

View File

@ -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<TaskEndstatePreprocessor> 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;
}
}

View File

@ -58,6 +58,7 @@ import pro.taskana.spi.task.internal.BeforeRequestChangesManager;
import pro.taskana.spi.task.internal.BeforeRequestReviewManager; import pro.taskana.spi.task.internal.BeforeRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager; import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
import pro.taskana.spi.task.internal.ReviewRequiredManager; 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.CallbackState;
import pro.taskana.task.api.TaskCommentQuery; import pro.taskana.task.api.TaskCommentQuery;
import pro.taskana.task.api.TaskCustomField; import pro.taskana.task.api.TaskCustomField;
@ -123,6 +124,7 @@ public class TaskServiceImpl implements TaskService {
private final AfterRequestReviewManager afterRequestReviewManager; private final AfterRequestReviewManager afterRequestReviewManager;
private final BeforeRequestChangesManager beforeRequestChangesManager; private final BeforeRequestChangesManager beforeRequestChangesManager;
private final AfterRequestChangesManager afterRequestChangesManager; private final AfterRequestChangesManager afterRequestChangesManager;
private final TaskEndstatePreprocessorManager taskEndstatePreprocessorManager;
public TaskServiceImpl( public TaskServiceImpl(
InternalTaskanaEngine taskanaEngine, InternalTaskanaEngine taskanaEngine,
@ -146,6 +148,7 @@ public class TaskServiceImpl implements TaskService {
this.afterRequestReviewManager = taskanaEngine.getAfterRequestReviewManager(); this.afterRequestReviewManager = taskanaEngine.getAfterRequestReviewManager();
this.beforeRequestChangesManager = taskanaEngine.getBeforeRequestChangesManager(); this.beforeRequestChangesManager = taskanaEngine.getBeforeRequestChangesManager();
this.afterRequestChangesManager = taskanaEngine.getAfterRequestChangesManager(); this.afterRequestChangesManager = taskanaEngine.getAfterRequestChangesManager();
this.taskEndstatePreprocessorManager = taskanaEngine.getTaskEndstatePreprocessorManager();
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this); this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
this.taskCommentService = this.taskCommentService =
new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, userMapper, this); new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, userMapper, this);
@ -874,12 +877,35 @@ public class TaskServiceImpl implements TaskService {
public Task cancelTask(String taskId) public Task cancelTask(String taskId)
throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException { throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException {
Task cancelledTask; TaskImpl cancelledTask;
try { try {
taskanaEngine.openConnection(); 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()) { if (historyEventManager.isEnabled()) {
historyEventManager.createEvent( historyEventManager.createEvent(
new TaskCancelledEvent( new TaskCancelledEvent(
@ -901,12 +927,35 @@ public class TaskServiceImpl implements TaskService {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN);
Task terminatedTask; TaskImpl terminatedTask;
try { try {
taskanaEngine.openConnection(); 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()) { if (historyEventManager.isEnabled()) {
historyEventManager.createEvent( historyEventManager.createEvent(
new TaskTerminatedEvent( new TaskTerminatedEvent(
@ -1208,35 +1257,11 @@ public class TaskServiceImpl implements TaskService {
newTaskImpl.setModified(Instant.now()); newTaskImpl.setModified(Instant.now());
} }
private TaskImpl terminateCancelCommonActions(String taskId, TaskState targetState) private static void terminateCancelCommonActions(TaskImpl task, 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);
}
Instant now = Instant.now(); Instant now = Instant.now();
task.setModified(now); task.setModified(now);
task.setCompleted(now); task.setCompleted(now);
task.setState(targetState); 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) private Task claim(String taskId, boolean forceClaim)
@ -1552,6 +1577,7 @@ public class TaskServiceImpl implements TaskService {
Instant now = Instant.now(); Instant now = Instant.now();
completeActionsOnTask(task, userId, now); completeActionsOnTask(task, userId, now);
task = (TaskImpl) taskEndstatePreprocessorManager.processTaskBeforeEndstate(task);
taskMapper.update(task); taskMapper.update(task);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId); LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId);

View File

@ -20,6 +20,7 @@ import pro.taskana.spi.task.api.BeforeRequestChangesProvider;
import pro.taskana.spi.task.api.BeforeRequestReviewProvider; import pro.taskana.spi.task.api.BeforeRequestReviewProvider;
import pro.taskana.spi.task.api.CreateTaskPreprocessor; import pro.taskana.spi.task.api.CreateTaskPreprocessor;
import pro.taskana.spi.task.api.ReviewRequiredProvider; import pro.taskana.spi.task.api.ReviewRequiredProvider;
import pro.taskana.spi.task.api.TaskEndstatePreprocessor;
import pro.taskana.testapi.WithServiceProvider; import pro.taskana.testapi.WithServiceProvider;
public class ServiceProviderExtractor { public class ServiceProviderExtractor {
@ -34,7 +35,8 @@ public class ServiceProviderExtractor {
BeforeRequestReviewProvider.class, BeforeRequestReviewProvider.class,
AfterRequestReviewProvider.class, AfterRequestReviewProvider.class,
BeforeRequestChangesProvider.class, BeforeRequestChangesProvider.class,
AfterRequestChangesProvider.class); AfterRequestChangesProvider.class,
TaskEndstatePreprocessor.class);
private ServiceProviderExtractor() { private ServiceProviderExtractor() {
throw new IllegalStateException("utility class"); throw new IllegalStateException("utility class");