diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java b/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java index b22a2f8f5c..b995ccb2b5 100644 --- a/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java +++ b/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java @@ -40,7 +40,8 @@ public static String determineChangesInAttributes(T oldObject, T newObject) // this has to be checked after we deal with List data types, because // we want to allow different implementations of the List interface to work as well. - if (!oldObject.getClass().equals(newObject.getClass())) { + if (!oldObject.getClass().equals(newObject.getClass()) + && !oldObject.getClass().isAssignableFrom(newObject.getClass())) { throw new SystemException( String.format( "The classes differ between the oldObject(%s) and newObject(%s). " diff --git a/history/taskana-simplehistory-provider/pom.xml b/history/taskana-simplehistory-provider/pom.xml index ff7282c426..4ddc5253f1 100644 --- a/history/taskana-simplehistory-provider/pom.xml +++ b/history/taskana-simplehistory-provider/pom.xml @@ -37,6 +37,12 @@ + + pro.taskana + taskana-test-api + ${project.version} + test + pro.taskana taskana-common-data diff --git a/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskRerouteAccTest.java b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskRerouteAccTest.java new file mode 100644 index 0000000000..07703aeae6 --- /dev/null +++ b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskRerouteAccTest.java @@ -0,0 +1,251 @@ +package acceptance.events.task; + +import static org.assertj.core.api.Assertions.assertThat; + +import acceptance.events.task.CreateHistoryEventOnTaskRerouteAccTest.TaskRoutingProviderForDomainA; +import java.lang.reflect.Field; +import java.util.List; +import org.apache.ibatis.session.SqlSessionManager; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import pro.taskana.classification.api.ClassificationService; +import pro.taskana.classification.api.models.ClassificationSummary; +import pro.taskana.common.api.TaskanaEngine; +import pro.taskana.common.test.security.JaasExtension; +import pro.taskana.common.test.security.WithAccessId; +import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl; +import pro.taskana.simplehistory.impl.TaskHistoryQueryImpl; +import pro.taskana.simplehistory.impl.TaskanaHistoryEngineImpl; +import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper; +import pro.taskana.spi.history.api.TaskanaHistory; +import pro.taskana.spi.history.api.events.task.TaskHistoryEvent; +import pro.taskana.spi.history.api.events.task.TaskHistoryEventType; +import pro.taskana.spi.routing.api.TaskRoutingProvider; +import pro.taskana.task.api.TaskService; +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.workbasket.api.WorkbasketPermission; +import pro.taskana.workbasket.api.WorkbasketService; +import pro.taskana.workbasket.api.models.Workbasket; +import pro.taskana.workbasket.api.models.WorkbasketSummary; + +@WithServiceProvider( + serviceProviderInterface = TaskRoutingProvider.class, + serviceProviders = TaskRoutingProviderForDomainA.class) +@WithServiceProvider( + serviceProviderInterface = TaskanaHistory.class, + serviceProviders = SimpleHistoryServiceImpl.class) +@TaskanaIntegrationTest +@ExtendWith(JaasExtension.class) +class CreateHistoryEventOnTaskRerouteAccTest { + @TaskanaInject TaskanaEngine taskanaEngine; + @TaskanaInject TaskService taskService; + @TaskanaInject WorkbasketService workbasketService; + @TaskanaInject ClassificationService classificationService; + ClassificationSummary classificationSummary; + WorkbasketSummary domainAWorkbasketSummary; + WorkbasketSummary domainBWorkbasketSummary; + Task task1; + Task task2; + Task task3; + Task task4; + private SimpleHistoryServiceImpl historyService = new SimpleHistoryServiceImpl(); + private TaskanaHistoryEngineImpl taskanaHistoryEngine; + + @WithAccessId(user = "admin") + @BeforeAll + void setUp() throws Exception { + taskanaHistoryEngine = TaskanaHistoryEngineImpl.createTaskanaEngine(taskanaEngine); + historyService.initialize(taskanaEngine); + classificationSummary = + DefaultTestEntities.defaultTestClassification() + .buildAndStoreAsSummary(classificationService); + domainAWorkbasketSummary = + DefaultTestEntities.defaultTestWorkbasket() + .domain("DOMAIN_A") + .buildAndStoreAsSummary(workbasketService); + domainBWorkbasketSummary = + DefaultTestEntities.defaultTestWorkbasket() + .domain("DOMAIN_B") + .buildAndStoreAsSummary(workbasketService); + + task1 = + TaskBuilder.newTask() + .classificationSummary(classificationSummary) + .workbasketSummary(domainAWorkbasketSummary) + .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build()) + .buildAndStore(taskService); + task2 = + TaskBuilder.newTask() + .classificationSummary(classificationSummary) + .workbasketSummary(domainAWorkbasketSummary) + .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build()) + .buildAndStore(taskService); + task3 = + TaskBuilder.newTask() + .classificationSummary(classificationSummary) + .workbasketSummary(domainBWorkbasketSummary) + .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build()) + .buildAndStore(taskService); + + task4 = + TaskBuilder.newTask() + .classificationSummary(classificationSummary) + .workbasketSummary(domainAWorkbasketSummary) + .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build()) + .buildAndStore(taskService); + + WorkbasketAccessItemBuilder.newWorkbasketAccessItem() + .workbasketId(domainAWorkbasketSummary.getId()) + .accessId("user-1-1") + .permission(WorkbasketPermission.OPEN) + .permission(WorkbasketPermission.READ) + .permission(WorkbasketPermission.APPEND) + .buildAndStore(workbasketService); + + WorkbasketAccessItemBuilder.newWorkbasketAccessItem() + .workbasketId(domainBWorkbasketSummary.getId()) + .accessId("user-1-1") + .permission(WorkbasketPermission.OPEN) + .permission(WorkbasketPermission.READ) + .permission(WorkbasketPermission.APPEND) + .buildAndStore(workbasketService); + } + + @WithAccessId(user = "admin") + @Test + void should_CreateRerouteHistoryEvent_When_TaskIsRerouted() throws Exception { + historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId())); + TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper(); + List events = + taskHistoryQueryMapper.queryHistoryEvents( + (TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(task4.getId())); + assertThat(events).isEmpty(); + taskService.rerouteTask(task4.getId()); + + events = + taskHistoryQueryMapper.queryHistoryEvents( + (TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(task4.getId())); + + assertThat(events).hasSize(1); + String eventType = events.get(0).getEventType(); + assertThat(eventType).isEqualTo(TaskHistoryEventType.REROUTED.getName()); + assertRerouteHistoryEvent( + events.get(0).getId(), + domainAWorkbasketSummary.getId(), + domainBWorkbasketSummary.getId(), + "admin"); + + historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId())); + } + + @WithAccessId(user = "admin") + @Test + void should_CreateRerouteHistoryEvent_When_MultipleTasksAreRerouted() throws Exception { + List taskIds = List.of(task1.getId(), task2.getId(), task3.getId()); + historyService.deleteHistoryEventsByTaskIds(taskIds); + TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper(); + + List events = + taskHistoryQueryMapper.queryHistoryEvents( + (TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(task1.getId())); + assertThat(events).isEmpty(); + taskService.rerouteTasks(taskIds); + + events = + taskHistoryQueryMapper.queryHistoryEvents( + (TaskHistoryQueryImpl) + historyService.createTaskHistoryQuery().taskIdIn(taskIds.toArray(new String[0]))); + + assertThat(events) + .extracting(TaskHistoryEvent::getTaskId) + .containsExactlyInAnyOrderElementsOf(taskIds); + + for (TaskHistoryEvent event : events) { + if (event.getTaskId().equals(task1.getId())) { + assertRerouteHistoryEvent( + event.getId(), + domainAWorkbasketSummary.getId(), + domainBWorkbasketSummary.getId(), + "admin"); + } else if (event.getTaskId().equals(task2.getId())) { + assertRerouteHistoryEvent( + event.getId(), + domainAWorkbasketSummary.getId(), + domainBWorkbasketSummary.getId(), + "admin"); + } else { + assertRerouteHistoryEvent( + event.getId(), + domainBWorkbasketSummary.getId(), + domainAWorkbasketSummary.getId(), + "admin"); + } + } + } + + TaskHistoryQueryMapper getHistoryQueryMapper() + throws NoSuchFieldException, IllegalAccessException { + Field sessionManagerField = TaskanaHistoryEngineImpl.class.getDeclaredField("sessionManager"); + sessionManagerField.setAccessible(true); + SqlSessionManager sqlSessionManager = + (SqlSessionManager) sessionManagerField.get(taskanaHistoryEngine); + + return sqlSessionManager.getMapper(TaskHistoryQueryMapper.class); + } + + private void assertRerouteHistoryEvent( + String eventId, String expectedOldValue, String expectedNewValue, String expectedUser) + throws Exception { + TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId); + assertThat(event.getDetails()).isNotNull(); + JSONArray changes = new JSONObject(event.getDetails()).getJSONArray("changes"); + assertThat(changes.length()).isPositive(); + boolean foundField = false; + for (int i = 0; i < changes.length() && !foundField; i++) { + JSONObject change = changes.getJSONObject(i); + if (change.get("fieldName").equals("workbasketSummary")) { + foundField = true; + String oldWorkbasketStr = change.get("oldValue").toString(); + String newWorkbasketStr = change.get("newValue").toString(); + Workbasket oldWorkbasket = workbasketService.getWorkbasket(expectedOldValue); + assertThat(oldWorkbasketStr) + .isEqualTo(JSONObject.wrap(oldWorkbasket.asSummary()).toString()); + Workbasket newWorkbasket = workbasketService.getWorkbasket(expectedNewValue); + assertThat(newWorkbasketStr) + .isEqualTo(JSONObject.wrap(newWorkbasket.asSummary()).toString()); + } + } + assertThat(foundField).describedAs("changes do not contain field 'workbasketSummary'").isTrue(); + + assertThat(event.getId()).startsWith("THI:"); + assertThat(event.getOldValue()).isEqualTo(expectedOldValue); + assertThat(event.getNewValue()).isEqualTo(expectedNewValue); + assertThat(event.getUserId()).isEqualTo(expectedUser); + assertThat(event.getEventType()).isEqualTo(TaskHistoryEventType.REROUTED.getName()); + } + + class TaskRoutingProviderForDomainA implements TaskRoutingProvider { + + @Override + public void initialize(TaskanaEngine taskanaEngine) {} + + @Override + public String determineWorkbasketId(Task task) { + if ("DOMAIN_A".equals(task.getDomain())) { + return domainBWorkbasketSummary.getId(); + } else if ("DOMAIN_B".equals(task.getDomain())) { + return domainAWorkbasketSummary.getId(); + } + return null; + } + } +} diff --git a/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskReroutingAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskReroutingAccTest.java new file mode 100644 index 0000000000..36a6da4302 --- /dev/null +++ b/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskReroutingAccTest.java @@ -0,0 +1,369 @@ +package acceptance.taskrouting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification; +import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference; +import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket; + +import acceptance.taskrouting.TaskReroutingAccTest.CustomTaskRoutingProvider; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import pro.taskana.classification.api.ClassificationService; +import pro.taskana.classification.api.models.ClassificationSummary; +import pro.taskana.common.api.BulkOperationResults; +import pro.taskana.common.api.TaskanaEngine; +import pro.taskana.common.api.exceptions.TaskanaException; +import pro.taskana.spi.routing.api.TaskRoutingProvider; +import pro.taskana.task.api.TaskService; +import pro.taskana.task.api.TaskState; +import pro.taskana.task.api.exceptions.InvalidTaskStateException; +import pro.taskana.task.api.exceptions.TaskNotFoundException; +import pro.taskana.task.api.models.ObjectReference; +import pro.taskana.task.api.models.Task; +import pro.taskana.task.api.models.TaskSummary; +import pro.taskana.testapi.TaskanaInject; +import pro.taskana.testapi.TaskanaIntegrationTest; +import pro.taskana.testapi.WithServiceProvider; +import pro.taskana.testapi.builder.ObjectReferenceBuilder; +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.exceptions.NotAuthorizedOnWorkbasketException; +import pro.taskana.workbasket.api.models.WorkbasketSummary; + +@WithServiceProvider( + serviceProviderInterface = TaskRoutingProvider.class, + serviceProviders = CustomTaskRoutingProvider.class) +@TaskanaIntegrationTest +class TaskReroutingAccTest { + + @TaskanaInject TaskanaEngine taskanaEngine; + @TaskanaInject TaskService taskService; + @TaskanaInject ClassificationService classificationService; + @TaskanaInject WorkbasketService workbasketService; + + ClassificationSummary defaultClassificationSummary; + WorkbasketSummary defaultWorkbasketSummary; + WorkbasketSummary workbasketSummary1; + WorkbasketSummary workbasketSummary2; + WorkbasketSummary workbasketSummary3; + ObjectReference defaultObjectReference; + + @WithAccessId(user = "businessadmin") + @BeforeAll + void setup() throws Exception { + defaultClassificationSummary = + defaultTestClassification().buildAndStoreAsSummary(classificationService); + defaultWorkbasketSummary = defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService); + workbasketSummary1 = + defaultTestWorkbasket().key("key_1").buildAndStoreAsSummary(workbasketService); + workbasketSummary2 = + defaultTestWorkbasket().key("key_2").buildAndStoreAsSummary(workbasketService); + workbasketSummary3 = + defaultTestWorkbasket().key("key_3").buildAndStoreAsSummary(workbasketService); + defaultObjectReference = defaultTestObjectReference().build(); + + WorkbasketAccessItemBuilder.newWorkbasketAccessItem() + .workbasketId(defaultWorkbasketSummary.getId()) + .accessId("user-1-2") + .permission(WorkbasketPermission.OPEN) + .permission(WorkbasketPermission.READ) + .permission(WorkbasketPermission.READTASKS) + .permission(WorkbasketPermission.EDITTASKS) + .permission(WorkbasketPermission.APPEND) + .permission(WorkbasketPermission.TRANSFER) + .buildAndStore(workbasketService); + + WorkbasketAccessItemBuilder.newWorkbasketAccessItem() + .workbasketId(workbasketSummary1.getId()) + .accessId("user-1-2") + .permission(WorkbasketPermission.OPEN) + .permission(WorkbasketPermission.READ) + .permission(WorkbasketPermission.READTASKS) + .permission(WorkbasketPermission.EDITTASKS) + .permission(WorkbasketPermission.APPEND) + .buildAndStore(workbasketService); + + WorkbasketAccessItemBuilder.newWorkbasketAccessItem() + .workbasketId(workbasketSummary2.getId()) + .accessId("user-1-2") + .permission(WorkbasketPermission.OPEN) + .permission(WorkbasketPermission.READ) + .permission(WorkbasketPermission.READTASKS) + .permission(WorkbasketPermission.EDITTASKS) + .buildAndStore(workbasketService); + + WorkbasketAccessItemBuilder.newWorkbasketAccessItem() + .workbasketId(workbasketSummary3.getId()) + .accessId("user-1-2") + .permission(WorkbasketPermission.OPEN) + .permission(WorkbasketPermission.READ) + .permission(WorkbasketPermission.READTASKS) + .permission(WorkbasketPermission.EDITTASKS) + .permission(WorkbasketPermission.APPEND) + .buildAndStore(workbasketService); + } + + @WithAccessId(user = "taskadmin") + @Test + void should_RerouteTask_When_PorValueIsChanged() throws Exception { + Task task = createDefaultTask().buildAndStore(taskService, "admin"); + Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS); + + task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value1")); + taskService.updateTask(task); + Task reroutedTask = taskService.rerouteTask(task.getId()); + + assertTaskIsRerouted(before, reroutedTask.asSummary(), task.getState(), workbasketSummary1); + } + + @WithAccessId(user = "user-1-2") + @Test + void should_NotRerouteTask_When_UserHasNoAppendPermissionToDestinationWb() throws Exception { + Task task = createDefaultTask().buildAndStore(taskService, "admin"); + + task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value2")); + taskService.updateTask(task); + + ThrowingCallable call = () -> taskService.rerouteTask(task.getId()); + NotAuthorizedOnWorkbasketException e = + catchThrowableOfType(call, NotAuthorizedOnWorkbasketException.class); + + assertThat(e.getWorkbasketId()).isEqualTo(workbasketSummary2.getId()); + assertThat(e.getCurrentUserId()).isEqualTo("user-1-2"); + assertThat(e.getRequiredPermissions()).containsExactlyInAnyOrder(WorkbasketPermission.APPEND); + + Task readTask = taskService.getTask(task.getId()); + assertThat(readTask.getWorkbasketSummary().getId()) + .isEqualTo(task.getWorkbasketSummary().getId()); + } + + @WithAccessId(user = "user-1-2") + @Test + void should_NotRerouteTask_When_UserHasNoTransferPermissionToOriginWb() throws Exception { + Task task = + createDefaultTask() + .workbasketSummary(workbasketSummary1) + .buildAndStore(taskService, "admin"); + + task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value2")); + taskService.updateTask(task); + + ThrowingCallable call = () -> taskService.rerouteTask(task.getId()); + NotAuthorizedOnWorkbasketException e = + catchThrowableOfType(call, NotAuthorizedOnWorkbasketException.class); + + assertThat(e.getWorkbasketId()).isEqualTo(workbasketSummary1.getId()); + assertThat(e.getCurrentUserId()).isEqualTo("user-1-2"); + assertThat(e.getRequiredPermissions()).containsExactlyInAnyOrder(WorkbasketPermission.TRANSFER); + + Task readTask = taskService.getTask(task.getId()); + assertThat(readTask.getWorkbasketSummary().getId()) + .isEqualTo(task.getWorkbasketSummary().getId()); + } + + @WithAccessId(user = "taskadmin") + @Test + void should_RerouteTasksToSameWb_When_PorValueIsChanged() throws Exception { + Task task1 = createDefaultTask().buildAndStore(taskService, "admin"); + Task task2 = createDefaultTask().buildAndStore(taskService, "admin"); + Task task3 = createDefaultTask().buildAndStore(taskService, "admin"); + List tasks = Arrays.asList(task1, task2, task3); + Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS); + + for (Task task : tasks) { + task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value1")); + taskService.updateTask(task); + } + List taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList()); + + BulkOperationResults results = taskService.rerouteTasks(taskIds); + assertThat(results.containsErrors()).isFalse(); + List reroutedTasks = + taskService.createTaskQuery().idIn(taskIds.toArray(new String[0])).list(); + assertThat(reroutedTasks).isNotEmpty(); + for (int i = 0; i < reroutedTasks.size(); i++) { + assertTaskIsRerouted( + before, reroutedTasks.get(i), tasks.get(i).getState(), workbasketSummary1); + } + } + + @WithAccessId(user = "taskadmin") + @Test + void should_RerouteTasksToMultipleWorkbaskets_When_PorValueIsChanged() throws Exception { + final Task task1 = createDefaultTask().buildAndStore(taskService, "admin"); + final Task task2 = createDefaultTask().buildAndStore(taskService, "admin"); + final Task task3 = createDefaultTask().buildAndStore(taskService, "admin"); + Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS); + + task1.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value1")); + taskService.updateTask(task1); + task2.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value2")); + taskService.updateTask(task2); + task3.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value3")); + taskService.updateTask(task3); + List tasks = Arrays.asList(task1, task2, task3); + List taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList()); + + BulkOperationResults results = taskService.rerouteTasks(taskIds); + assertThat(results.containsErrors()).isFalse(); + List reroutedTasks = + taskService.createTaskQuery().idIn(taskIds.toArray(new String[0])).list(); + assertThat(reroutedTasks).isNotEmpty(); + for (TaskSummary reroutedTask : reroutedTasks) { + if (reroutedTask.getId().equals(task1.getId())) { + assertTaskIsRerouted(before, reroutedTask, task1.getState(), workbasketSummary1); + } else if (reroutedTask.getId().equals(task2.getId())) { + assertTaskIsRerouted(before, reroutedTask, task2.getState(), workbasketSummary2); + } else { + assertTaskIsRerouted(before, reroutedTask, task3.getState(), workbasketSummary3); + } + } + } + + @WithAccessId(user = "user-1-2") + @Test + void should_RerouteValidTasksEvenIfErrorsExist_When_PorValueIsChanged() throws Exception { + final Task taskToBeRerouted1 = createDefaultTask().buildAndStore(taskService, "admin"); + final Task taskToBeRerouted3 = createDefaultTask().buildAndStore(taskService, "admin"); + final Task taskNotNeededToReroute = + createDefaultTask() + .workbasketSummary(workbasketSummary1) + .buildAndStore(taskService, "admin"); + final Task taskWithFinalState = + createDefaultTask().state(TaskState.COMPLETED).buildAndStore(taskService, "admin"); + final Task taskWithNoTransferPerm = + createDefaultTask() + .workbasketSummary(workbasketSummary1) + .buildAndStore(taskService, "admin"); + final Task taskWithNoAppendDestPerm = createDefaultTask().buildAndStore(taskService, "admin"); + Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS); + + taskToBeRerouted1.setPrimaryObjRef( + createObjectReference("company", null, null, "MyType1", "Value1")); + taskService.updateTask(taskToBeRerouted1); + taskToBeRerouted3.setPrimaryObjRef( + createObjectReference("company", null, null, "MyType1", "Value3")); + taskService.updateTask(taskToBeRerouted3); + taskNotNeededToReroute.setPrimaryObjRef("company", null, null, "MyType1", "Value1"); + taskService.updateTask(taskNotNeededToReroute); + taskWithFinalState.setPrimaryObjRef("company", null, null, "MyType1", "Value1"); + taskService.updateTask(taskWithFinalState); + taskWithNoTransferPerm.setPrimaryObjRef("company", null, null, "MyType1", "Value3"); + taskService.updateTask(taskWithNoTransferPerm); + taskWithNoAppendDestPerm.setPrimaryObjRef("company", null, null, "MyType1", "Value2"); + taskService.updateTask(taskWithNoAppendDestPerm); + List tasks = + Arrays.asList( + taskToBeRerouted1, + taskToBeRerouted3, + taskNotNeededToReroute, + taskWithFinalState, + taskWithNoTransferPerm, + taskWithNoAppendDestPerm); + List taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList()); + taskIds.add("invalid-id"); + + BulkOperationResults results = taskService.rerouteTasks(taskIds); + final List reroutedTasks = + taskService.createTaskQuery().idIn(taskIds.toArray(new String[0])).list(); + assertThat(results.containsErrors()).isTrue(); + assertThat(results.getErrorMap()).hasSize(4); + assertThat(results.getErrorForId("invalid-id")).isOfAnyClassIn(TaskNotFoundException.class); + assertThat(results.getErrorForId(taskWithFinalState.getId())) + .isOfAnyClassIn(InvalidTaskStateException.class); + assertThat(results.getErrorForId(taskWithNoTransferPerm.getId())) + .isOfAnyClassIn(NotAuthorizedOnWorkbasketException.class); + assertThat(results.getErrorForId(taskWithNoAppendDestPerm.getId())) + .isOfAnyClassIn(NotAuthorizedOnWorkbasketException.class); + + for (TaskSummary reroutedTask : reroutedTasks) { + if (reroutedTask.getId().equals(taskToBeRerouted1.getId())) { + assertTaskIsRerouted( + before, reroutedTask, taskToBeRerouted1.getState(), workbasketSummary1); + } else if (reroutedTask.getId().equals(taskToBeRerouted3.getId())) { + assertTaskIsRerouted( + before, reroutedTask, taskToBeRerouted3.getState(), workbasketSummary3); + } else if (reroutedTask.getId().equals(taskNotNeededToReroute.getId())) { + assertThat(reroutedTask).isEqualTo(taskNotNeededToReroute.asSummary()); + } else if (reroutedTask.getId().equals(taskWithFinalState.getId())) { + assertThat(reroutedTask).isEqualTo(taskWithFinalState.asSummary()); + } else if (reroutedTask.getId().equals(taskWithNoTransferPerm.getId())) { + assertThat(reroutedTask).isEqualTo(taskWithNoTransferPerm.asSummary()); + } else if (reroutedTask.getId().equals(taskWithNoAppendDestPerm.getId())) { + assertThat(reroutedTask).isEqualTo(taskWithNoAppendDestPerm.asSummary()); + } + } + } + + ObjectReference createObjectReference( + String company, String system, String systemInstance, String type, String value) { + return ObjectReferenceBuilder.newObjectReference() + .company(company) + .system(system) + .systemInstance(systemInstance) + .type(type) + .value(value) + .build(); + } + + private void assertTaskIsRerouted( + Instant before, + TaskSummary reroutedTask, + TaskState stateBeforeTransfer, + WorkbasketSummary wbAfterReroute) { + assertThat(reroutedTask).isNotNull(); + assertThat(reroutedTask.isRead()).isFalse(); + assertThat(reroutedTask.isTransferred()).isTrue(); + assertThat(reroutedTask.getState()).isEqualTo(getStateAfterTransfer(stateBeforeTransfer)); + assertThat(reroutedTask.getOwner()).isNull(); + assertThat(reroutedTask.getWorkbasketSummary().getId()).isEqualTo(wbAfterReroute.getId()); + assertThat(reroutedTask.getDomain()).isEqualTo(wbAfterReroute.getDomain()); + assertThat(reroutedTask.getModified()).isAfterOrEqualTo(before); + } + + private TaskBuilder createDefaultTask() { + return (TaskBuilder.newTask() + .workbasketSummary(defaultWorkbasketSummary) + .primaryObjRef(defaultObjectReference)) + .classificationSummary(defaultClassificationSummary); + } + + private TaskState getStateAfterTransfer(TaskState stateBeforeTransfer) { + if (stateBeforeTransfer.equals(TaskState.CLAIMED)) { + return TaskState.READY; + } + if (stateBeforeTransfer.equals(TaskState.IN_REVIEW)) { + return TaskState.READY_FOR_REVIEW; + } else { + return stateBeforeTransfer; + } + } + + class CustomTaskRoutingProvider implements TaskRoutingProvider { + + @Override + public void initialize(TaskanaEngine taskanaEngine) {} + + @Override + public String determineWorkbasketId(Task task) { + if (task.getPrimaryObjRef().getValue().equals("Value1")) { + return workbasketSummary1.getId(); + } else if (task.getPrimaryObjRef().getValue().equals("Value2")) { + return workbasketSummary2.getId(); + } else if (task.getPrimaryObjRef().getValue().equals("Value3")) { + return workbasketSummary3.getId(); + } + return null; + } + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java index 6ed98104d2..0174608803 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java +++ b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java @@ -10,7 +10,8 @@ public enum TaskHistoryEventType { COMPLETED("COMPLETED"), CANCELLED("CANCELLED"), TERMINATED("TERMINATED"), - TRANSFERRED("TRANSFERRED"); + TRANSFERRED("TRANSFERRED"), + REROUTED("REROUTED"); private String name; diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskReroutedEvent.java b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskReroutedEvent.java new file mode 100644 index 0000000000..d787184556 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskReroutedEvent.java @@ -0,0 +1,19 @@ +package pro.taskana.spi.history.api.events.task; + +import pro.taskana.task.api.models.TaskSummary; + +public class TaskReroutedEvent extends TaskHistoryEvent { + + public TaskReroutedEvent( + String id, + TaskSummary task, + String oldWorkbasketId, + String newWorkbasketId, + String userId, + String details) { + super(id, task, userId, details); + eventType = TaskHistoryEventType.REROUTED.getName(); + this.oldValue = oldWorkbasketId; + this.newValue = newWorkbasketId; + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java b/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java index 0c51e9b956..b0e2cb7871 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java @@ -981,4 +981,12 @@ ObjectReference newObjectReference( * @return a {@linkplain TaskCommentQuery} */ TaskCommentQuery createTaskCommentQuery(); + + Task rerouteTask(String taskId) + throws NotAuthorizedOnWorkbasketException, + TaskNotFoundException, + WorkbasketNotFoundException, + InvalidTaskStateException; + + BulkOperationResults rerouteTasks(List taskIds); } diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java b/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java index 5ee606b9c7..908ecb1a32 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java @@ -308,4 +308,6 @@ void addSecondaryObjectReference( * @return a copy of this TaskSummary */ TaskSummary copy(); + + Task asTask(); } diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java index 2e25c5cd62..dd0945639f 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java @@ -167,6 +167,92 @@ void setOwnerOfTasks( void updateTransfered( @Param("taskIds") Set taskIds, @Param("referencetask") TaskImpl referencetask); + @Update( + "") + void updateTransferMultipleWorkbaskets( + @Param("taskIds") Set taskIds, + @Param("referenceTasks") List referenceTasks); + + @Update( + "") + void updateIsReadAndOwner( + @Param("taskIds") Set taskIds); + + @Update( + "") + void updateTransferMultipleWorkbasketsPostgres( + @Param("taskIds") Set taskIds, + @Param("referenceTasks") List referenceTasks); + + @Update({ + "" + }) + void updateTransferMultipleWorkbasketsDB2( + @Param("taskIds") Set taskIds, + @Param("referenceTasks") List referenceTasks); + @Update( "