diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java b/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java index ed99b1d5f..757cc8044 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java +++ b/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java @@ -33,6 +33,17 @@ public final class HistoryEventProducer { return getInstance(null).isEnabled(); } + public boolean isEnabled() { + return enabled; + } + + public void createEvent(TaskanaHistoryEvent event) { + LOGGER.debug("Sending event to history service providers: {}", event); + serviceLoader.forEach(historyProvider -> { + historyProvider.create(event); + }); + } + private HistoryEventProducer(TaskanaEngineConfiguration taskanaEngineConfiguration) { this.taskanaEngineConfiguration = taskanaEngineConfiguration; serviceLoader = ServiceLoader.load(TaskanaHistory.class); @@ -47,15 +58,4 @@ public final class HistoryEventProducer { LOGGER.info("No history provider found. Running without history."); } } - - public boolean isEnabled() { - return enabled; - } - - public void createEvent(TaskanaHistoryEvent event) { - LOGGER.debug("Sending event to history service providers: {}", event); - serviceLoader.forEach(historyProvider -> { - historyProvider.create(event); - }); - } } diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskCompletionEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskCompletionEvent.java deleted file mode 100644 index 149a06b19..000000000 --- a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskCompletionEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package pro.taskana.history.api; - -import pro.taskana.Task; - -/** - * Event fired if a task is completed. - */ -public class TaskCompletionEvent extends TaskHistoryEvent { - - public TaskCompletionEvent(Task completedTask) { - super(completedTask); - type = "TASK_COMPLETED"; - created = completedTask.getCompleted(); - } - -} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java index a67ac4cde..557ce23e3 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java +++ b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java @@ -10,21 +10,58 @@ import pro.taskana.security.CurrentUserContext; public class TaskanaHistoryEvent { protected long id; + protected String businessProcessId; + protected String parentBusinessProcessId; + protected String taskId; protected String type; - protected String userId; protected Instant created; + protected String userId; + protected String domain; + protected String workbasketKey; + protected String porCompany; + protected String porSystem; + protected String porInstance; + protected String porType; + protected String porValue; + protected String taskClassificationKey; + protected String taskClassificationCategory; + protected String attachmentClassificationKey; protected String comment; + protected String oldValue; + protected String newValue; + protected String custom1; + protected String custom2; + protected String custom3; + protected String custom4; + protected String oldData; + protected String newData; public TaskanaHistoryEvent() { userId = CurrentUserContext.getUserid(); } - public long getId() { - return id; + public String getBusinessProcessId() { + return businessProcessId; } - public void setId(long id) { - this.id = id; + public void setBusinessProcessId(String businessProcessId) { + this.businessProcessId = businessProcessId; + } + + public String getParentBusinessProcessId() { + return parentBusinessProcessId; + } + + public void setParentBusinessProcessId(String parentBusinessProcessId) { + this.parentBusinessProcessId = parentBusinessProcessId; + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; } public String getType() { @@ -35,6 +72,14 @@ public class TaskanaHistoryEvent { this.type = type; } + public Instant getCreated() { + return created; + } + + public void setCreated(Instant created) { + this.created = created; + } + public String getUserId() { return userId; } @@ -43,12 +88,84 @@ public class TaskanaHistoryEvent { this.userId = userId; } - public Instant getCreated() { - return created; + public String getDomain() { + return domain; } - public void setCreated(Instant created) { - this.created = created; + public void setDomain(String domain) { + this.domain = domain; + } + + public String getWorkbasketKey() { + return workbasketKey; + } + + public void setWorkbasketKey(String workbasketKey) { + this.workbasketKey = workbasketKey; + } + + public String getPorCompany() { + return porCompany; + } + + public void setPorCompany(String porCompany) { + this.porCompany = porCompany; + } + + public String getPorSystem() { + return porSystem; + } + + public void setPorSystem(String porSystem) { + this.porSystem = porSystem; + } + + public String getPorInstance() { + return porInstance; + } + + public void setPorInstance(String porInstance) { + this.porInstance = porInstance; + } + + public String getPorType() { + return porType; + } + + public void setPorType(String porType) { + this.porType = porType; + } + + public String getPorValue() { + return porValue; + } + + public void setPorValue(String porValue) { + this.porValue = porValue; + } + + public String getTaskClassificationKey() { + return taskClassificationKey; + } + + public void setTaskClassificationKey(String taskClassificationKey) { + this.taskClassificationKey = taskClassificationKey; + } + + public String getTaskClassificationCategory() { + return taskClassificationCategory; + } + + public void setTaskClassificationCategory(String taskClassificationCategory) { + this.taskClassificationCategory = taskClassificationCategory; + } + + public String getAttachmentClassificationKey() { + return attachmentClassificationKey; + } + + public void setAttachmentClassificationKey(String attachmentClassificationKey) { + this.attachmentClassificationKey = attachmentClassificationKey; } public String getComment() { @@ -59,4 +176,67 @@ public class TaskanaHistoryEvent { this.comment = comment; } + public String getOldValue() { + return oldValue; + } + + public void setOldValue(String oldValue) { + this.oldValue = oldValue; + } + + public String getNewValue() { + return newValue; + } + + public void setNewValue(String newValue) { + this.newValue = newValue; + } + + public String getCustom1() { + return custom1; + } + + public void setCustom1(String custom1) { + this.custom1 = custom1; + } + + public String getCustom2() { + return custom2; + } + + public void setCustom2(String custom2) { + this.custom2 = custom2; + } + + public String getCustom3() { + return custom3; + } + + public void setCustom3(String custom3) { + this.custom3 = custom3; + } + + public String getCustom4() { + return custom4; + } + + public void setCustom4(String custom4) { + this.custom4 = custom4; + } + + public String getOldData() { + return oldData; + } + + public void setOldData(String oldData) { + this.oldData = oldData; + } + + public String getNewData() { + return newData; + } + + public void setNewData(String newData) { + this.newData = newData; + } } diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/events/task/ClaimCancelledEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/ClaimCancelledEvent.java new file mode 100644 index 000000000..d90a69830 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/ClaimCancelledEvent.java @@ -0,0 +1,15 @@ +package pro.taskana.history.events.task; + +import pro.taskana.Task; + +/** + * Event fired if a task is cancelled to be claimed. + */ +public class ClaimCancelledEvent extends TaskEvent { + + public ClaimCancelledEvent(Task task) { + super(task); + type = "TASK_CLAIM_CANCELLED"; + created = task.getModified(); + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/events/task/ClaimedEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/ClaimedEvent.java new file mode 100644 index 000000000..2578a1e03 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/ClaimedEvent.java @@ -0,0 +1,14 @@ +package pro.taskana.history.events.task; + +import pro.taskana.Task; +/** + * Event fired if a task is claimed. + */ +public class ClaimedEvent extends TaskEvent { + + public ClaimedEvent(Task task) { + super(task); + setType("TASK_CLAIMED"); + created = task.getClaimed(); + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/events/task/CompletedEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/CompletedEvent.java new file mode 100644 index 000000000..2d498bb91 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/CompletedEvent.java @@ -0,0 +1,23 @@ +package pro.taskana.history.events.task; + +import pro.taskana.Task; +import pro.taskana.TaskSummary; + +/** + * Event fired if a task is completed. + */ +public class CompletedEvent extends TaskEvent { + + public CompletedEvent(Task completedTask) { + super(completedTask); + type = "TASK_COMPLETED"; + created = completedTask.getCompleted(); + } + + public CompletedEvent(TaskSummary completedTask) { + super(completedTask); + type = "TASK_COMPLETED"; + created = completedTask.getCompleted(); + } + +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/events/task/CreatedEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/CreatedEvent.java new file mode 100644 index 000000000..4f0a473b9 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/CreatedEvent.java @@ -0,0 +1,14 @@ +package pro.taskana.history.events.task; + +import pro.taskana.Task; +/** + * Event fired if a task is created. + */ +public class CreatedEvent extends TaskEvent { + + public CreatedEvent(Task task) { + super(task); + type = "TASK_CREATED"; + created = task.getCreated(); + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskHistoryEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/TaskEvent.java similarity index 71% rename from lib/taskana-core/src/main/java/pro/taskana/history/api/TaskHistoryEvent.java rename to lib/taskana-core/src/main/java/pro/taskana/history/events/task/TaskEvent.java index c517a0f33..9c41f8eb2 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskHistoryEvent.java +++ b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/TaskEvent.java @@ -1,11 +1,13 @@ -package pro.taskana.history.api; +package pro.taskana.history.events.task; import pro.taskana.Task; +import pro.taskana.TaskSummary; +import pro.taskana.history.api.TaskanaHistoryEvent; /** * Super class for all task related events. */ -public class TaskHistoryEvent extends TaskanaHistoryEvent { +public class TaskEvent extends TaskanaHistoryEvent { protected String taskId; protected String businessProcessId; @@ -21,7 +23,7 @@ public class TaskHistoryEvent extends TaskanaHistoryEvent { protected String porType; protected String porValue; - public TaskHistoryEvent(Task task) { + public TaskEvent(Task task) { super(); taskId = task.getId(); businessProcessId = task.getBusinessProcessId(); @@ -29,7 +31,9 @@ public class TaskHistoryEvent extends TaskanaHistoryEvent { domain = task.getDomain(); workbasketKey = task.getWorkbasketKey(); taskClassificationCategory = task.getClassificationCategory(); - taskClassificationKey = task.getClassificationSummary().getKey(); + if (task.getClassificationSummary() != null) { + taskClassificationKey = task.getClassificationSummary().getKey(); + } if (!task.getAttachments().isEmpty()) { attachmentClassificationKey = task.getAttachments().get(0).getClassificationSummary().getKey(); } @@ -42,6 +46,31 @@ public class TaskHistoryEvent extends TaskanaHistoryEvent { } } + public TaskEvent(TaskSummary task) { + super(); + taskId = task.getTaskId(); + businessProcessId = task.getBusinessProcessId(); + parentBusinessProcessId = task.getParentBusinessProcessId(); + domain = task.getDomain(); + if (task.getWorkbasketSummary() != null) { + workbasketKey = task.getWorkbasketSummary().getKey(); + } + if (task.getClassificationSummary() != null) { + taskClassificationKey = task.getClassificationSummary().getKey(); + taskClassificationCategory = task.getClassificationSummary().getCategory(); + } + if (!task.getAttachmentSummaries().isEmpty()) { + attachmentClassificationKey = task.getAttachmentSummaries().get(0).getClassificationSummary().getKey(); + } + if (task.getPrimaryObjRef() != null) { + porCompany = task.getPrimaryObjRef().getCompany(); + porSystem = task.getPrimaryObjRef().getSystem(); + porInstance = task.getPrimaryObjRef().getSystemInstance(); + porType = task.getPrimaryObjRef().getType(); + porValue = task.getPrimaryObjRef().getValue(); + } + } + public String getTaskId() { return taskId; } diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/events/task/TransferredEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/TransferredEvent.java new file mode 100644 index 000000000..23fbeb9c7 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/events/task/TransferredEvent.java @@ -0,0 +1,23 @@ +package pro.taskana.history.events.task; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import pro.taskana.Task; +import pro.taskana.WorkbasketSummary; + +/** + * Event fired if a task is transferred. + */ +public class TransferredEvent extends TaskEvent { + + private static final Logger LOGGER = LoggerFactory.getLogger(TransferredEvent.class); + + public TransferredEvent(Task task, WorkbasketSummary oldWorkbasket, WorkbasketSummary newWorkbasket) { + super(task); + type = "TASK_TRANSFERRED"; + created = task.getModified(); + this.oldValue = oldWorkbasket.getId(); + this.newValue = newWorkbasket.getId(); + } +} 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 e105257b5..20eb2a521 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 @@ -45,7 +45,11 @@ import pro.taskana.exceptions.TaskNotFoundException; import pro.taskana.exceptions.TaskanaException; import pro.taskana.exceptions.WorkbasketNotFoundException; import pro.taskana.history.HistoryEventProducer; -import pro.taskana.history.api.TaskCompletionEvent; +import pro.taskana.history.events.task.ClaimCancelledEvent; +import pro.taskana.history.events.task.ClaimedEvent; +import pro.taskana.history.events.task.CompletedEvent; +import pro.taskana.history.events.task.CreatedEvent; +import pro.taskana.history.events.task.TransferredEvent; import pro.taskana.impl.report.TimeIntervalColumnHeader; import pro.taskana.impl.util.IdGenerator; import pro.taskana.impl.util.LoggerUtils; @@ -66,7 +70,7 @@ public class TaskServiceImpl implements TaskService { private static final String MUST_NOT_BE_EMPTY = " must not be empty"; private static final Duration MAX_DURATION = Duration.ofSeconds(Long.MAX_VALUE, 999_999_999); private static final Set ALLOWED_KEYS = new HashSet<>( - Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16")); + Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16")); private DaysToWorkingDaysConverter converter; private TaskanaEngineImpl taskanaEngine; private WorkbasketService workbasketService; @@ -104,37 +108,6 @@ public class TaskServiceImpl implements TaskService { return claim(taskId, true); } - private Task claim(String taskId, boolean forceClaim) - throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, NotAuthorizedException { - String userId = CurrentUserContext.getUserid(); - LOGGER.debug("entry to claim(id = {}, userId = {}, forceClaim = {})", taskId, userId, forceClaim); - TaskImpl task = null; - try { - taskanaEngine.openConnection(); - task = (TaskImpl) getTask(taskId); - TaskState state = task.getState(); - if (state == TaskState.COMPLETED) { - throw new InvalidStateException("Task with id " + taskId + " is already completed."); - } - if (state == TaskState.CLAIMED && !forceClaim && !task.getOwner().equals(userId)) { - throw new InvalidOwnerException( - "Task with id " + taskId + " is already claimed by " + task.getOwner() + "."); - } - Instant now = Instant.now(); - task.setOwner(userId); - task.setModified(now); - task.setClaimed(now); - task.setRead(true); - task.setState(TaskState.CLAIMED); - taskMapper.update(task); - LOGGER.debug("Task '{}' claimed by user '{}'.", taskId, userId); - } finally { - taskanaEngine.returnConnection(); - LOGGER.debug("exit from claim()"); - } - return task; - } - @Override public Task cancelClaim(String taskId) throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, NotAuthorizedException { @@ -147,38 +120,6 @@ public class TaskServiceImpl implements TaskService { return this.cancelClaim(taskId, true); } - private Task cancelClaim(String taskId, boolean forceUnclaim) - throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, NotAuthorizedException { - String userId = CurrentUserContext.getUserid(); - LOGGER.debug("entry to cancelClaim(taskId = {}), userId = {}, forceUnclaim = {})", taskId, userId, - forceUnclaim); - TaskImpl task = null; - try { - taskanaEngine.openConnection(); - task = (TaskImpl) getTask(taskId); - TaskState state = task.getState(); - if (state == TaskState.COMPLETED) { - throw new InvalidStateException("Task with id " + taskId + " is already completed."); - } - if (state == TaskState.CLAIMED && !forceUnclaim && !userId.equals(task.getOwner())) { - throw new InvalidOwnerException( - "Task with id " + taskId + " is already claimed by " + task.getOwner() + "."); - } - Instant now = Instant.now(); - task.setOwner(null); - task.setModified(now); - task.setClaimed(null); - task.setRead(true); - task.setState(TaskState.READY); - taskMapper.update(task); - LOGGER.debug("Task '{}' unclaimed by user '{}'.", taskId, userId); - } finally { - taskanaEngine.returnConnection(); - LOGGER.debug("exit from cancelClaim()"); - } - return task; - } - @Override public Task completeTask(String taskId) throws TaskNotFoundException, InvalidOwnerException, InvalidStateException, NotAuthorizedException { @@ -191,115 +132,32 @@ public class TaskServiceImpl implements TaskService { return completeTask(taskId, true); } - private Task completeTask(String taskId, boolean isForced) - throws TaskNotFoundException, InvalidOwnerException, InvalidStateException, NotAuthorizedException { - String userId = CurrentUserContext.getUserid(); - LOGGER.debug("entry to completeTask(id = {}, userId = {}, isForced = {})", taskId, userId, isForced); - TaskImpl task = null; - try { - taskanaEngine.openConnection(); - task = (TaskImpl) this.getTask(taskId); - - if (task.getState() == TaskState.COMPLETED) { - return task; - } - - // check pre-conditions for non-forced invocation - if (!isForced) { - if (task.getClaimed() == null || task.getState() != TaskState.CLAIMED) { - throw new InvalidStateException("Task with id " + taskId + " has to be claimed before."); - } else if (!CurrentUserContext.getAccessIds().contains(task.getOwner())) { - throw new InvalidOwnerException( - "Owner of task " + taskId + " is " + task.getOwner() + ", but current User is " + userId); - } - } else { - // CLAIM-forced, if task was not already claimed before. - if (task.getClaimed() == null || task.getState() != TaskState.CLAIMED) { - task = (TaskImpl) this.forceClaim(taskId); - } - } - Instant now = Instant.now(); - task.setCompleted(now); - task.setModified(now); - task.setState(TaskState.COMPLETED); - task.setOwner(userId); - taskMapper.update(task); - LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId); - if (HistoryEventProducer.isHistoryEnabled()) { - historyEventProducer.createEvent(new TaskCompletionEvent(task)); - } - - } finally { - taskanaEngine.returnConnection(); - LOGGER.debug("exit from completeTask()"); - } - return task; - } - @Override - public BulkOperationResults completeTasks(List taskIds) + public BulkOperationResults completeTasks(List taskIdsToBeCompleted) throws InvalidArgumentException { try { - LOGGER.debug("entry to completeTasks(taskIds = {})", taskIds); + LOGGER.debug("entry to completeTasks(taskIds = {})", taskIdsToBeCompleted); taskanaEngine.openConnection(); - // Check pre-conditions with throwing Exceptions - if (taskIds == null) { + if (taskIdsToBeCompleted == null || taskIdsToBeCompleted.isEmpty()) { throw new InvalidArgumentException( "TaskIds can“t be used as NULL-Parameter."); } - // process bulk-complete BulkOperationResults bulkLog = new BulkOperationResults<>(); - if (!taskIds.isEmpty()) { - // remove null/empty taskIds with message - Iterator taskIdIterator = taskIds.iterator(); - while (taskIdIterator.hasNext()) { - String currentTaskId = taskIdIterator.next(); - if (currentTaskId == null || currentTaskId.isEmpty()) { - bulkLog.addError("", - new InvalidArgumentException("IDs with EMPTY or NULL value are not allowed and invalid.")); - taskIdIterator.remove(); - } - } + List taskIds = new ArrayList<>(taskIdsToBeCompleted); + removeNonExistingTasksFromTaskIdList(taskIds, bulkLog); - // query for existing tasks, modify values and LOG missing ones. - List taskSummaries = this.createTaskQuery().idIn(taskIds.toArray(new String[0])).list(); - Instant now = Instant.now(); - taskIdIterator = taskIds.iterator(); - while (taskIdIterator.hasNext()) { - String currentTaskId = taskIdIterator.next(); - TaskSummaryImpl taskSummary = (TaskSummaryImpl) taskSummaries.stream() - .filter(ts -> currentTaskId.equals(ts.getTaskId())) - .findFirst() - .orElse(null); - if (taskSummary == null) { - bulkLog.addError(currentTaskId, new TaskNotFoundException(currentTaskId, "task with id " - + currentTaskId + " was not found.")); - taskIdIterator.remove(); - } else if (taskSummary.getClaimed() == null || taskSummary.getState() != TaskState.CLAIMED) { - bulkLog.addError(currentTaskId, new InvalidStateException(currentTaskId)); - taskIdIterator.remove(); - } else if (!CurrentUserContext.getAccessIds().contains(taskSummary.getOwner())) { - bulkLog.addError(currentTaskId, new InvalidOwnerException( - "TaskOwner is" + taskSummary.getOwner() + ", but current User is " - + CurrentUserContext.getUserid())); - taskIdIterator.remove(); - } else { - taskSummary.setCompleted(now); - taskSummary.setModified(now); - taskSummary.setState(TaskState.COMPLETED); - } - } + List taskSummaries = this.createTaskQuery().idIn(taskIds.toArray(new String[0])).list(); + + checkIfTasksMatchCompleteCriteria(taskIds, taskSummaries, bulkLog); + + updateTasksToBeCompleted(taskIds, taskSummaries); - if (!taskIds.isEmpty() && !taskSummaries.isEmpty()) { - taskMapper.updateCompleted(taskIds, (TaskSummaryImpl) taskSummaries.get(0)); - } - } return bulkLog; } finally { taskanaEngine.returnConnection(); - LOGGER.debug("exit from to completeTasks(taskIds = {})", taskIds); + LOGGER.debug("exit from to completeTasks(taskIds = {})", taskIdsToBeCompleted); } } @@ -352,6 +210,9 @@ public class TaskServiceImpl implements TaskService { standardSettings(task, classification, prioDurationFromAttachments); this.taskMapper.insert(task); LOGGER.debug("Method createTask() created Task '{}'.", task.getId()); + if (HistoryEventProducer.isHistoryEnabled()) { + historyEventProducer.createEvent(new CreatedEvent(task)); + } } return task; } finally { @@ -420,6 +281,7 @@ public class TaskServiceImpl implements TaskService { throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException, InvalidStateException { LOGGER.debug("entry to transfer(taskId = {}, destinationWorkbasketId = {})", taskId, destinationWorkbasketId); TaskImpl task = null; + WorkbasketSummary oldWorkbasketSummary = null; try { taskanaEngine.openConnection(); task = (TaskImpl) getTask(taskId); @@ -427,6 +289,7 @@ public class TaskServiceImpl implements TaskService { if (task.getState() == TaskState.COMPLETED) { throw new InvalidStateException("Completed task with id " + task.getId() + " cannot be transferred."); } + oldWorkbasketSummary = task.getWorkbasketSummary(); // transfer requires TRANSFER in source and APPEND on destination workbasket workbasketService.checkAuthorization(destinationWorkbasketId, WorkbasketPermission.APPEND); @@ -453,6 +316,9 @@ public class TaskServiceImpl implements TaskService { taskMapper.update(task); LOGGER.debug("Method transfer() transferred Task '{}' to destination workbasket {}", taskId, destinationWorkbasketId); + if (HistoryEventProducer.isHistoryEnabled()) { + createTaskTransferredEvent(task, oldWorkbasketSummary, destinationWorkbasket.asSummary()); + } return task; } finally { taskanaEngine.returnConnection(); @@ -466,6 +332,7 @@ public class TaskServiceImpl implements TaskService { LOGGER.debug("entry to transfer(taskId = {}, destinationWorkbasketKey = {}, domain = {})", taskId, destinationWorkbasketKey, domain); TaskImpl task = null; + WorkbasketSummary oldWorkbasketSummary = null; try { taskanaEngine.openConnection(); task = (TaskImpl) getTask(taskId); @@ -474,6 +341,9 @@ public class TaskServiceImpl implements TaskService { throw new InvalidStateException("Completed task with id " + task.getId() + " cannot be transferred."); } + // Save previous workbasket id before transfer it. + oldWorkbasketSummary = task.getWorkbasketSummary(); + // transfer requires TRANSFER in source and APPEND on destination workbasket workbasketService.checkAuthorization(destinationWorkbasketKey, domain, WorkbasketPermission.APPEND); workbasketService.checkAuthorization(task.getWorkbasketSummary().getId(), @@ -499,6 +369,9 @@ public class TaskServiceImpl implements TaskService { taskMapper.update(task); LOGGER.debug("Method transfer() transferred Task '{}' to destination workbasket {}", taskId, destinationWorkbasket.getId()); + if (HistoryEventProducer.isHistoryEnabled()) { + createTaskTransferredEvent(task, oldWorkbasketSummary, destinationWorkbasket.asSummary()); + } return task; } finally { taskanaEngine.returnConnection(); @@ -550,103 +423,6 @@ public class TaskServiceImpl implements TaskService { } } - private BulkOperationResults transferTasks(List taskIdsToBeTransferred, - Workbasket destinationWorkbasket) - throws InvalidArgumentException, WorkbasketNotFoundException, NotAuthorizedException { - - workbasketService.checkAuthorization(destinationWorkbasket.getId(), WorkbasketPermission.APPEND); - - // Check pre-conditions with trowing Exceptions - if (taskIdsToBeTransferred == null) { - throw new InvalidArgumentException("TaskIds must not be null."); - } - BulkOperationResults bulkLog = new BulkOperationResults<>(); - - // convert to ArrayList if necessary to prevent a UnsupportedOperationException while removing - List taskIds = new ArrayList<>(taskIdsToBeTransferred); - - // check tasks Ids exist and not empty - log and remove - 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(); - } - } - - // Check pre-conditions with trowing Exceptions after removing invalid invalid arguments. - if (taskIds.isEmpty()) { - throw new InvalidArgumentException("TaskIds must not contain only invalid arguments."); - } - - // query for existing tasks. use taskMapper.findExistingTasks because this method - // returns only the required information. - List taskSummaries; - if (taskIds.isEmpty()) { - taskSummaries = new ArrayList<>(); - } else { - taskSummaries = taskMapper.findExistingTasks(taskIds); - } - // check source WB (read)+transfer - Set workbasketIds = new HashSet<>(); - taskSummaries.forEach(t -> workbasketIds.add(t.getWorkbasketId())); - WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery(); - query.setUsedToAugmentTasks(true); - List sourceWorkbaskets; - if (taskSummaries.isEmpty()) { - sourceWorkbaskets = new ArrayList<>(); - } else { - sourceWorkbaskets = query - .callerHasPermission(WorkbasketPermission.TRANSFER) - .idIn(workbasketIds.toArray(new String[0])) - .list(); - } - taskIdIterator = taskIds.iterator(); - while (taskIdIterator.hasNext()) { - String currentTaskId = taskIdIterator.next(); - MinimalTaskSummary taskSummary = taskSummaries.stream() - .filter(t -> currentTaskId.equals(t.getTaskId())) - .findFirst() - .orElse(null); - if (taskSummary == null) { - bulkLog.addError(currentTaskId, - new TaskNotFoundException(currentTaskId, "Task with id " + currentTaskId + " was not found.")); - taskIdIterator.remove(); - } else if (taskSummary.getTaskState() == TaskState.COMPLETED) { - bulkLog.addError(currentTaskId, - new InvalidStateException("Completed task with id " + currentTaskId + " cannot be transferred.")); - taskIdIterator.remove(); - } else if (sourceWorkbaskets.stream() - .noneMatch(wb -> taskSummary.getWorkbasketId().equals(wb.getId()))) { - 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(); - TaskSummaryImpl updateObject = new TaskSummaryImpl(); - updateObject.setRead(false); - updateObject.setTransferred(true); - updateObject.setWorkbasketSummary(destinationWorkbasket.asSummary()); - updateObject.setDomain(destinationWorkbasket.getDomain()); - updateObject.setModified(now); - updateObject.setState(TaskState.READY); - updateObject.setOwner(null); - taskMapper.updateTransfered(taskIds, updateObject); - } - return bulkLog; - } - @Override public Task setTaskRead(String taskId, boolean isRead) throws TaskNotFoundException, NotAuthorizedException { @@ -671,6 +447,165 @@ public class TaskServiceImpl implements TaskService { return new TaskQueryImpl(taskanaEngine); } + @Override + public Task newTask(String workbasketId) { + TaskImpl task = new TaskImpl(); + WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl(); + wb.setId(workbasketId); + task.setWorkbasketSummary(wb); + return task; + } + + @Override + public Task newTask(String workbasketKey, String domain) { + TaskImpl task = new TaskImpl(); + WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl(); + wb.setKey(workbasketKey); + wb.setDomain(domain); + task.setWorkbasketSummary(wb); + return task; + } + + @Override + public Attachment newAttachment() { + return new AttachmentImpl(); + } + + @Override + public void deleteTask(String taskId) + throws TaskNotFoundException, InvalidStateException, NotAuthorizedException { + deleteTask(taskId, false); + } + + @Override + public void forceDeleteTask(String taskId) + throws TaskNotFoundException, InvalidStateException, NotAuthorizedException { + deleteTask(taskId, true); + } + + @Override + public BulkOperationResults deleteTasks(List taskIds) + throws InvalidArgumentException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("entry to deleteTasks(tasks = {})", LoggerUtils.listToString(taskIds)); + } + try { + taskanaEngine.openConnection(); + if (taskIds == null) { + throw new InvalidArgumentException("List of TaskIds must not be null."); + } + + BulkOperationResults bulkLog = new BulkOperationResults<>(); + + if (taskIds.isEmpty()) { + return bulkLog; + } + + List taskSummaries = taskMapper.findExistingTasks(taskIds); + + 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 { + MinimalTaskSummary foundSummary = taskSummaries.stream() + .filter(taskState -> currentTaskId.equals(taskState.getTaskId())) + .findFirst() + .orElse(null); + if (foundSummary == null) { + bulkLog.addError(currentTaskId, new TaskNotFoundException(currentTaskId, + "Task with id " + currentTaskId + " was not found.")); + taskIdIterator.remove(); + } else { + if (!TaskState.COMPLETED.equals(foundSummary.getTaskState())) { + bulkLog.addError(currentTaskId, new InvalidStateException(currentTaskId)); + taskIdIterator.remove(); + } + } + } + } + if (!taskIds.isEmpty()) { + taskMapper.deleteMultiple(taskIds); + } + return bulkLog; + } finally { + LOGGER.debug("exit from deleteTasks()"); + taskanaEngine.returnConnection(); + } + } + + @Override + public List updateTasks(ObjectReference selectionCriteria, + Map customFieldsToUpdate) throws InvalidArgumentException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("entry to updateTasks(selectionCriteria = {}, customFieldsToUpdate = {})", selectionCriteria, + customFieldsToUpdate); + } + validateObjectReference(selectionCriteria, "ObjectReference", "updateTasks call"); + validateCustomFields(customFieldsToUpdate); + CustomPropertySelector fieldSelector = new CustomPropertySelector(); + TaskImpl updated = initUpdatedTask(customFieldsToUpdate, fieldSelector); + + try { + taskanaEngine.openConnection(); + + // use query in order to find only those tasks that are visible to the current user + List taskSummaries = getTasksToChange(selectionCriteria); + + List changedTasks = new ArrayList<>(); + if (!taskSummaries.isEmpty()) { + changedTasks = taskSummaries.stream().map(TaskSummary::getTaskId).collect(Collectors.toList()); + taskMapper.updateTasks(changedTasks, updated, fieldSelector); + LOGGER.debug("updateTasks() updated the following tasks: {} ", + LoggerUtils.listToString(changedTasks)); + } else { + LOGGER.debug("updateTasks() found no tasks for update "); + } + return changedTasks; + } finally { + LOGGER.debug("exit from updateTasks()."); + taskanaEngine.returnConnection(); + } + + } + + @Override + public List updateTasks(List taskIds, Map customFieldsToUpdate) + throws InvalidArgumentException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("entry to updateTasks(taskIds = {}, customFieldsToUpdate = {})", taskIds, + customFieldsToUpdate); + } + + validateCustomFields(customFieldsToUpdate); + CustomPropertySelector fieldSelector = new CustomPropertySelector(); + TaskImpl updatedTask = initUpdatedTask(customFieldsToUpdate, fieldSelector); + + try { + taskanaEngine.openConnection(); + + // use query in order to find only those tasks that are visible to the current user + List taskSummaries = getTasksToChange(taskIds); + + List changedTasks = new ArrayList<>(); + if (!taskSummaries.isEmpty()) { + changedTasks = taskSummaries.stream().map(TaskSummary::getTaskId).collect(Collectors.toList()); + taskMapper.updateTasks(changedTasks, updatedTask, fieldSelector); + LOGGER.debug("updateTasks() updated the following tasks: {} ", + LoggerUtils.listToString(changedTasks)); + } else { + LOGGER.debug("updateTasks() found no tasks for update "); + } + return changedTasks; + } finally { + LOGGER.debug("exit from updateTasks()."); + taskanaEngine.returnConnection(); + } + } + @Override public Task updateTask(Task task) throws InvalidArgumentException, TaskNotFoundException, ConcurrencyException, @@ -761,29 +696,190 @@ public class TaskServiceImpl implements TaskService { } } - List augmentTaskSummariesByContainedSummaries(List taskSummaries) { - LOGGER.debug("entry to augmentTaskSummariesByContainedSummaries()"); - List result = new ArrayList<>(); - if (taskSummaries == null || taskSummaries.isEmpty()) { - return result; + private BulkOperationResults transferTasks(List taskIdsToBeTransferred, + Workbasket destinationWorkbasket) + throws InvalidArgumentException, WorkbasketNotFoundException, NotAuthorizedException { + + workbasketService.checkAuthorization(destinationWorkbasket.getId(), WorkbasketPermission.APPEND); + + if (taskIdsToBeTransferred == null) { + throw new InvalidArgumentException("TaskIds must not be null."); + } + BulkOperationResults bulkLog = new BulkOperationResults<>(); + List taskIds = new ArrayList<>(taskIdsToBeTransferred); + removeNonExistingTasksFromTaskIdList(taskIds, bulkLog); + + if (taskIds.isEmpty()) { + throw new InvalidArgumentException("TaskIds must not contain only invalid arguments."); } - Set taskIdSet = taskSummaries.stream().map(TaskSummaryImpl::getTaskId).collect(Collectors.toSet()); - String[] taskIdArray = taskIdSet.toArray(new String[0]); + List taskSummaries; + if (taskIds.isEmpty()) { + taskSummaries = new ArrayList<>(); + } else { + taskSummaries = taskMapper.findExistingTasks(taskIds); + } + checkIfTransferConditionsAreFulfilled(taskIds, taskSummaries, bulkLog); + updateTasksToBeTransferred(taskIds, taskSummaries, destinationWorkbasket); + return bulkLog; + } - LOGGER.debug("augmentTaskSummariesByContainedSummaries() about to query for attachmentSummaries "); - List attachmentSummaries = attachmentMapper - .findAttachmentSummariesByTaskIds(taskIdArray); + private void removeNonExistingTasksFromTaskIdList(List taskIds, + BulkOperationResults bulkLog) { + 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(); + } + } + } - List classifications = findClassificationsForTasksAndAttachments(taskSummaries, - attachmentSummaries); + private void checkIfTransferConditionsAreFulfilled(List taskIds, List taskSummaries, + BulkOperationResults bulkLog) { + Set workbasketIds = new HashSet<>(); + taskSummaries.forEach(t -> workbasketIds.add(t.getWorkbasketId())); + WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery(); + query.setUsedToAugmentTasks(true); + List sourceWorkbaskets; + if (taskSummaries.isEmpty()) { + sourceWorkbaskets = new ArrayList<>(); + } else { + sourceWorkbaskets = query + .callerHasPermission(WorkbasketPermission.TRANSFER) + .idIn(workbasketIds.toArray(new String[0])) + .list(); + } + checkIfTasksMatchTransferCriteria(taskIds, taskSummaries, sourceWorkbaskets, bulkLog); + } - addClassificationSummariesToTaskSummaries(taskSummaries, classifications); - addWorkbasketSummariesToTaskSummaries(taskSummaries); - addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries, classifications); - result.addAll(taskSummaries); - LOGGER.debug("exit from to augmentTaskSummariesByContainedSummaries()"); - return result; + private void checkIfTasksMatchTransferCriteria(List taskIds, List taskSummaries, + List sourceWorkbaskets, BulkOperationResults bulkLog) { + Iterator taskIdIterator = taskIds.iterator(); + while (taskIdIterator.hasNext()) { + String currentTaskId = taskIdIterator.next(); + MinimalTaskSummary taskSummary = taskSummaries.stream() + .filter(t -> currentTaskId.equals(t.getTaskId())) + .findFirst() + .orElse(null); + if (taskSummary == null) { + bulkLog.addError(currentTaskId, + new TaskNotFoundException(currentTaskId, "Task with id " + currentTaskId + " was not found.")); + taskIdIterator.remove(); + } else if (taskSummary.getTaskState() == TaskState.COMPLETED) { + bulkLog.addError(currentTaskId, + new InvalidStateException("Completed task with id " + currentTaskId + " cannot be transferred.")); + taskIdIterator.remove(); + } else if (sourceWorkbaskets.stream() + .noneMatch(wb -> taskSummary.getWorkbasketId().equals(wb.getId()))) { + bulkLog.addError(currentTaskId, + new NotAuthorizedException( + "The workbasket of this task got not TRANSFER permissions. TaskId=" + currentTaskId)); + taskIdIterator.remove(); + } + } + } + + private void checkIfTasksMatchCompleteCriteria(List taskIds, List taskSummaries, + BulkOperationResults bulkLog) { + Instant now = Instant.now(); + Iterator taskIdIterator = taskIds.iterator(); + while (taskIdIterator.hasNext()) { + String currentTaskId = taskIdIterator.next(); + TaskSummaryImpl taskSummary = (TaskSummaryImpl) taskSummaries.stream() + .filter(ts -> currentTaskId.equals(ts.getTaskId())) + .findFirst() + .orElse(null); + if (taskSummary == null) { + bulkLog.addError(currentTaskId, new TaskNotFoundException(currentTaskId, "task with id " + + currentTaskId + " was not found.")); + taskIdIterator.remove(); + } else if (taskSummary.getClaimed() == null || taskSummary.getState() != TaskState.CLAIMED) { + bulkLog.addError(currentTaskId, new InvalidStateException(currentTaskId)); + taskIdIterator.remove(); + } else if (!CurrentUserContext.getAccessIds().contains(taskSummary.getOwner())) { + bulkLog.addError(currentTaskId, new InvalidOwnerException( + "TaskOwner is" + taskSummary.getOwner() + ", but current User is " + + CurrentUserContext.getUserid())); + taskIdIterator.remove(); + } else { + taskSummary.setCompleted(now); + taskSummary.setModified(now); + taskSummary.setState(TaskState.COMPLETED); + } + } + } + + private void updateTasksToBeTransferred(List taskIds, + List taskSummaries, Workbasket destinationWorkbasket) { + + taskSummaries = taskSummaries.stream() + .filter(ts -> taskIds.contains(ts.getTaskId())) + .collect( + Collectors.toList()); + if (!taskSummaries.isEmpty()) { + Instant now = Instant.now(); + TaskSummaryImpl updateObject = new TaskSummaryImpl(); + updateObject.setRead(false); + updateObject.setTransferred(true); + updateObject.setWorkbasketSummary(destinationWorkbasket.asSummary()); + updateObject.setDomain(destinationWorkbasket.getDomain()); + updateObject.setModified(now); + updateObject.setState(TaskState.READY); + updateObject.setOwner(null); + taskMapper.updateTransfered(taskIds, updateObject); + if (HistoryEventProducer.isHistoryEnabled()) { + createTasksTransferredEvents(taskSummaries, updateObject); + } + } + + } + + private void updateTasksToBeCompleted(List taskIds, + List taskSummaries) { + if (!taskIds.isEmpty() && !taskSummaries.isEmpty()) { + taskMapper.updateCompleted(taskIds, (TaskSummaryImpl) taskSummaries.get(0)); + if (HistoryEventProducer.isHistoryEnabled()) { + createTasksCompletedEvents(taskSummaries); + } + } + } + + private Task cancelClaim(String taskId, boolean forceUnclaim) + throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, NotAuthorizedException { + String userId = CurrentUserContext.getUserid(); + LOGGER.debug("entry to cancelClaim(taskId = {}), userId = {}, forceUnclaim = {})", taskId, userId, + forceUnclaim); + TaskImpl task = null; + try { + taskanaEngine.openConnection(); + task = (TaskImpl) getTask(taskId); + TaskState state = task.getState(); + if (state == TaskState.COMPLETED) { + throw new InvalidStateException("Task with id " + taskId + " is already completed."); + } + if (state == TaskState.CLAIMED && !forceUnclaim && !userId.equals(task.getOwner())) { + throw new InvalidOwnerException( + "Task with id " + taskId + " is already claimed by " + task.getOwner() + "."); + } + Instant now = Instant.now(); + task.setOwner(null); + task.setModified(now); + task.setClaimed(null); + task.setRead(true); + task.setState(TaskState.READY); + taskMapper.update(task); + LOGGER.debug("Task '{}' unclaimed by user '{}'.", taskId, userId); + if (HistoryEventProducer.isHistoryEnabled()) { + historyEventProducer.createEvent(new ClaimCancelledEvent(task)); + } + } finally { + taskanaEngine.returnConnection(); + LOGGER.debug("exit from cancelClaim()"); + } + return task; } private void addClassificationSummariesToTaskSummaries(List tasks, @@ -864,7 +960,6 @@ public class TaskServiceImpl implements TaskService { .collect( Collectors.toSet()); String[] workbasketIdArray = workbasketIdSet.toArray(new String[0]); - // perform workbasket query LOGGER.debug("addWorkbasketSummariesToTaskSummaries() about to query workbaskets"); WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery(); query.setUsedToAugmentTasks(true); @@ -872,13 +967,11 @@ public class TaskServiceImpl implements TaskService { List workbaskets = query .idIn(workbasketIdArray) .list(); - // assign query results to appropriate tasks. Iterator taskIterator = taskSummaries.iterator(); while (taskIterator.hasNext()) { TaskSummaryImpl task = taskIterator.next(); String workbasketId = task.getWorkbasketSummaryImpl().getId(); - // find the appropriate workbasket from the query result WorkbasketSummary aWorkbasket = workbaskets.stream() .filter(x -> workbasketId != null && workbasketId.equals(x.getId())) .findFirst() @@ -888,7 +981,7 @@ public class TaskServiceImpl implements TaskService { taskIterator.remove(); continue; } - // set the classification on the task object + task.setWorkbasketSummary(aWorkbasket); } LOGGER.debug("exit from addWorkbasketSummariesToTaskSummaries()"); @@ -960,188 +1053,8 @@ public class TaskServiceImpl implements TaskService { return result; } - @Override - public Task newTask(String workbasketId) { - TaskImpl task = new TaskImpl(); - WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl(); - wb.setId(workbasketId); - task.setWorkbasketSummary(wb); - return task; - } - - @Override - public Task newTask(String workbasketKey, String domain) { - TaskImpl task = new TaskImpl(); - WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl(); - wb.setKey(workbasketKey); - wb.setDomain(domain); - task.setWorkbasketSummary(wb); - return task; - } - - @Override - public Attachment newAttachment() { - return new AttachmentImpl(); - } - - @Override - public void deleteTask(String taskId) - throws TaskNotFoundException, InvalidStateException, NotAuthorizedException { - deleteTask(taskId, false); - } - - @Override - public void forceDeleteTask(String taskId) - throws TaskNotFoundException, InvalidStateException, NotAuthorizedException { - deleteTask(taskId, true); - } - - private void deleteTask(String taskId, boolean forceDelete) - throws TaskNotFoundException, InvalidStateException, NotAuthorizedException { - LOGGER.debug("entry to deleteTask(taskId = {} , forceDelete = {})", taskId, forceDelete); - taskanaEngine.checkRoleMembership(TaskanaRole.ADMIN); - TaskImpl task = null; - try { - taskanaEngine.openConnection(); - task = (TaskImpl) getTask(taskId); - - if (!TaskState.COMPLETED.equals(task.getState()) && !forceDelete) { - throw new InvalidStateException("Cannot delete Task " + taskId + " because it is not completed."); - } - taskMapper.delete(taskId); - LOGGER.debug("Task {} deleted.", taskId); - } finally { - taskanaEngine.returnConnection(); - LOGGER.debug("exit from deleteTask()."); - } - } - - @Override - public BulkOperationResults deleteTasks(List taskIds) - throws InvalidArgumentException { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("entry to deleteTasks(tasks = {})", LoggerUtils.listToString(taskIds)); - } - try { - taskanaEngine.openConnection(); - if (taskIds == null) { - throw new InvalidArgumentException("List of TaskIds must not be null."); - } - - BulkOperationResults bulkLog = new BulkOperationResults<>(); - - if (taskIds.isEmpty()) { - return bulkLog; - } - - List taskSummaries = taskMapper.findExistingTasks(taskIds); - - 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 { - MinimalTaskSummary foundSummary = taskSummaries.stream() - .filter(taskState -> currentTaskId.equals(taskState.getTaskId())) - .findFirst() - .orElse(null); - if (foundSummary == null) { - bulkLog.addError(currentTaskId, new TaskNotFoundException(currentTaskId, - "Task with id " + currentTaskId + " was not found.")); - taskIdIterator.remove(); - } else { - if (!TaskState.COMPLETED.equals(foundSummary.getTaskState())) { - bulkLog.addError(currentTaskId, new InvalidStateException(currentTaskId)); - taskIdIterator.remove(); - } - } - } - } - if (!taskIds.isEmpty()) { - taskMapper.deleteMultiple(taskIds); - } - return bulkLog; - } finally { - LOGGER.debug("exit from updateTasks()"); - taskanaEngine.returnConnection(); - } - } - - @Override - public List updateTasks(ObjectReference selectionCriteria, - Map customFieldsToUpdate) throws InvalidArgumentException { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("entry to updateTasks(selectionCriteria = {}, customFieldsToUpdate = {})", selectionCriteria, - customFieldsToUpdate); - } - validateObjectReference(selectionCriteria, "ObjectReference", "updateTasks call"); - validateCustomFields(customFieldsToUpdate); - CustomPropertySelector fieldSelector = new CustomPropertySelector(); - TaskImpl updated = initUpdatedTask(customFieldsToUpdate, fieldSelector); - - try { - taskanaEngine.openConnection(); - - // use query in order to find only those tasks that are visible to the current user - List taskSummaries = getTasksToChange(selectionCriteria); - - List changedTasks = new ArrayList<>(); - if (!taskSummaries.isEmpty()) { - changedTasks = taskSummaries.stream().map(TaskSummary::getTaskId).collect(Collectors.toList()); - taskMapper.updateTasks(changedTasks, updated, fieldSelector); - LOGGER.debug("updateTasks() updated the following tasks: {} ", - LoggerUtils.listToString(changedTasks)); - } else { - LOGGER.debug("updateTasks() found no tasks for update "); - } - return changedTasks; - } finally { - LOGGER.debug("exit from updateTasks()."); - taskanaEngine.returnConnection(); - } - - } - - @Override - public List updateTasks(List taskIds, - Map customFieldsToUpdate) throws InvalidArgumentException { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("entry to updateTasks(taskIds = {}, customFieldsToUpdate = {})", taskIds, - customFieldsToUpdate); - } - - validateCustomFields(customFieldsToUpdate); - CustomPropertySelector fieldSelector = new CustomPropertySelector(); - TaskImpl updatedTask = initUpdatedTask(customFieldsToUpdate, fieldSelector); - - try { - taskanaEngine.openConnection(); - - // use query in order to find only those tasks that are visible to the current user - List taskSummaries = getTasksToChange(taskIds); - - List changedTasks = new ArrayList<>(); - if (!taskSummaries.isEmpty()) { - changedTasks = taskSummaries.stream().map(TaskSummary::getTaskId).collect(Collectors.toList()); - taskMapper.updateTasks(changedTasks, updatedTask, fieldSelector); - LOGGER.debug("updateTasks() updated the following tasks: {} ", - LoggerUtils.listToString(changedTasks)); - } else { - LOGGER.debug("updateTasks() found no tasks for update "); - } - return changedTasks; - } finally { - LOGGER.debug("exit from deleteTasks()."); - taskanaEngine.returnConnection(); - } - - } - private TaskImpl initUpdatedTask(Map customFieldsToUpdate, CustomPropertySelector fieldSelector) - throws InvalidArgumentException { + throws InvalidArgumentException { TaskImpl newTask = new TaskImpl(); newTask.setModified(Instant.now()); @@ -1490,10 +1403,11 @@ public class TaskServiceImpl implements TaskService { .list(); // tasks indirectly affected via attachments - List taskIdsFromAttachments = attachmentMapper.findTaskIdsAffectedByClassificationChange(classificationId); + List taskIdsFromAttachments = attachmentMapper.findTaskIdsAffectedByClassificationChange( + classificationId); List filteredTaskIdsFromAttachments = taskIdsFromAttachments.isEmpty() ? new ArrayList<>() - : taskMapper.filterTaskIdsForNotCompleted(taskIdsFromAttachments); + : taskMapper.filterTaskIdsForNotCompleted(taskIdsFromAttachments); Set affectedTaskIds = new HashSet<>(filteredTaskIdsFromAttachments); for (TaskSummary task : tasks) { @@ -1506,7 +1420,6 @@ public class TaskServiceImpl implements TaskService { return affectedTaskIds; } - public void refreshPriorityAndDueDate(String taskId) throws ClassificationNotFoundException { LOGGER.debug("entry to refreshPriorityAndDueDate(taskId = {})", taskId); @@ -1579,6 +1492,84 @@ public class TaskServiceImpl implements TaskService { task.setPriority(newPriority); } + private Task completeTask(String taskId, boolean isForced) + throws TaskNotFoundException, InvalidOwnerException, InvalidStateException, NotAuthorizedException { + String userId = CurrentUserContext.getUserid(); + LOGGER.debug("entry to completeTask(id = {}, userId = {}, isForced = {})", taskId, userId, isForced); + TaskImpl task = null; + try { + taskanaEngine.openConnection(); + task = (TaskImpl) this.getTask(taskId); + + if (task.getState() == TaskState.COMPLETED) { + return task; + } + + // check pre-conditions for non-forced invocation + if (!isForced) { + if (task.getClaimed() == null || task.getState() != TaskState.CLAIMED) { + throw new InvalidStateException("Task with id " + taskId + " has to be claimed before."); + } else if (!CurrentUserContext.getAccessIds().contains(task.getOwner())) { + throw new InvalidOwnerException( + "Owner of task " + taskId + " is " + task.getOwner() + ", but current User is " + userId); + } + } else { + // CLAIM-forced, if task was not already claimed before. + if (task.getClaimed() == null || task.getState() != TaskState.CLAIMED) { + task = (TaskImpl) this.forceClaim(taskId); + } + } + Instant now = Instant.now(); + task.setCompleted(now); + task.setModified(now); + task.setState(TaskState.COMPLETED); + task.setOwner(userId); + taskMapper.update(task); + LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId); + if (HistoryEventProducer.isHistoryEnabled()) { + historyEventProducer.createEvent(new CompletedEvent(task)); + } + } finally { + taskanaEngine.returnConnection(); + LOGGER.debug("exit from completeTask()"); + } + return task; + } + + private Task claim(String taskId, boolean forceClaim) + throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, NotAuthorizedException { + String userId = CurrentUserContext.getUserid(); + LOGGER.debug("entry to claim(id = {}, userId = {}, forceClaim = {})", taskId, userId, forceClaim); + TaskImpl task = null; + try { + taskanaEngine.openConnection(); + task = (TaskImpl) getTask(taskId); + TaskState state = task.getState(); + if (state == TaskState.COMPLETED) { + throw new InvalidStateException("Task with id " + taskId + " is already completed."); + } + if (state == TaskState.CLAIMED && !forceClaim && !task.getOwner().equals(userId)) { + throw new InvalidOwnerException( + "Task with id " + taskId + " is already claimed by " + task.getOwner() + "."); + } + Instant now = Instant.now(); + task.setOwner(userId); + task.setModified(now); + task.setClaimed(now); + task.setRead(true); + task.setState(TaskState.CLAIMED); + taskMapper.update(task); + LOGGER.debug("Task '{}' claimed by user '{}'.", taskId, userId); + if (HistoryEventProducer.isHistoryEnabled()) { + historyEventProducer.createEvent(new ClaimedEvent(task)); + } + } finally { + taskanaEngine.returnConnection(); + LOGGER.debug("exit from claim()"); + } + return task; + } + private void updateTaskPrioDurationFromAttachments(TaskImpl task, PrioDurationHolder prioDurationFromAttachments) { if (prioDurationFromAttachments.getDuration() != null) { long days = converter.convertWorkingDaysToDays(task.getPlanned(), @@ -1623,6 +1614,77 @@ public class TaskServiceImpl implements TaskService { return result; } + private void deleteTask(String taskId, boolean forceDelete) + throws TaskNotFoundException, InvalidStateException, NotAuthorizedException { + LOGGER.debug("entry to deleteTask(taskId = {} , forceDelete = {})", taskId, forceDelete); + taskanaEngine.checkRoleMembership(TaskanaRole.ADMIN); + TaskImpl task = null; + try { + taskanaEngine.openConnection(); + task = (TaskImpl) getTask(taskId); + + if (!TaskState.COMPLETED.equals(task.getState()) && !forceDelete) { + throw new InvalidStateException("Cannot delete Task " + taskId + " because it is not completed."); + } + taskMapper.delete(taskId); + LOGGER.debug("Task {} deleted.", taskId); + } finally { + taskanaEngine.returnConnection(); + LOGGER.debug("exit from deleteTask()."); + } + } + + private void createTaskTransferredEvent(Task task, WorkbasketSummary oldWorkbasketSummary, + WorkbasketSummary newWorkbasketSummary) { + historyEventProducer.createEvent(new TransferredEvent(task, oldWorkbasketSummary, newWorkbasketSummary)); + } + + private void createTasksTransferredEvents(List taskSummaries, TaskSummaryImpl updateObject) { + taskSummaries.stream().forEach(task -> { + TaskImpl newTask = (TaskImpl) newTask(task.getWorkbasketId()); + newTask.setWorkbasketSummary(updateObject.getWorkbasketSummary()); + newTask.setRead(updateObject.isRead()); + newTask.setTransferred(updateObject.isTransferred()); + newTask.setWorkbasketSummary(updateObject.getWorkbasketSummary()); + newTask.setDomain(updateObject.getDomain()); + newTask.setModified(updateObject.getModified()); + newTask.setState(updateObject.getState()); + newTask.setOwner(updateObject.getOwner()); + createTaskTransferredEvent(newTask, newTask.getWorkbasketSummary(), + updateObject.getWorkbasketSummary()); + }); + } + + private void createTasksCompletedEvents(List taskSummaries) { + taskSummaries.stream().forEach(task -> historyEventProducer.createEvent(new CompletedEvent(task)) + ); + } + + List augmentTaskSummariesByContainedSummaries(List taskSummaries) { + LOGGER.debug("entry to augmentTaskSummariesByContainedSummaries()"); + List result = new ArrayList<>(); + if (taskSummaries == null || taskSummaries.isEmpty()) { + return result; + } + + Set taskIdSet = taskSummaries.stream().map(TaskSummaryImpl::getTaskId).collect(Collectors.toSet()); + String[] taskIdArray = taskIdSet.toArray(new String[0]); + + LOGGER.debug("augmentTaskSummariesByContainedSummaries() about to query for attachmentSummaries "); + List attachmentSummaries = attachmentMapper + .findAttachmentSummariesByTaskIds(taskIdArray); + + List classifications = findClassificationsForTasksAndAttachments(taskSummaries, + attachmentSummaries); + + addClassificationSummariesToTaskSummaries(taskSummaries, classifications); + addWorkbasketSummariesToTaskSummaries(taskSummaries); + addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries, classifications); + result.addAll(taskSummaries); + LOGGER.debug("exit from to augmentTaskSummariesByContainedSummaries()"); + return result; + } + /** * hold a pair of priority and Duration. * diff --git a/lib/taskana-core/src/test/java/acceptance/history/HistoryEventProducerTest.java b/lib/taskana-core/src/test/java/acceptance/history/TaskEventProducerTest.java similarity index 89% rename from lib/taskana-core/src/test/java/acceptance/history/HistoryEventProducerTest.java rename to lib/taskana-core/src/test/java/acceptance/history/TaskEventProducerTest.java index b7cd854a0..c23982f39 100644 --- a/lib/taskana-core/src/test/java/acceptance/history/HistoryEventProducerTest.java +++ b/lib/taskana-core/src/test/java/acceptance/history/TaskEventProducerTest.java @@ -11,7 +11,7 @@ import pro.taskana.impl.TaskanaEngineImpl; /** * Acceptance test for historyEventProducer class. */ -public class HistoryEventProducerTest extends AbstractAccTest { +public class TaskEventProducerTest extends AbstractAccTest { @Test public void testHistoryEventProducerIsNotEnabled() {