TSK-1937: implemented AfterRequestReviewProvider

Co-authored-by: ryzheboka <25465835+ryzheboka@users.noreply.github.com>
This commit is contained in:
Mustapha Zorgati 2022-08-10 02:39:29 +02:00
parent b1166939cd
commit 1f9d20f76f
7 changed files with 384 additions and 1 deletions

View File

@ -0,0 +1,267 @@
package acceptance.task.requestreview;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static pro.taskana.common.internal.util.CheckedSupplier.wrap;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import pro.taskana.TaskanaEngineConfiguration;
import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.models.Classification;
import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.jobs.PlainJavaTransactionProvider;
import pro.taskana.spi.task.api.AfterRequestReviewProvider;
import pro.taskana.task.api.TaskCustomField;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.models.ObjectReference;
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;
import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.models.WorkbasketSummary;
@TaskanaIntegrationTest
public class RequestReviewWithSpiAccTest {
private static final String NEW_WORKBASKET_KEY = "W1000";
ClassificationSummary defaultClassificationSummary;
WorkbasketSummary defaultWorkbasketSummary;
ObjectReference defaultObjectReference;
WorkbasketSummary newWorkbasket;
@WithAccessId(user = "businessadmin")
@BeforeAll
void setup(ClassificationService classificationService, WorkbasketService workbasketService)
throws Exception {
defaultClassificationSummary =
defaultTestClassification().buildAndStoreAsSummary(classificationService);
defaultWorkbasketSummary = defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService);
newWorkbasket =
defaultTestWorkbasket().key(NEW_WORKBASKET_KEY).buildAndStoreAsSummary(workbasketService);
WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
.workbasketId(defaultWorkbasketSummary.getId())
.accessId("user-1-1")
.permission(WorkbasketPermission.READ)
.permission(WorkbasketPermission.APPEND)
.permission(WorkbasketPermission.TRANSFER)
.buildAndStore(workbasketService);
WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
.workbasketId(newWorkbasket.getId())
.accessId("user-1-1")
.permission(WorkbasketPermission.READ)
.permission(WorkbasketPermission.APPEND)
.buildAndStore(workbasketService);
defaultObjectReference = DefaultTestEntities.defaultTestObjectReference().build();
}
private TaskBuilder createTaskClaimedByUser(String owner) {
return createDefaultTask().owner(owner).state(TaskState.CLAIMED);
}
private TaskBuilder createDefaultTask() {
return TaskBuilder.newTask()
.classificationSummary(defaultClassificationSummary)
.workbasketSummary(defaultWorkbasketSummary)
.primaryObjRef(defaultObjectReference);
}
static class ExceptionThrower implements AfterRequestReviewProvider {
@Override
public void initialize(TaskanaEngine taskanaEngine) {}
@Override
public Task afterRequestReview(Task task) {
throw new SystemException("I AM THE EXCEPTION THROWER (*_*)");
}
}
static class TaskModifierAndTransferrer implements AfterRequestReviewProvider {
private TaskanaEngine taskanaEngine;
@Override
public void initialize(TaskanaEngine taskanaEngine) {
this.taskanaEngine = taskanaEngine;
}
@Override
public Task afterRequestReview(Task task) throws Exception {
task.setCustomField(TaskCustomField.CUSTOM_3, "bla");
task = taskanaEngine.getTaskService().updateTask(task);
task = taskanaEngine.getTaskService().transfer(task.getId(), NEW_WORKBASKET_KEY, "DOMAIN_A");
return task;
}
}
static class ClassificationUpdater implements AfterRequestReviewProvider {
public static final String SPI_CLASSIFICATION_NAME = "Neuer Classification Name";
private TaskanaEngine taskanaEngine;
@Override
public void initialize(TaskanaEngine taskanaEngine) {
this.taskanaEngine = taskanaEngine;
}
@Override
public Task afterRequestReview(Task task) throws Exception {
Classification newClassification =
defaultTestClassification().buildAndStore(taskanaEngine.getClassificationService());
task.setClassificationKey(newClassification.getKey());
task = taskanaEngine.getTaskService().updateTask(task);
newClassification.setName(SPI_CLASSIFICATION_NAME);
taskanaEngine.getClassificationService().updateClassification(newClassification);
return task;
}
}
@Nested
@TestInstance(Lifecycle.PER_CLASS)
@WithServiceProvider(
serviceProviderInterface = AfterRequestReviewProvider.class,
serviceProviders = TaskModifierAndTransferrer.class)
class SpiModifiesTask {
@TaskanaInject TaskService taskService;
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnModifiedTask_When_SpiModifiesAndTransfersTask() throws Exception {
Task task = createTaskClaimedByUser("user-1-1").buildAndStore(taskService);
Task result = taskService.requestReview(task.getId());
assertThat(result.getCustomField(TaskCustomField.CUSTOM_3)).isEqualTo("bla");
assertThat(result.getWorkbasketSummary()).isEqualTo(newWorkbasket);
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnPersistentModifiedTask_When_SpiModifiesAndTransfersTask() throws Exception {
Task task = createTaskClaimedByUser("user-1-1").buildAndStore(taskService);
taskService.requestReview(task.getId());
Task result = taskService.getTask(task.getId());
assertThat(result.getCustomField(TaskCustomField.CUSTOM_3)).isEqualTo("bla");
assertThat(result.getWorkbasketSummary()).isEqualTo(newWorkbasket);
}
}
@Nested
@TestInstance(Lifecycle.PER_CLASS)
@WithServiceProvider(
serviceProviderInterface = AfterRequestReviewProvider.class,
serviceProviders = ClassificationUpdater.class)
class SpiModifiesTaskAndClassification {
@TaskanaInject TaskService taskService;
@WithAccessId(user = "user-1-1", groups = "businessadmin")
@Test
void should_ReturnUpdateMultipleEntities_When_SpiModifiesTask() throws Exception {
Task task = createTaskClaimedByUser("user-1-1").buildAndStore(taskService);
taskService.requestReview(task.getId());
Task result = taskService.getTask(task.getId());
assertThat(result.getClassificationSummary().getId())
.isNotEqualTo(task.getClassificationSummary().getId());
assertThat(result.getClassificationSummary().getName())
.isEqualTo(ClassificationUpdater.SPI_CLASSIFICATION_NAME);
}
}
@Nested
@TestInstance(Lifecycle.PER_CLASS)
@WithServiceProvider(
serviceProviderInterface = AfterRequestReviewProvider.class,
serviceProviders = {TaskModifierAndTransferrer.class, ClassificationUpdater.class})
class MultipleSpisAreDefined {
@TaskanaInject TaskService taskService;
@WithAccessId(user = "user-1-1", groups = "businessadmin")
@Test
void should_ApplyMultipleChanges_When_MultipleSpisAreChained() throws Exception {
Task task = createTaskClaimedByUser("user-1-1").buildAndStore(taskService);
taskService.requestReview(task.getId());
Task result = taskService.getTask(task.getId());
// changes from TaskModifierAndTransferrer
assertThat(result.getCustomField(TaskCustomField.CUSTOM_3)).isEqualTo("bla");
assertThat(result.getWorkbasketSummary()).isEqualTo(newWorkbasket);
// changes from ClassificationUpdater
assertThat(result.getClassificationSummary().getId())
.isNotEqualTo(task.getClassificationSummary().getId());
assertThat(result.getClassificationSummary().getName())
.isEqualTo(ClassificationUpdater.SPI_CLASSIFICATION_NAME);
}
}
@Nested
@TestInstance(Lifecycle.PER_CLASS)
@WithServiceProvider(
serviceProviderInterface = AfterRequestReviewProvider.class,
serviceProviders = ExceptionThrower.class)
class SpiThrowsException {
@TaskanaInject TaskService taskService;
@TaskanaInject TaskanaEngine taskanaEngine;
PlainJavaTransactionProvider transactionProvider;
@BeforeAll
void setup(TaskanaEngineConfiguration taskanaEngineConfiguration) {
transactionProvider =
new PlainJavaTransactionProvider(
taskanaEngine, taskanaEngineConfiguration.getDatasource());
}
@WithAccessId(user = "user-1-1")
@Test
void should_RollbackTransaction_When_SpiThrowsAnException() throws Exception {
Task task = createTaskClaimedByUser("user-1-1").buildAndStore(taskService);
ThrowingCallable call =
() ->
transactionProvider.executeInTransaction(
wrap(() -> taskService.requestReview(task.getId())));
assertThatThrownBy(call)
.isInstanceOf(SystemException.class)
.getCause() // unwrap the "wrap" within "call"
.hasMessage("service provider '%s' threw an exception", ExceptionThrower.class.getName())
.getCause() // unwrap the "wrap" from the service provider manager
.hasMessage("I AM THE EXCEPTION THROWER (*_*)");
Task persistentTask = taskService.getTask(task.getId());
assertThat(persistentTask).isEqualTo(task);
}
}
}

View File

@ -7,6 +7,7 @@ import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.spi.history.internal.HistoryEventManager;
import pro.taskana.spi.priority.internal.PriorityServiceManager;
import pro.taskana.spi.routing.internal.TaskRoutingManager;
import pro.taskana.spi.task.internal.AfterRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
/**
@ -105,4 +106,11 @@ public interface InternalTaskanaEngine {
* @return the {@linkplain PriorityServiceManager} instance
*/
PriorityServiceManager getPriorityServiceManager();
/**
* Retrieves the {@linkplain AfterRequestReviewManager}.
*
* @return the {@linkplain AfterRequestReviewManager} instance
*/
AfterRequestReviewManager getAfterRequestReviewManager();
}

View File

@ -53,6 +53,7 @@ import pro.taskana.monitor.internal.MonitorServiceImpl;
import pro.taskana.spi.history.internal.HistoryEventManager;
import pro.taskana.spi.priority.internal.PriorityServiceManager;
import pro.taskana.spi.routing.internal.TaskRoutingManager;
import pro.taskana.spi.task.internal.AfterRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.internal.AttachmentMapper;
@ -82,6 +83,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
private final TaskRoutingManager taskRoutingManager;
private final CreateTaskPreprocessorManager createTaskPreprocessorManager;
private final PriorityServiceManager priorityServiceManager;
private final AfterRequestReviewManager afterRequestReviewManager;
private final InternalTaskanaEngineImpl internalTaskanaEngineImpl;
private final WorkingDaysToDaysConverter workingDaysToDaysConverter;
private final HistoryEventManager historyEventManager;
@ -117,6 +119,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
createTaskPreprocessorManager = new CreateTaskPreprocessorManager();
historyEventManager = new HistoryEventManager(this);
taskRoutingManager = new TaskRoutingManager(this);
afterRequestReviewManager = new AfterRequestReviewManager(this);
}
public static TaskanaEngine createTaskanaEngine(
@ -521,5 +524,10 @@ public class TaskanaEngineImpl implements TaskanaEngine {
public PriorityServiceManager getPriorityServiceManager() {
return priorityServiceManager;
}
@Override
public AfterRequestReviewManager getAfterRequestReviewManager() {
return afterRequestReviewManager;
}
}
}

View File

@ -0,0 +1,43 @@
package pro.taskana.spi.task.api;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.task.api.models.Task;
/**
* The AfterRequestReviewProvider allows to implement customized behaviour after a review has been
* requested on a given {@linkplain Task}.
*/
public interface AfterRequestReviewProvider {
/**
* Provide the active {@linkplain TaskanaEngine} which is initialized for this TASKANA
* installation.
*
* <p>This method is called during TASKANA startup and allows the service provider to store the
* active {@linkplain TaskanaEngine} for later usage.
*
* @param taskanaEngine the active {@linkplain TaskanaEngine} which is initialized for this
* installation.
*/
void initialize(TaskanaEngine taskanaEngine);
/**
* Perform any action after a review has been requested on a {@linkplain Task} through {@linkplain
* pro.taskana.task.api.TaskService#requestReview(String)}.
*
* <p>This SPI is executed within the same transaction staple as {@linkplain
* pro.taskana.task.api.TaskService#requestReview(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 {@linkplain
* pro.taskana.task.api.TaskService#requestReview(String)}.
*
* @param task the {@linkplain Task} after {@linkplain
* pro.taskana.task.api.TaskService#requestReview(String)} has completed
* @return the modified {@linkplain Task}. <b>IMPORTANT:</b> persistent changes to the {@linkplain
* Task} have to be managed by the service provider.
* @throws Exception if the service provider throws any exception.
*/
Task afterRequestReview(Task task) throws Exception;
}

View File

@ -0,0 +1,50 @@
package pro.taskana.spi.task.internal;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.util.SpiLoader;
import pro.taskana.spi.task.api.AfterRequestReviewProvider;
import pro.taskana.task.api.models.Task;
public class AfterRequestReviewManager {
private static final Logger LOGGER = LoggerFactory.getLogger(AfterRequestReviewManager.class);
private final List<AfterRequestReviewProvider> afterRequestReviewProviders;
public AfterRequestReviewManager(TaskanaEngine taskanaEngine) {
afterRequestReviewProviders = SpiLoader.load(AfterRequestReviewProvider.class);
for (AfterRequestReviewProvider serviceProvider : afterRequestReviewProviders) {
serviceProvider.initialize(taskanaEngine);
LOGGER.info(
"Registered AfterRequestReview service provider: {}",
serviceProvider.getClass().getName());
}
if (afterRequestReviewProviders.isEmpty()) {
LOGGER.info(
"No AfterRequestReviewProvider service provider found. "
+ "Running without any AfterRequestReviewProvider implementation.");
}
}
public Task afterRequestReview(Task task) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending Task to AfterRequestReview service providers: {}", task);
}
for (AfterRequestReviewProvider serviceProvider : afterRequestReviewProviders) {
try {
task = serviceProvider.afterRequestReview(task);
} catch (Exception e) {
throw new SystemException(
String.format(
"service provider '%s' threw an exception", serviceProvider.getClass().getName()),
e);
}
}
return task;
}
}

View File

@ -53,6 +53,7 @@ import pro.taskana.spi.history.api.events.task.TaskTerminatedEvent;
import pro.taskana.spi.history.api.events.task.TaskUpdatedEvent;
import pro.taskana.spi.history.internal.HistoryEventManager;
import pro.taskana.spi.priority.internal.PriorityServiceManager;
import pro.taskana.spi.task.internal.AfterRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
import pro.taskana.task.api.CallbackState;
import pro.taskana.task.api.TaskCommentQuery;
@ -114,6 +115,7 @@ public class TaskServiceImpl implements TaskService {
private final HistoryEventManager historyEventManager;
private final CreateTaskPreprocessorManager createTaskPreprocessorManager;
private final PriorityServiceManager priorityServiceManager;
private final AfterRequestReviewManager afterRequestReviewManager;
public TaskServiceImpl(
InternalTaskanaEngine taskanaEngine,
@ -132,6 +134,7 @@ public class TaskServiceImpl implements TaskService {
this.historyEventManager = taskanaEngine.getHistoryEventManager();
this.createTaskPreprocessorManager = taskanaEngine.getCreateTaskPreprocessorManager();
this.priorityServiceManager = taskanaEngine.getPriorityServiceManager();
this.afterRequestReviewManager = taskanaEngine.getAfterRequestReviewManager();
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
this.taskCommentService =
new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, userMapper, this);
@ -1260,6 +1263,8 @@ public class TaskServiceImpl implements TaskService {
taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
changeDetails));
}
task = (TaskImpl) afterRequestReviewManager.afterRequestReview(task);
} finally {
taskanaEngine.returnConnection();
}

View File

@ -16,6 +16,7 @@ import org.junit.platform.commons.JUnitException;
import pro.taskana.spi.history.api.TaskanaHistory;
import pro.taskana.spi.priority.api.PriorityServiceProvider;
import pro.taskana.spi.routing.api.TaskRoutingProvider;
import pro.taskana.spi.task.api.AfterRequestReviewProvider;
import pro.taskana.spi.task.api.CreateTaskPreprocessor;
import pro.taskana.testapi.WithServiceProvider;
@ -26,7 +27,8 @@ public class ServiceProviderExtractor {
TaskanaHistory.class,
PriorityServiceProvider.class,
TaskRoutingProvider.class,
CreateTaskPreprocessor.class);
CreateTaskPreprocessor.class,
AfterRequestReviewProvider.class);
private ServiceProviderExtractor() {
throw new IllegalStateException("utility class");