From 8b0bdb94b5913d60860dc44cb048427d3b50d8c0 Mon Sep 17 00:00:00 2001 From: Marcel Lengl <52546181+LenglBoy@users.noreply.github.com> Date: Wed, 7 Feb 2018 12:45:06 +0100 Subject: [PATCH] TSK-252: Bulk-Transfer for Task --- .../src/main/java/pro/taskana/TaskQuery.java | 7 ++ .../main/java/pro/taskana/TaskService.java | 21 ++++ .../taskana/impl/BulkOperationResults.java | 90 +++++++++++++++ .../java/pro/taskana/impl/TaskQueryImpl.java | 11 ++ .../pro/taskana/impl/TaskServiceImpl.java | 87 ++++++++++++++ .../taskana/model/mappings/QueryMapper.java | 1 + .../taskana/model/mappings/TaskMapper.java | 7 ++ .../acceptance/task/TransferTaskAccTest.java | 106 ++++++++++++------ 8 files changed, 293 insertions(+), 37 deletions(-) create mode 100644 lib/taskana-core/src/main/java/pro/taskana/impl/BulkOperationResults.java diff --git a/lib/taskana-core/src/main/java/pro/taskana/TaskQuery.java b/lib/taskana-core/src/main/java/pro/taskana/TaskQuery.java index acfe21e97..de25639b1 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/TaskQuery.java +++ b/lib/taskana-core/src/main/java/pro/taskana/TaskQuery.java @@ -531,4 +531,11 @@ public interface TaskQuery extends BaseQuery { * @return the query */ TaskQuery orderByCustom10(SortDirection sortDirection); + + /* + * Filter for summaries which are containing one of the given taskIds. + * @param taskIds + * @return the taskQuery + */ + TaskQuery idIn(String... taskIds); } diff --git a/lib/taskana-core/src/main/java/pro/taskana/TaskService.java b/lib/taskana-core/src/main/java/pro/taskana/TaskService.java index 37711b634..f8dbe8ea8 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/TaskService.java +++ b/lib/taskana-core/src/main/java/pro/taskana/TaskService.java @@ -12,7 +12,9 @@ import pro.taskana.exceptions.InvalidWorkbasketException; import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.TaskAlreadyExistException; import pro.taskana.exceptions.TaskNotFoundException; +import pro.taskana.exceptions.TaskanaException; import pro.taskana.exceptions.WorkbasketNotFoundException; +import pro.taskana.impl.BulkOperationResults; import pro.taskana.model.TaskState; /** @@ -236,4 +238,23 @@ public interface TaskService { Task updateTask(Task task) throws InvalidArgumentException, TaskNotFoundException, ConcurrencyException, WorkbasketNotFoundException, ClassificationNotFoundException, InvalidWorkbasketException, NotAuthorizedException, AttachmentPersistenceException; + + /** + * Transfers a list of tasks to an other workbasket. Exceptions will be thrown if the caller got no permissions on + * the target or it doesn´t exist. Other Exceptions will be stored and returned in the end. + * + * @param destinationWorkbasketKey + * target workbasket key + * @param taskIds + * source task which will be moved + * @return Bulkresult with ID and Error in it for failed transactions. + * @throws NotAuthorizedException + * if the caller hasn´t permissions on tarket WB. + * @throws InvalidArgumentException + * if the method paramesters are EMPTY or NULL. + * @throws WorkbasketNotFoundException + * if the target WB can´t be found. + */ + BulkOperationResults transferBulk(String destinationWorkbasketKey, List taskIds) + throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException; } diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/BulkOperationResults.java b/lib/taskana-core/src/main/java/pro/taskana/impl/BulkOperationResults.java new file mode 100644 index 000000000..f84349c6e --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/BulkOperationResults.java @@ -0,0 +1,90 @@ +package pro.taskana.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Returning type for a bulk db interaction with errors. This wrapper is storing them with a matching object ID. + * + * @param + * unique keys for the logs. + * @param + * type of the stored informations + */ +public class BulkOperationResults { + + private Optional> errorMap = Optional.of(new HashMap()); + private static final Logger LOGGER = LoggerFactory.getLogger(BulkOperationResults.class); + + /** + * Returning a list of current errors as map. If there are no errors the result will be empty. + * + * @return map of errors which can´t be null. + */ + public Map getErrorMap() { + return this.errorMap.get(); + } + + /** + * Adding an appearing error to the map and list them by a unique ID as key. NULL keys will be ignored. + * + * @param objectId + * unique key of a entity. + * @param error + * occurred error of a interaction with the entity + * @return status of adding the values. + */ + public boolean addError(K objectId, V error) { + boolean status = false; + try { + if (objectId != null) { + this.errorMap.get().put(objectId, error); + status = true; + } + } catch (Exception e) { + LOGGER.warn( + "Can´t add bulkoperation-error, because of a map failure. ID={}, error={} and current failure={}", + objectId, error, e); + } + return status; + } + + /** + * Returning the status of a bulk-error-log. + * + * @return true if there are logged errors. + */ + public boolean containErrors() { + boolean isContainingErrors = false; + if (!this.errorMap.get().isEmpty()) { + isContainingErrors = true; + } + return isContainingErrors; + } + + /** + * Returns the stored error for a unique ID or NULL if there is no error stored or ID invalid. + * + * @param idKey + * which is mapped with an error + * @return stored error for ID + */ + public V getErrorForId(K idKey) { + V result = null; + if (idKey != null) { + result = this.errorMap.get().get(idKey); + } + return result; + } + + /** + * Clearing the map - all entries will be removed. + */ + public void clearErrors() { + this.errorMap.get().clear(); + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskQueryImpl.java b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskQueryImpl.java index 194507014..b56093f25 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskQueryImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskQueryImpl.java @@ -30,6 +30,7 @@ public class TaskQueryImpl implements TaskQuery { private TaskanaEngineImpl taskanaEngineImpl; private TaskServiceImpl taskService; private String[] name; + private String[] taskIds; private String description; private String note; private int[] priority; @@ -59,6 +60,12 @@ public class TaskQueryImpl implements TaskQuery { this.orderBy = new ArrayList<>(); } + @Override + public TaskQuery idIn(String... taskIds) { + this.taskIds = taskIds; + return this; + } + @Override public TaskQuery nameIn(String... names) { this.name = names; @@ -302,6 +309,10 @@ public class TaskQueryImpl implements TaskQuery { this.taskanaEngineImpl = taskanaEngine; } + public String[] getTaskIds() { + return taskIds; + } + public String[] getName() { return name; } diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java index 9da945af9..7ccadf9bd 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java @@ -36,6 +36,7 @@ import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.SystemException; import pro.taskana.exceptions.TaskAlreadyExistException; import pro.taskana.exceptions.TaskNotFoundException; +import pro.taskana.exceptions.TaskanaException; import pro.taskana.exceptions.WorkbasketNotFoundException; import pro.taskana.impl.util.IdGenerator; import pro.taskana.impl.util.LoggerUtils; @@ -268,6 +269,7 @@ public class TaskServiceImpl implements TaskService { task.setDomain(destinationWorkbasket.getDomain()); task.setModified(Instant.now()); task.setState(TaskState.READY); + task.setOwner(null); taskMapper.update(task); LOGGER.debug("Method transfer() transferred Task '{}' to destination workbasket {}", taskId, destinationWorkbasketKey); @@ -278,6 +280,91 @@ public class TaskServiceImpl implements TaskService { } } + @Override + public BulkOperationResults transferBulk(String destinationWorkbasketKey, + List taskIds) throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException { + try { + taskanaEngineImpl.openConnection(); + LOGGER.debug("entry to transferBulk(targetWbKey = {}, taskIds = {})", destinationWorkbasketKey, taskIds); + // Check pre-conditions with trowing Exceptions + if (destinationWorkbasketKey == null || taskIds == null) { + throw new InvalidArgumentException( + "DestinationWorkbasketKey or TaskIds can´t be used as NULL-Parameter."); + } + Workbasket destinationWorkbasket = workbasketService.getWorkbasketByKey(destinationWorkbasketKey); + + BulkOperationResults bulkLog = new BulkOperationResults<>(); + // check tasks exist and Ids valid - log and remove + List taskSummaries = this.createTaskQuery().idIn(taskIds.toArray(new String[0])).list(); + Iterator taskIdIterator = taskIds.iterator(); + while (taskIdIterator.hasNext()) { + String currentTaskId = taskIdIterator.next(); + if (currentTaskId == null || currentTaskId.equals("")) { + bulkLog.addError("", + new InvalidArgumentException("IDs with EMPTY or NULL value are not allowed.")); + taskIdIterator.remove(); + } else if (!taskSummaries.stream() + .filter(taskSummary -> currentTaskId.equals(taskSummary.getTaskId())) + .findFirst() + .isPresent()) { + bulkLog.addError(currentTaskId, new TaskNotFoundException(currentTaskId)); + taskIdIterator.remove(); + } + } + + // check source WB (read)+transfer + Set workbasketKeys = new HashSet<>(); + taskSummaries.stream().forEach(t -> workbasketKeys.add(t.getWorkbasketSummary().getKey())); + List sourceWorkbaskets = workbasketService.createWorkbasketQuery() + .callerHasPermission(WorkbasketAuthorization.TRANSFER) + .keyIn(workbasketKeys.toArray(new String[0])) + .list(); + taskIdIterator = taskIds.iterator(); + while (taskIdIterator.hasNext()) { + String currentTaskId = taskIdIterator.next(); + TaskSummary taskSummary = taskSummaries.stream() + .filter(t -> currentTaskId.equals(t.getTaskId())) + .findFirst() + .orElse(null); + if (taskSummaries != null) { + if (!sourceWorkbaskets.stream() + .filter(wb -> taskSummary.getWorkbasketSummary().getKey().equals(wb.getKey())) + .findFirst() + .isPresent()) { + bulkLog.addError(currentTaskId, + new NotAuthorizedException( + "The workbasket of this task got not TRANSFER permissions. TaskId=" + currentTaskId)); + taskIdIterator.remove(); + } + } + } + + // filter taskSummaries and update values + taskSummaries = taskSummaries.stream().filter(ts -> taskIds.contains(ts.getTaskId())).collect( + Collectors.toList()); + if (!taskSummaries.isEmpty()) { + Instant now = Instant.now(); + List updateObjects = new ArrayList<>(); + for (TaskSummary ts : taskSummaries) { + TaskSummaryImpl taskSummary = (TaskSummaryImpl) ts; + taskSummary.setRead(false); + taskSummary.setTransferred(true); + taskSummary.setWorkbasketSummary(destinationWorkbasket.asSummary()); + taskSummary.setDomain(destinationWorkbasket.getDomain()); + taskSummary.setModified(now); + taskSummary.setState(TaskState.READY); + taskSummary.setOwner(null); + updateObjects.add(taskSummary); + } + taskMapper.updateTransfered(taskIds, updateObjects.get(0)); + } + return bulkLog; + } finally { + LOGGER.debug("exit from transferBulk(targetWbKey = {}, taskIds = {})", destinationWorkbasketKey, taskIds); + taskanaEngineImpl.returnConnection(); + } + } + @Override public Task setTaskRead(String taskId, boolean isRead) throws TaskNotFoundException { diff --git a/lib/taskana-core/src/main/java/pro/taskana/model/mappings/QueryMapper.java b/lib/taskana-core/src/main/java/pro/taskana/model/mappings/QueryMapper.java index 7c0062cf9..3554b14ce 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/model/mappings/QueryMapper.java +++ b/lib/taskana-core/src/main/java/pro/taskana/model/mappings/QueryMapper.java @@ -28,6 +28,7 @@ public interface QueryMapper { @Select("") + void updateTransfered(@Param("taskIds") List taskIds, + @Param("referencetask") TaskSummaryImpl referencetask); } diff --git a/lib/taskana-core/src/test/java/acceptance/task/TransferTaskAccTest.java b/lib/taskana-core/src/test/java/acceptance/task/TransferTaskAccTest.java index 0552f7ba6..7fcdd842b 100644 --- a/lib/taskana-core/src/test/java/acceptance/task/TransferTaskAccTest.java +++ b/lib/taskana-core/src/test/java/acceptance/task/TransferTaskAccTest.java @@ -1,23 +1,27 @@ package acceptance.task; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.sql.SQLException; +import java.time.Instant; import java.util.ArrayList; import org.h2.store.fs.FileUtils; import org.junit.AfterClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import acceptance.AbstractAccTest; import pro.taskana.Task; import pro.taskana.TaskService; +import pro.taskana.Workbasket; import pro.taskana.exceptions.ClassificationNotFoundException; import pro.taskana.exceptions.InvalidArgumentException; import pro.taskana.exceptions.InvalidOwnerException; @@ -26,7 +30,9 @@ import pro.taskana.exceptions.InvalidWorkbasketException; import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.TaskAlreadyExistException; import pro.taskana.exceptions.TaskNotFoundException; +import pro.taskana.exceptions.TaskanaException; import pro.taskana.exceptions.WorkbasketNotFoundException; +import pro.taskana.impl.BulkOperationResults; import pro.taskana.model.TaskState; import pro.taskana.security.JAASRunner; import pro.taskana.security.WithAccessId; @@ -106,7 +112,6 @@ public class TransferTaskAccTest extends AbstractAccTest { taskService.transfer(task.getId(), "USER_1_1"); } - @Ignore @WithAccessId( userName = "teamlead_1", groupNames = {"group_1"}) @@ -115,55 +120,82 @@ public class TransferTaskAccTest extends AbstractAccTest { throws SQLException, NotAuthorizedException, InvalidArgumentException, ClassificationNotFoundException, WorkbasketNotFoundException, TaskAlreadyExistException, InvalidWorkbasketException, TaskNotFoundException, InvalidStateException, InvalidOwnerException { + Instant before = Instant.now(); TaskService taskService = taskanaEngine.getTaskService(); ArrayList taskIdList = new ArrayList<>(); taskIdList.add("TKI:000000000000000000000000000000000004"); taskIdList.add("TKI:000000000000000000000000000000000005"); - // BulkOperationsResults results = taskService.transfer(taskIdList, "USER_1_1"); - // - // assertFalse(results.containsErrors()); - // Task transferredTask = taskService.getTask("TKI:000000000000000000000000000000000004"); - // assertNotNull(transferredTask); - // assertTrue(transferredTask.isTransferred()); - // assertFalse(transferredTask.isRead()); - // assertEquals(TaskState.READY, transferredTask.getState()); - // transferredTask = taskService.getTask("TKI:000000000000000000000000000000000005"); - // assertNotNull(transferredTask); - // assertTrue(transferredTask.isTransferred()); - // assertFalse(transferredTask.isRead()); - // assertEquals(TaskState.READY, transferredTask.getState()); + BulkOperationResults results = taskService.transferBulk("USER_1_1", taskIdList); + assertFalse(results.containErrors()); + + Workbasket wb = taskanaEngine.getWorkbasketService().getWorkbasketByKey("USER_1_1"); + Task transferredTask = taskService.getTask("TKI:000000000000000000000000000000000004"); + assertNotNull(transferredTask); + assertTrue(transferredTask.isTransferred()); + assertFalse(transferredTask.isRead()); + assertEquals(TaskState.READY, transferredTask.getState()); + assertThat(transferredTask.getWorkbasketKey(), equalTo(wb.getKey())); + assertThat(transferredTask.getDomain(), equalTo(wb.getDomain())); + assertTrue(transferredTask.getModified().isAfter(before)); + assertThat(transferredTask.getOwner(), equalTo(null)); + transferredTask = taskService.getTask("TKI:000000000000000000000000000000000005"); + assertNotNull(transferredTask); + assertTrue(transferredTask.isTransferred()); + assertFalse(transferredTask.isRead()); + assertEquals(TaskState.READY, transferredTask.getState()); + assertThat(transferredTask.getWorkbasketKey(), equalTo(wb.getKey())); + assertThat(transferredTask.getDomain(), equalTo(wb.getDomain())); + assertTrue(transferredTask.getModified().isAfter(before)); + assertThat(transferredTask.getOwner(), equalTo(null)); } - @Ignore - @WithAccessId( - userName = "teamlead_1") + @WithAccessId(userName = "teamlead_1", groupNames = {"group_1"}) @Test - public void testBulkTransferTaskWithException() + public void testBulkTransferTaskWithExceptions() throws SQLException, NotAuthorizedException, InvalidArgumentException, ClassificationNotFoundException, WorkbasketNotFoundException, TaskAlreadyExistException, InvalidWorkbasketException, TaskNotFoundException, InvalidStateException, InvalidOwnerException { TaskService taskService = taskanaEngine.getTaskService(); + Workbasket wb = taskanaEngine.getWorkbasketService().getWorkbasketByKey("USER_1_1"); + Instant before = Instant.now(); ArrayList taskIdList = new ArrayList<>(); - taskIdList.add("TKI:000000000000000000000000000000000006"); - taskIdList.add("TKI:000000000000000000000000000000000002"); + taskIdList.add("TKI:000000000000000000000000000000000006"); // working + taskIdList.add("TKI:000000000000000000000000000000000002"); // NotAuthorized + taskIdList.add(""); // InvalidArgument + taskIdList.add(null); // InvalidArgument (added with ""), duplicate + taskIdList.add("TKI:000000000000000000000000000000000099"); // TaskNotFound - // BulkOperationsResults results = taskService.transfer(taskIdList, "USER_1_1"); - // - // assertTrue(results.containsErrors()); - // for (results.getErrorMap().keys()) { - // assertEquals("TKI:000000000000000000000000000000000002", key); - // assertTrue(results.getErrorForId(key) instanceOf NotAuthorizedException.class); - // } - // Task transferredTask = taskService.getTask("TKI:000000000000000000000000000000000006"); - // assertNotNull(transferredTask); - // assertTrue(transferredTask.isTransferred()); - // assertFalse(transferredTask.isRead()); - // assertEquals(TaskState.READY, transferredTask.getState()); - // transferredTask = taskService.getTask("TKI:000000000000000000000000000000000002"); - // assertNotNull(transferredTask); - // assertFalse(transferredTask.isTransferred()); - // assertEquals("GPK_B_KSC", transferredTask.getWorkbasketKey()); + BulkOperationResults results = taskService.transferBulk("USER_1_1", taskIdList); + assertTrue(results.containErrors()); + assertThat(results.getErrorMap().values().size(), equalTo(3)); + // react to result + for (String taskId : results.getErrorMap().keySet()) { + TaskanaException ex = results.getErrorForId(taskId); + if (ex instanceof NotAuthorizedException) { + System.out.println("NotAuthorizedException on bulkTransfer for taskId=" + taskId); + } else if (ex instanceof InvalidArgumentException) { + System.out.println("InvalidArgumentException on bulkTransfer for EMPTY/NULL taskId='" + taskId + "'"); + } else if (ex instanceof TaskNotFoundException) { + System.out.println("TaskNotFoundException on bulkTransfer for taskId=" + taskId); + } else { + fail("Impossible failure Entry registered"); + } + } + Task transferredTask = taskService.getTask("TKI:000000000000000000000000000000000006"); + assertNotNull(transferredTask); + assertTrue(transferredTask.isTransferred()); + assertFalse(transferredTask.isRead()); + assertEquals(TaskState.READY, transferredTask.getState()); + assertThat(transferredTask.getWorkbasketKey(), equalTo(wb.getKey())); + assertThat(transferredTask.getDomain(), equalTo(wb.getDomain())); + assertTrue(transferredTask.getModified().isAfter(before)); + assertThat(transferredTask.getOwner(), equalTo(null)); + + transferredTask = taskService.getTask("TKI:000000000000000000000000000000000002"); + assertNotNull(transferredTask); + assertFalse(transferredTask.isTransferred()); + assertEquals("GPK_B_KSC", transferredTask.getWorkbasketKey()); } @AfterClass