
pro.taskana.task.internal.TaskServiceImpl Maven / Gradle / Ivy
package pro.taskana.task.internal;
import java.lang.reflect.Field;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.ibatis.exceptions.PersistenceException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.classification.api.models.Classification;
import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.LoggerUtils;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.CustomPropertySelector;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.security.CurrentUserContext;
import pro.taskana.common.internal.util.CheckedFunction;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.common.internal.util.Pair;
import pro.taskana.common.internal.util.Triplet;
import pro.taskana.spi.history.api.events.task.ClaimCancelledEvent;
import pro.taskana.spi.history.api.events.task.ClaimedEvent;
import pro.taskana.spi.history.api.events.task.CompletedEvent;
import pro.taskana.spi.history.api.events.task.CreatedEvent;
import pro.taskana.spi.history.api.events.task.UpdatedEvent;
import pro.taskana.spi.history.internal.HistoryEventProducer;
import pro.taskana.task.api.CallbackState;
import pro.taskana.task.api.TaskQuery;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
import pro.taskana.task.api.exceptions.InvalidOwnerException;
import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.TaskAlreadyExistException;
import pro.taskana.task.api.exceptions.TaskCommentNotFoundException;
import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.exceptions.UpdateFailedException;
import pro.taskana.task.api.models.Attachment;
import pro.taskana.task.api.models.ObjectReference;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskComment;
import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.task.internal.ServiceLevelHandler.BulkLog;
import pro.taskana.task.internal.models.AttachmentImpl;
import pro.taskana.task.internal.models.AttachmentSummaryImpl;
import pro.taskana.task.internal.models.MinimalTaskSummary;
import pro.taskana.task.internal.models.TaskImpl;
import pro.taskana.task.internal.models.TaskSummaryImpl;
import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.Workbasket;
import pro.taskana.workbasket.api.models.WorkbasketSummary;
import pro.taskana.workbasket.internal.WorkbasketQueryImpl;
import pro.taskana.workbasket.internal.models.WorkbasketSummaryImpl;
/** This is the implementation of TaskService. */
@SuppressWarnings("checkstyle:OverloadMethodsDeclarationOrder")
public class TaskServiceImpl implements TaskService {
public static final String IDS_WITH_EMPTY_OR_NULL_VALUE_ARE_NOT_ALLOWED =
"IDs with EMPTY or NULL value are not allowed.";
private static final String TASK_WITH_ID_IS_NOT_READY =
"Task with id %s is in state %s and not in state ready.";
private static final String TASK_WITH_ID_WAS_NOT_FOUND = "Task with id %s was not found.";
private static final String TASK_WITH_ID_HAS_TO_BE_CLAIMED_BEFORE =
"Task with Id %s has to be claimed before.";
private static final String TASK_WITH_ID_CALLBACK_NOT_PROCESSED =
"Task wit Id %s cannot be deleted because its callback is not yet processed";
private static final String TASK_WITH_ID_IS_ALREADY_CLAIMED_BY =
"Task with id %s is already claimed by %s.";
private static final String WAS_MARKED_FOR_DELETION = " was marked for deletion";
private static final String THE_WORKBASKET = "The workbasket ";
private static final String TASK = "Task";
private static final Logger LOGGER = LoggerFactory.getLogger(TaskServiceImpl.class);
private static final String ID_PREFIX_TASK = "TKI";
private static final String ID_PREFIX_EXT_TASK_ID = "ETI";
private static final String ID_PREFIX_BUSINESS_PROCESS = "BPI";
private static final Set ALLOWED_KEYS =
IntStream.rangeClosed(1, 16).mapToObj(String::valueOf).collect(Collectors.toSet());
private static final String TASK_WITH_ID_IS_ALREADY_IN_END_STATE =
"Task with Id %s is already in an end state.";
private InternalTaskanaEngine taskanaEngine;
private WorkbasketService workbasketService;
private ClassificationService classificationService;
private TaskMapper taskMapper;
private AttachmentMapper attachmentMapper;
private HistoryEventProducer historyEventProducer;
private TaskTransferrer taskTransferrer;
private TaskCommentServiceImpl taskCommentService;
private ServiceLevelHandler serviceLevelHandler;
private AttachmentHandler attachmentHandler;
public TaskServiceImpl(
InternalTaskanaEngine taskanaEngine,
TaskMapper taskMapper,
TaskCommentMapper taskCommentMapper,
AttachmentMapper attachmentMapper) {
super();
this.taskanaEngine = taskanaEngine;
this.taskMapper = taskMapper;
this.workbasketService = taskanaEngine.getEngine().getWorkbasketService();
this.attachmentMapper = attachmentMapper;
this.classificationService = taskanaEngine.getEngine().getClassificationService();
this.historyEventProducer = taskanaEngine.getHistoryEventProducer();
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
this.taskCommentService = new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, this);
this.serviceLevelHandler = new ServiceLevelHandler(taskanaEngine, taskMapper, attachmentMapper);
this.attachmentHandler = new AttachmentHandler(attachmentMapper, classificationService);
}
@Override
public Task claim(String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
return claim(taskId, false);
}
@Override
public Task forceClaim(String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
return claim(taskId, true);
}
@Override
public Task cancelClaim(String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
return this.cancelClaim(taskId, false);
}
@Override
public Task forceCancelClaim(String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
return this.cancelClaim(taskId, true);
}
@Override
public Task completeTask(String taskId)
throws TaskNotFoundException, InvalidOwnerException, InvalidStateException,
NotAuthorizedException {
return completeTask(taskId, false);
}
@Override
public Task forceCompleteTask(String taskId)
throws TaskNotFoundException, InvalidOwnerException, InvalidStateException,
NotAuthorizedException {
return completeTask(taskId, true);
}
@Override
public Task createTask(Task taskToCreate)
throws NotAuthorizedException, WorkbasketNotFoundException, ClassificationNotFoundException,
TaskAlreadyExistException, InvalidArgumentException {
LOGGER.debug("entry to createTask(task = {})", taskToCreate);
TaskImpl task = (TaskImpl) taskToCreate;
try {
taskanaEngine.openConnection();
if (task.getId() != null && !task.getId().equals("")) {
throw new TaskAlreadyExistException(task.getId());
}
LOGGER.debug("Task {} cannot be found, so it can be created.", task.getId());
Workbasket workbasket;
if (task.getWorkbasketSummary().getId() != null) {
workbasket = workbasketService.getWorkbasket(task.getWorkbasketSummary().getId());
} else if (task.getWorkbasketKey() != null) {
workbasket = workbasketService.getWorkbasket(task.getWorkbasketKey(), task.getDomain());
} else {
String workbasketId = taskanaEngine.getTaskRoutingManager().determineWorkbasketId(task);
if (workbasketId != null) {
workbasket = workbasketService.getWorkbasket(workbasketId);
task.setWorkbasketSummary(workbasket.asSummary());
} else {
throw new InvalidArgumentException("Cannot create a task outside a workbasket");
}
}
if (workbasket.isMarkedForDeletion()) {
throw new WorkbasketNotFoundException(
workbasket.getId(), THE_WORKBASKET + workbasket.getId() + WAS_MARKED_FOR_DELETION);
}
task.setWorkbasketSummary(workbasket.asSummary());
task.setDomain(workbasket.getDomain());
workbasketService.checkAuthorization(
task.getWorkbasketSummary().getId(), WorkbasketPermission.APPEND);
// we do use the key and not the ID to make sure that we use the classification from the right
// domain.
// otherwise we would have to check the classification and its domain for validity.
String classificationKey = task.getClassificationKey();
if (classificationKey == null || classificationKey.length() == 0) {
throw new InvalidArgumentException("classificationKey of task must not be empty");
}
Classification classification =
this.classificationService.getClassification(classificationKey, workbasket.getDomain());
task.setClassificationSummary(classification.asSummary());
ObjectReference.validate(task.getPrimaryObjRef(), "primary ObjectReference", TASK);
standardSettings(task, classification);
setCallbackStateOnTaskCreation(task);
try {
this.taskMapper.insert(task);
LOGGER.debug("Method createTask() created Task '{}'.", task.getId());
if (HistoryEventProducer.isHistoryEnabled()) {
String details = determineChangesInTaskAttributes(newTask(), task);
historyEventProducer.createEvent(
new CreatedEvent(task, CurrentUserContext.getUserid(), details));
}
} catch (PersistenceException e) {
// Error messages:
// Postgres: ERROR: duplicate key value violates unique constraint "uc_external_id"
// DB/2: ### Error updating database. Cause:
// com.ibm.db2.jcc.am.SqlIntegrityConstraintViolationException: DB2 SQL Error: SQLCODE=-803,
// SQLSTATE=23505, SQLERRMC=2;TASKANA.TASK, DRIVER=4.22.29
// ### The error may involve pro.taskana.mappings.TaskMapper.insert-Inline
// ### The error occurred while setting parameters
// ### SQL: INSERT INTO TASK(ID, EXTERNAL_ID, CREATED, CLAIMED, COMPLETED, MODIFIED,
// PLANNED, DUE, NAME, CREATOR, DESCRIPTION, NOTE, PRIORITY, STATE,
// CLASSIFICATION_CATEGORY, CLASSIFICATION_KEY, CLASSIFICATION_ID, WORKBASKET_ID,
// WORKBASKET_KEY, DOMAIN, BUSINESS_PROCESS_ID, PARENT_BUSINESS_PROCESS_ID, OWNER,
// POR_COMPANY, POR_SYSTEM, POR_INSTANCE, POR_TYPE, POR_VALUE, IS_READ, IS_TRANSFERRED,
// CALLBACK_INFO, CUSTOM_ATTRIBUTES, CUSTOM_1, CUSTOM_2, CUSTOM_3, CUSTOM_4, CUSTOM_5,
// CUSTOM_6, CUSTOM_7, CUSTOM_8, CUSTOM_9, CUSTOM_10, CUSTOM_11, CUSTOM_12, CUSTOM_13,
// CUSTOM_14, CUSTOM_15, CUSTOM_16 ) VALUES(?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
// ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
// ?, ?)
// ### Cause: com.ibm.db2.jcc.am.SqlIntegrityConstraintViolationException: DB2 SQL
// Error: SQLCODE=-803, SQLSTATE=23505, SQLERRMC=2;TASKANA.TASK, DRIVER=4.22.29
// H2: ### Error updating database. Cause: org.h2.jdbc.JdbcSQLException: Unique index or
// primary key violation: "UC_EXTERNAL_ID_INDEX_2 ON TASKANA.TASK(EXTERNAL_ID) ...
String msg = e.getMessage() != null ? e.getMessage().toLowerCase() : null;
if (msg != null
&& (msg.contains("violation") || msg.contains("violates") || msg.contains("verletzt"))
&& msg.contains("external_id")) {
throw new TaskAlreadyExistException(
"Task with external id " + task.getExternalId() + " already exists");
} else {
throw e;
}
}
return task;
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from createTask(task = {})", task);
}
}
@Override
public Task getTask(String id) throws NotAuthorizedException, TaskNotFoundException {
LOGGER.debug("entry to getTaskById(id = {})", id);
TaskImpl resultTask = null;
try {
taskanaEngine.openConnection();
resultTask = taskMapper.findById(id);
if (resultTask != null) {
WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery();
query.setUsedToAugmentTasks(true);
String workbasketId = resultTask.getWorkbasketSummary().getId();
List workbaskets = query.idIn(workbasketId).list();
if (workbaskets.isEmpty()) {
String currentUser = CurrentUserContext.getUserid();
throw new NotAuthorizedException(
"The current user "
+ currentUser
+ " has no read permission for workbasket "
+ workbasketId,
CurrentUserContext.getUserid());
} else {
resultTask.setWorkbasketSummary(workbaskets.get(0));
}
List attachmentImpls =
attachmentMapper.findAttachmentsByTaskId(resultTask.getId());
if (attachmentImpls == null) {
attachmentImpls = new ArrayList<>();
}
List classifications;
classifications = findClassificationForTaskImplAndAttachments(resultTask, attachmentImpls);
List attachments =
addClassificationSummariesToAttachments(attachmentImpls, classifications);
resultTask.setAttachments(attachments);
String classificationId = resultTask.getClassificationSummary().getId();
ClassificationSummary classification =
classifications.stream()
.filter(c -> c.getId().equals(classificationId))
.findFirst()
.orElse(null);
if (classification == null) {
throw new SystemException(
"Could not find a Classification for task " + resultTask.getId());
}
resultTask.setClassificationSummary(classification);
return resultTask;
} else {
throw new TaskNotFoundException(id, String.format(TASK_WITH_ID_WAS_NOT_FOUND, id));
}
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from getTaskById(). Returning result {} ", resultTask);
}
}
@Override
public Task transfer(String taskId, String destinationWorkbasketId)
throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException,
InvalidStateException {
return taskTransferrer.transfer(taskId, destinationWorkbasketId);
}
@Override
public Task transfer(String taskId, String workbasketKey, String domain)
throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException,
InvalidStateException {
return taskTransferrer.transfer(taskId, workbasketKey, domain);
}
@Override
public Task setTaskRead(String taskId, boolean isRead)
throws TaskNotFoundException, NotAuthorizedException {
LOGGER.debug("entry to setTaskRead(taskId = {}, isRead = {})", taskId, isRead);
TaskImpl task = null;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
task.setRead(isRead);
task.setModified(Instant.now());
taskMapper.update(task);
LOGGER.debug("Method setTaskRead() set read property of Task '{}' to {} ", task, isRead);
return task;
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from setTaskRead(taskId, isRead). Returning result {} ", task);
}
}
@Override
public TaskQuery createTaskQuery() {
return new TaskQueryImpl(taskanaEngine);
}
@Override
public Task newTask() {
return newTask(null);
}
@Override
public Task newTask(String workbasketId) {
TaskImpl task = new TaskImpl();
WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl();
wb.setId(workbasketId);
task.setWorkbasketSummary(wb);
task.setCallbackState(CallbackState.NONE);
return task;
}
@Override
public Task newTask(String workbasketKey, String domain) {
LOGGER.debug("entry to newTask(workbasketKey = {}, domain = {})", workbasketKey, domain);
TaskImpl task = new TaskImpl();
WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl();
wb.setKey(workbasketKey);
wb.setDomain(domain);
task.setWorkbasketSummary(wb);
LOGGER.debug("exit from newTask(), returning {}", task);
return task;
}
@Override
public TaskComment newTaskComment(String taskId) {
return taskCommentService.newTaskComment(taskId);
}
@Override
public Attachment newAttachment() {
return new AttachmentImpl();
}
@Override
public Task updateTask(Task task)
throws InvalidArgumentException, TaskNotFoundException, ConcurrencyException,
NotAuthorizedException, AttachmentPersistenceException, InvalidStateException,
ClassificationNotFoundException {
String userId = CurrentUserContext.getUserid();
LOGGER.debug("entry to updateTask(task = {}, userId = {})", task, userId);
TaskImpl newTaskImpl = (TaskImpl) task;
TaskImpl oldTaskImpl;
try {
taskanaEngine.openConnection();
oldTaskImpl = (TaskImpl) getTask(newTaskImpl.getId());
newTaskImpl = checkConcurrencyAndSetModified(newTaskImpl, oldTaskImpl);
attachmentHandler.insertAndDeleteAttachmentsOnTaskUpdate(newTaskImpl, oldTaskImpl);
ObjectReference.validate(newTaskImpl.getPrimaryObjRef(), "primary ObjectReference", TASK);
standardUpdateActions(oldTaskImpl, newTaskImpl);
taskMapper.update(newTaskImpl);
LOGGER.debug("Method updateTask() updated task '{}' for user '{}'.", task.getId(), userId);
if (HistoryEventProducer.isHistoryEnabled()) {
String changeDetails = determineChangesInTaskAttributes(oldTaskImpl, newTaskImpl);
LOGGER.warn(changeDetails);
historyEventProducer.createEvent(
new UpdatedEvent(task, CurrentUserContext.getUserid(), changeDetails));
}
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from claim()");
}
return task;
}
@Override
public BulkOperationResults transferTasks(
String destinationWorkbasketId, List taskIds)
throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException {
return taskTransferrer.transferTasks(destinationWorkbasketId, taskIds);
}
@Override
public BulkOperationResults transferTasks(
String destinationWorkbasketKey, String destinationWorkbasketDomain, List taskIds)
throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException {
return taskTransferrer.transferTasks(
destinationWorkbasketKey, destinationWorkbasketDomain, taskIds);
}
@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, NotAuthorizedException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("entry to deleteTasks(tasks = {})", LoggerUtils.listToString(taskIds));
}
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN);
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, null);
Iterator taskIdIterator = taskIds.iterator();
while (taskIdIterator.hasNext()) {
removeSingleTaskForTaskDeletionById(bulkLog, taskSummaries, taskIdIterator);
}
if (!taskIds.isEmpty()) {
taskMapper.deleteMultiple(taskIds);
}
return bulkLog;
} finally {
LOGGER.debug("exit from deleteTasks()");
taskanaEngine.returnConnection();
}
}
@Override
public BulkOperationResults completeTasks(
List taskIdsToBeCompleted) throws InvalidArgumentException {
try {
LOGGER.debug("entry to completeTasks(taskIds = {})", taskIdsToBeCompleted);
taskanaEngine.openConnection();
if (taskIdsToBeCompleted == null || taskIdsToBeCompleted.isEmpty()) {
throw new InvalidArgumentException("TaskIds can´t be used as NULL-Parameter.");
}
BulkOperationResults bulkLog = new BulkOperationResults<>();
List taskIds = new ArrayList<>(taskIdsToBeCompleted);
removeNonExistingTasksFromTaskIdList(taskIds, bulkLog);
List taskSummaries =
this.createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
checkIfTasksMatchCompleteCriteria(taskIds, taskSummaries, bulkLog);
updateTasksToBeCompleted(taskIds, taskSummaries);
return bulkLog;
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from to completeTasks(taskIds = {})", taskIdsToBeCompleted);
}
}
@Override
public List updateTasks(
ObjectReference selectionCriteria, Map customFieldsToUpdate)
throws InvalidArgumentException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to updateTasks(selectionCriteria = {}, customFieldsToUpdate = {})",
selectionCriteria,
customFieldsToUpdate);
}
ObjectReference.validate(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::getId).collect(Collectors.toList());
taskMapper.updateTasks(changedTasks, updated, fieldSelector);
if (LOGGER.isDebugEnabled()) {
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::getId).collect(Collectors.toList());
taskMapper.updateTasks(changedTasks, updatedTask, fieldSelector);
if (LOGGER.isDebugEnabled()) {
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 TaskComment createTaskComment(TaskComment taskComment)
throws NotAuthorizedException, TaskNotFoundException, InvalidArgumentException {
return taskCommentService.createTaskComment(taskComment);
}
@Override
public TaskComment updateTaskComment(TaskComment taskComment)
throws NotAuthorizedException, ConcurrencyException, TaskCommentNotFoundException,
TaskNotFoundException, InvalidArgumentException {
return taskCommentService.updateTaskComment(taskComment);
}
@Override
public void deleteTaskComment(String taskCommentId)
throws NotAuthorizedException, TaskCommentNotFoundException, TaskNotFoundException,
InvalidArgumentException {
taskCommentService.deleteTaskComment(taskCommentId);
}
@Override
public TaskComment getTaskComment(String taskCommentid)
throws TaskCommentNotFoundException, NotAuthorizedException, TaskNotFoundException,
InvalidArgumentException {
return taskCommentService.getTaskComment(taskCommentid);
}
@Override
public List getTaskComments(String taskId)
throws NotAuthorizedException, TaskNotFoundException {
return taskCommentService.getTaskComments(taskId);
}
@Override
public BulkOperationResults setCallbackStateForTasks(
List externalIds, CallbackState state) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to setCallbackStateForTasks(externalIds = {})",
LoggerUtils.listToString(externalIds));
}
try {
taskanaEngine.openConnection();
BulkOperationResults bulkLog = new BulkOperationResults<>();
if (externalIds == null || externalIds.isEmpty()) {
return bulkLog;
}
List taskSummaries = taskMapper.findExistingTasks(null, externalIds);
Iterator taskIdIterator = externalIds.iterator();
while (taskIdIterator.hasNext()) {
removeSingleTaskForCallbackStateByExternalId(bulkLog, taskSummaries, taskIdIterator, state);
}
if (!externalIds.isEmpty()) {
taskMapper.setCallbackStateMultiple(externalIds, state);
}
return bulkLog;
} finally {
LOGGER.debug("exit from setCallbckStateForTasks()");
taskanaEngine.returnConnection();
}
}
@Override
public BulkOperationResults setOwnerOfTasks(
String owner, List argTaskIds) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to setOwnerOfTasks(owner = {}, tasks = {})",
owner,
LoggerUtils.listToString(argTaskIds));
}
BulkOperationResults bulkLog = new BulkOperationResults<>();
if (argTaskIds == null || argTaskIds.isEmpty()) {
return bulkLog;
}
// remove duplicates
List taskIds = argTaskIds.stream().distinct().collect(Collectors.toList());
final int requestSize = taskIds.size();
try {
taskanaEngine.openConnection();
// use only elements we are authorized for
Pair, BulkLog> resultsPair = getMinimalTaskSummaries(taskIds);
// set the Owner of these tasks we are authorized for
List existingMinimalTaskSummaries = resultsPair.getLeft();
taskIds =
existingMinimalTaskSummaries.stream()
.map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toList());
bulkLog.addAllErrors(resultsPair.getRight());
if (taskIds.isEmpty()) {
return bulkLog;
} else {
final int numberOfAffectedTasks = taskMapper.setOwnerOfTasks(owner, taskIds, Instant.now());
if (numberOfAffectedTasks != taskIds.size()) { // all tasks were updated
// check the outcome
existingMinimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(
addExceptionsForTasksWhoseOwnerWasNotSet(owner, existingMinimalTaskSummaries));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Received the Request to set owner on {} tasks, actually modified tasks = {}"
+ ", could not set owner on {} tasks.",
requestSize,
numberOfAffectedTasks,
bulkLog.getFailedIds().size());
}
}
return bulkLog;
}
} finally {
LOGGER.debug("exit from setOwnerOfTasks()");
taskanaEngine.returnConnection();
}
}
@Override
public BulkOperationResults setPlannedPropertyOfTasks(
Instant planned, List argTaskIds) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to setPlannedPropertyOfTasks(planned = {}, tasks = {})",
planned,
LoggerUtils.listToString(argTaskIds));
}
BulkLog bulkLog = new BulkLog();
if (argTaskIds == null || argTaskIds.isEmpty()) {
return bulkLog;
}
try {
taskanaEngine.openConnection();
Pair, BulkLog> resultsPair = getMinimalTaskSummaries(argTaskIds);
List tasksToModify = resultsPair.getLeft();
bulkLog.addAllErrors(resultsPair.getRight());
BulkLog errorsFromProcessing =
serviceLevelHandler.setPlannedPropertyOfTasksImpl(planned, tasksToModify);
bulkLog.addAllErrors(errorsFromProcessing);
return bulkLog;
} finally {
LOGGER.debug("exit from setPlannedPropertyOfTasks");
taskanaEngine.returnConnection();
}
}
@Override
public Task cancelTask(String taskId)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException {
LOGGER.debug("entry to cancelTask(task = {})", taskId);
try {
taskanaEngine.openConnection();
return terminateCancelCommonActions(taskId, TaskState.CANCELLED);
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from cancelTask()");
}
}
@Override
public Task terminateTask(String taskId)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException {
LOGGER.debug("entry to terminateTask(task = {})", taskId);
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN);
try {
taskanaEngine.openConnection();
return terminateCancelCommonActions(taskId, TaskState.TERMINATED);
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from terminateTask()");
}
}
public List findTasksIdsAffectedByClassificationChange(String classificationId) {
LOGGER.debug(
"entry to findTasksIdsAffectedByClassificationChange(classificationId = {})",
classificationId);
// tasks directly affected
List tasksAffectedDirectly =
createTaskQuery()
.classificationIdIn(classificationId)
.stateIn(TaskState.READY, TaskState.CLAIMED)
.list();
// tasks indirectly affected via attachments
List> affectedPairs =
tasksAffectedDirectly.stream()
.map(t -> new Pair(t.getId(), t.getPlanned()))
.collect(Collectors.toList());
// tasks indirectly affected via attachments
List> taskIdsAndPlannedFromAttachments =
attachmentMapper.findTaskIdsAndPlannedAffectedByClassificationChange(classificationId);
List taskIdsFromAttachments =
taskIdsAndPlannedFromAttachments.stream().map(Pair::getLeft).collect(Collectors.toList());
List> filteredTaskIdsAndPlannedFromAttachments =
taskIdsFromAttachments.isEmpty()
? new ArrayList<>()
: taskMapper.filterTaskIdsForReadyAndClaimed(taskIdsFromAttachments);
affectedPairs.addAll(filteredTaskIdsAndPlannedFromAttachments);
// sort all affected tasks according to the planned instant
List affectedTaskIds =
affectedPairs.stream()
.sorted(Comparator.comparing(Pair::getRight))
.distinct()
.map(Pair::getLeft)
.collect(Collectors.toList());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"the following tasks are affected by the update of classification {} : {}",
classificationId,
LoggerUtils.listToString(affectedTaskIds));
}
LOGGER.debug("exit from findTasksIdsAffectedByClassificationChange(). ");
return affectedTaskIds;
}
public void refreshPriorityAndDueDatesOfTasksOnClassificationUpdate(
List taskIds, boolean serviceLevelChanged, boolean priorityChanged) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to refreshPriorityAndDueDateOfTasks(tasks = {})",
LoggerUtils.listToString(taskIds));
}
Pair, BulkLog> resultsPair = getMinimalTaskSummaries(taskIds);
List tasks = resultsPair.getLeft();
try {
taskanaEngine.openConnection();
Set adminAccessIds =
taskanaEngine.getEngine().getConfiguration().getRoleMap().get(TaskanaRole.ADMIN);
if (adminAccessIds.contains(CurrentUserContext.getUserid())) {
serviceLevelHandler.refreshPriorityAndDueDatesOfTasks(
tasks, serviceLevelChanged, priorityChanged);
} else {
taskanaEngine.runAsAdmin(
() -> {
serviceLevelHandler.refreshPriorityAndDueDatesOfTasks(
tasks, serviceLevelChanged, priorityChanged);
return null;
});
}
} finally {
LOGGER.debug("exit from refreshPriorityAndDueDateOfTasks");
taskanaEngine.returnConnection();
}
}
protected String determineChangesInTaskAttributes(Task oldTaskImpl, Task newTaskImpl) {
LOGGER.debug(
"Entry to determineChangesInTaskAttributes (oldTaskImpl = {}, newTaskImpl = {}",
oldTaskImpl,
newTaskImpl);
List fields = new ArrayList<>();
Class> currentClass = oldTaskImpl.getClass();
while (currentClass.getSuperclass() != null) {
fields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}
Predicate> areFieldsNotEqual =
fieldAndValuePairTriplet ->
!Objects.equals(
fieldAndValuePairTriplet.getMiddle(), fieldAndValuePairTriplet.getRight());
Predicate> isFieldNotCustomAttributes =
fieldAndValuePairTriplet ->
!fieldAndValuePairTriplet.getLeft().getName().equals("customAttributes");
List changedAttributes =
fields.stream()
.peek(field -> field.setAccessible(true))
.map(
CheckedFunction.wrap(
field -> new Triplet<>(field, field.get(oldTaskImpl), field.get(newTaskImpl))))
.filter(areFieldsNotEqual.and(isFieldNotCustomAttributes))
.map(
fieldAndValuePairTriplet -> {
JSONObject changedAttribute = new JSONObject();
changedAttribute.put("fieldName", fieldAndValuePairTriplet.getLeft().getName());
changedAttribute.put(
"oldValue",
Optional.ofNullable(fieldAndValuePairTriplet.getMiddle()).orElse(""));
changedAttribute.put(
"newValue",
Optional.ofNullable(fieldAndValuePairTriplet.getRight()).orElse(""));
return changedAttribute;
})
.collect(Collectors.toList());
JSONObject changes = new JSONObject();
changes.put("changes", changedAttributes);
LOGGER.debug("Exit from determineChangesInTaskAttributes()");
return changes.toString();
}
Pair, BulkLog> getMinimalTaskSummaries(List argTaskIds) {
BulkLog bulkLog = new BulkLog();
// remove duplicates
List taskIds = argTaskIds.stream().distinct().collect(Collectors.toList());
// get existing tasks
List minimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(addExceptionsForNonExistingTasksToBulkLog(taskIds, minimalTaskSummaries));
Pair, BulkLog> filteredPair =
filterTasksAuthorizedForAndLogErrorsForNotAuthorized(minimalTaskSummaries);
bulkLog.addAllErrors(filteredPair.getRight());
return new Pair<>(filteredPair.getLeft(), bulkLog);
}
Pair, BulkLog> filterTasksAuthorizedForAndLogErrorsForNotAuthorized(
List existingTasks) {
BulkLog bulkLog = new BulkLog();
// check authorization only for non-admin or task-admin users
if (taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN)) {
return new Pair<>(existingTasks, bulkLog);
} else {
List taskIds =
existingTasks.stream().map(MinimalTaskSummary::getTaskId).collect(Collectors.toList());
List accessIds = CurrentUserContext.getAccessIds();
List taskIdsNotAuthorizedFor =
taskMapper.filterTaskIdsNotAuthorizedFor(taskIds, accessIds);
String userId = CurrentUserContext.getUserid();
for (String taskId : taskIdsNotAuthorizedFor) {
bulkLog.addError(
taskId,
new NotAuthorizedException(
String.format("User %s is not authorized for task %s ", userId, taskId), userId));
}
taskIds.removeAll(taskIdsNotAuthorizedFor);
List tasksAuthorizedFor =
existingTasks.stream()
.filter(t -> taskIds.contains(t.getTaskId()))
.collect(Collectors.toList());
return new Pair<>(tasksAuthorizedFor, bulkLog);
}
}
BulkLog addExceptionsForNonExistingTasksToBulkLog(
List requestTaskIds, List existingMinimalTaskSummaries) {
BulkLog bulkLog = new BulkLog();
List nonExistingTaskIds = new ArrayList<>(requestTaskIds);
List existingTaskIds =
existingMinimalTaskSummaries.stream()
.map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toList());
nonExistingTaskIds.removeAll(existingTaskIds);
nonExistingTaskIds.forEach(
taskId ->
bulkLog.addError(taskId, new TaskNotFoundException(taskId, "Task was not found")));
return bulkLog;
}
void removeNonExistingTasksFromTaskIdList(
List taskIds, BulkOperationResults bulkLog) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to removeNonExistingTasksFromTaskIdList(targetWbId = {}, taskIds = {})",
taskIds,
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();
}
}
LOGGER.debug("exit from removeNonExistingTasksFromTaskIdList()");
}
List augmentTaskSummariesByContainedSummaries(List taskSummaries) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to augmentTaskSummariesByContainedSummaries(taskSummaries= {})",
LoggerUtils.listToString(taskSummaries));
}
List result = new ArrayList<>();
if (taskSummaries == null || taskSummaries.isEmpty()) {
return result;
}
List taskIds =
taskSummaries.stream().map(TaskSummaryImpl::getId).distinct().collect(Collectors.toList());
if (taskIds.isEmpty()) {
taskIds = null;
}
LOGGER.debug(
"augmentTaskSummariesByContainedSummaries() about to query for attachmentSummaries ");
List attachmentSummaries =
attachmentMapper.findAttachmentSummariesByTaskIds(taskIds);
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;
}
private TaskImpl checkConcurrencyAndSetModified(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
throws ConcurrencyException {
// TODO: not safe to rely only on different timestamps.
// With fast execution below 1ms there will be no concurrencyException
if (oldTaskImpl.getModified() != null
&& !oldTaskImpl.getModified().equals(newTaskImpl.getModified())
|| oldTaskImpl.getClaimed() != null
&& !oldTaskImpl.getClaimed().equals(newTaskImpl.getClaimed())
|| oldTaskImpl.getState() != null
&& !oldTaskImpl.getState().equals(newTaskImpl.getState())) {
throw new ConcurrencyException("The task has already been updated by another user");
}
newTaskImpl.setModified(Instant.now());
return newTaskImpl;
}
private TaskImpl terminateCancelCommonActions(String taskId, TaskState targetState)
throws NotAuthorizedException, TaskNotFoundException, InvalidStateException {
if (taskId == null || taskId.isEmpty()) {
throw new TaskNotFoundException(taskId, String.format(TASK_WITH_ID_WAS_NOT_FOUND, taskId));
}
TaskImpl task = (TaskImpl) getTask(taskId);
TaskState state = task.getState();
if (state.isEndState()) {
throw new InvalidStateException(String.format(TASK_WITH_ID_IS_ALREADY_IN_END_STATE, taskId));
}
Instant now = Instant.now();
task.setModified(now);
task.setCompleted(now);
task.setState(targetState);
taskMapper.update(task);
LOGGER.debug("Task '{}' cancelled by user '{}'.", taskId, CurrentUserContext.getUserid());
return task;
}
private BulkOperationResults addExceptionsForTasksWhoseOwnerWasNotSet(
String owner, List existingMinimalTaskSummaries) {
BulkOperationResults bulkLog = new BulkOperationResults<>();
for (MinimalTaskSummary taskSummary : existingMinimalTaskSummaries) {
if (!owner.equals(taskSummary.getOwner())) { // owner was not set
if (!TaskState.READY.equals(taskSummary.getTaskState())) { // due to invalid state
bulkLog.addError(
taskSummary.getTaskId(),
new InvalidStateException(
String.format(
TASK_WITH_ID_IS_NOT_READY,
taskSummary.getTaskId(),
taskSummary.getTaskState())));
} else { // due to unknown reason
bulkLog.addError(
taskSummary.getTaskId(),
new UpdateFailedException(
String.format("Could not set owner of Task %s .", taskSummary.getTaskId())));
}
}
}
return bulkLog;
}
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;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
TaskState state = task.getState();
if (!state.in(TaskState.READY, TaskState.CLAIMED)) {
throw new InvalidStateException(
String.format(TASK_WITH_ID_IS_ALREADY_IN_END_STATE, taskId));
}
if (state == TaskState.CLAIMED && !forceClaim && !task.getOwner().equals(userId)) {
throw new InvalidOwnerException(
String.format(TASK_WITH_ID_IS_ALREADY_CLAIMED_BY, taskId, 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, CurrentUserContext.getUserid()));
}
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from claim()");
}
return task;
}
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;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
TaskState state = task.getState();
if (state.isEndState()) {
throw new InvalidStateException(
String.format(TASK_WITH_ID_IS_ALREADY_IN_END_STATE, taskId));
}
if (state == TaskState.CLAIMED && !forceUnclaim && !userId.equals(task.getOwner())) {
throw new InvalidOwnerException(
String.format(TASK_WITH_ID_IS_ALREADY_CLAIMED_BY, taskId, 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, CurrentUserContext.getUserid()));
}
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from cancelClaim()");
}
return task;
}
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;
try {
taskanaEngine.openConnection();
task = (TaskImpl) this.getTask(taskId);
if (task.getState() == TaskState.COMPLETED) {
return task;
}
if (task.getState().in(TaskState.CANCELLED, TaskState.TERMINATED)) {
throw new InvalidStateException(
String.format(
"Cannot complete task %s because it is in state %s.", taskId, task.getState()));
}
// check pre-conditions for non-forced invocation
if (!isForced) {
if (task.getClaimed() == null || task.getState() != TaskState.CLAIMED) {
throw new InvalidStateException(
String.format(TASK_WITH_ID_HAS_TO_BE_CLAIMED_BEFORE, taskId));
} else if (!CurrentUserContext.getAccessIds().contains(task.getOwner())) {
throw new InvalidOwnerException(
String.format(
"Owner of task %s is %s, but current user is %s ",
taskId, task.getOwner(), 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, CurrentUserContext.getUserid()));
}
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from completeTask()");
}
return task;
}
private void deleteTask(String taskId, boolean forceDelete)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException {
LOGGER.debug("entry to deleteTask(taskId = {} , forceDelete = {})", taskId, forceDelete);
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN);
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
if (!(task.getState().isEndState()) && !forceDelete) {
throw new InvalidStateException(
"Cannot delete Task " + taskId + " because it is not in an end state.");
}
if ((!task.getState().in(TaskState.TERMINATED, TaskState.CANCELLED))
&& CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(task.getCallbackState())) {
throw new InvalidStateException(String.format(TASK_WITH_ID_CALLBACK_NOT_PROCESSED, taskId));
}
taskMapper.delete(taskId);
LOGGER.debug("Task {} deleted.", taskId);
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from deleteTask().");
}
}
private void removeSingleTaskForTaskDeletionById(
BulkOperationResults bulkLog,
List taskSummaries,
Iterator taskIdIterator) {
LOGGER.debug("entry to removeSingleTask()");
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(taskSummary -> currentTaskId.equals(taskSummary.getTaskId()))
.findFirst()
.orElse(null);
if (foundSummary == null) {
bulkLog.addError(
currentTaskId,
new TaskNotFoundException(
currentTaskId, String.format(TASK_WITH_ID_WAS_NOT_FOUND, currentTaskId)));
taskIdIterator.remove();
} else if (!(foundSummary.getTaskState().isEndState())) {
bulkLog.addError(currentTaskId, new InvalidStateException(currentTaskId));
taskIdIterator.remove();
} else {
if ((!foundSummary.getTaskState().in(TaskState.CANCELLED, TaskState.TERMINATED))
&& CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(foundSummary.getCallbackState())) {
bulkLog.addError(
currentTaskId,
new InvalidStateException(
String.format(TASK_WITH_ID_CALLBACK_NOT_PROCESSED, currentTaskId)));
taskIdIterator.remove();
}
}
}
LOGGER.debug("exit from removeSingleTask()");
}
private void removeSingleTaskForCallbackStateByExternalId(
BulkOperationResults bulkLog,
List taskSummaries,
Iterator externalIdIterator,
CallbackState desiredCallbackState) {
LOGGER.debug("entry to removeSingleTask()");
String currentExternalId = externalIdIterator.next();
if (currentExternalId == null || currentExternalId.equals("")) {
bulkLog.addError(
"", new InvalidArgumentException(IDS_WITH_EMPTY_OR_NULL_VALUE_ARE_NOT_ALLOWED));
externalIdIterator.remove();
} else {
MinimalTaskSummary foundSummary =
taskSummaries.stream()
.filter(taskSummary -> currentExternalId.equals(taskSummary.getExternalId()))
.findFirst()
.orElse(null);
if (foundSummary == null) {
bulkLog.addError(
currentExternalId,
new TaskNotFoundException(
currentExternalId, String.format(TASK_WITH_ID_WAS_NOT_FOUND, currentExternalId)));
externalIdIterator.remove();
} else if (!desiredCallbackStateCanBeSetForFoundSummary(foundSummary, desiredCallbackState)) {
bulkLog.addError(currentExternalId, new InvalidStateException(currentExternalId));
externalIdIterator.remove();
}
}
LOGGER.debug("exit from removeSingleTask()");
}
private boolean desiredCallbackStateCanBeSetForFoundSummary(
MinimalTaskSummary foundSummary, CallbackState desiredCallbackState) {
CallbackState currentTaskCallbackState = foundSummary.getCallbackState();
TaskState currentTaskState = foundSummary.getTaskState();
switch (desiredCallbackState) {
case CALLBACK_PROCESSING_COMPLETED:
return currentTaskState.isEndState();
case CLAIMED:
if (!currentTaskState.equals(TaskState.CLAIMED)) {
return false;
} else {
return currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_REQUIRED);
}
case CALLBACK_PROCESSING_REQUIRED:
return !currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_COMPLETED);
default:
return false;
}
}
private void standardSettings(TaskImpl task, Classification classification)
throws InvalidArgumentException {
TaskImpl task1 = task;
LOGGER.debug("entry to standardSettings()");
final Instant now = Instant.now();
task1.setId(IdGenerator.generateWithPrefix(ID_PREFIX_TASK));
if (task1.getExternalId() == null) {
task1.setExternalId(IdGenerator.generateWithPrefix(ID_PREFIX_EXT_TASK_ID));
}
task1.setState(TaskState.READY);
task1.setCreated(now);
task1.setModified(now);
task1.setRead(false);
task1.setTransferred(false);
String creator = CurrentUserContext.getUserid();
if (taskanaEngine.getEngine().getConfiguration().isSecurityEnabled() && creator == null) {
throw new SystemException(
"TaskanaSecurity is enabled, but the current UserId is NULL while creating a Task.");
}
task1.setCreator(creator);
// if no business process id is provided, a unique id is created.
if (task1.getBusinessProcessId() == null) {
task1.setBusinessProcessId(IdGenerator.generateWithPrefix(ID_PREFIX_BUSINESS_PROCESS));
}
// null in case of manual tasks
if (task1.getPlanned() == null && (classification == null || task1.getDue() == null)) {
task1.setPlanned(now);
}
if (classification != null) {
task1 = serviceLevelHandler.updatePrioPlannedDueOfTask(task1, null, false);
}
if (task1.getName() == null && classification != null) {
task1.setName(classification.getName());
}
if (task1.getDescription() == null && classification != null) {
task1.setDescription(classification.getDescription());
}
try {
attachmentHandler.insertNewAttachmentsOnTaskCreation(task);
} catch (AttachmentPersistenceException e) {
throw new SystemException(
"Internal error when trying to insert new Attachments on Task Creation.", e);
}
LOGGER.debug("exit from standardSettings()");
}
private void setCallbackStateOnTaskCreation(TaskImpl task) throws InvalidArgumentException {
Map callbackInfo = task.getCallbackInfo();
if (callbackInfo != null && callbackInfo.containsKey(Task.CALLBACK_STATE)) {
String value = callbackInfo.get(Task.CALLBACK_STATE);
if (value != null && !value.isEmpty()) {
try {
CallbackState state = CallbackState.valueOf(value);
task.setCallbackState(state);
} catch (Exception e) {
LOGGER.warn(
"Attempted to determine callback state from {} and caught exception", value, e);
throw new InvalidArgumentException(
String.format("Attempted to set callback state for task %s.", task.getId()), e);
}
}
}
}
private void checkIfTasksMatchCompleteCriteria(
List taskIds,
List taskSummaries,
BulkOperationResults bulkLog) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to checkIfTasksMatchCompleteCriteria(taskIds = {}, "
+ "taskSummaries = {}, bulkLog = {})",
LoggerUtils.listToString(taskIds),
LoggerUtils.listToString(taskSummaries),
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.getId()))
.findFirst()
.orElse(null);
if (taskSummary == null) {
bulkLog.addError(
currentTaskId,
new TaskNotFoundException(
currentTaskId, String.format(TASK_WITH_ID_WAS_NOT_FOUND, currentTaskId)));
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(
String.format(
"TaskOwner is %s, but currentUser is %s.",
taskSummary.getOwner(), CurrentUserContext.getUserid())));
taskIdIterator.remove();
} else {
taskSummary.setCompleted(now);
taskSummary.setModified(now);
taskSummary.setState(TaskState.COMPLETED);
}
}
LOGGER.debug("exit from checkIfTasksMatchCompleteCriteria()");
}
private void updateTasksToBeCompleted(List taskIds, List taskSummaries) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to updateTasksToBeCompleted(taskIds = {}, taskSummaries = {})",
LoggerUtils.listToString(taskIds),
LoggerUtils.listToString(taskSummaries));
}
if (!taskIds.isEmpty() && !taskSummaries.isEmpty()) {
taskMapper.updateCompleted(taskIds, (TaskSummaryImpl) taskSummaries.get(0));
if (HistoryEventProducer.isHistoryEnabled()) {
createTasksCompletedEvents(taskSummaries);
}
}
LOGGER.debug("exit from updateTasksToBeCompleted()");
}
private void addClassificationSummariesToTaskSummaries(
List tasks, List classifications) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to addClassificationSummariesToTaskSummaries(tasks = {}, classifications = {})",
LoggerUtils.listToString(tasks),
LoggerUtils.listToString(classifications));
}
if (tasks == null || tasks.isEmpty()) {
LOGGER.debug("exit from addClassificationSummariesToTaskSummaries()");
return;
}
// assign query results to appropriate tasks.
for (TaskSummaryImpl task : tasks) {
String classificationId = task.getClassificationSummary().getId();
ClassificationSummary classificationSummary =
classifications.stream()
.filter(c -> c.getId().equals(classificationId))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
throw new SystemException(
"Did not find a Classification for task (Id="
+ task.getId()
+ ",classification="
+ task.getClassificationSummary().getId()
+ ")");
}
// set the classification on the task object
task.setClassificationSummary(classificationSummary);
}
LOGGER.debug("exit from addClassificationSummariesToTaskSummaries()");
}
private List findClassificationsForTasksAndAttachments(
List taskSummaries, List attachmentSummaries) {
LOGGER.debug("entry to findClassificationsForTasksAndAttachments()");
if (taskSummaries == null || taskSummaries.isEmpty()) {
return new ArrayList<>();
}
Set classificationIdSet =
taskSummaries.stream()
.map(t -> t.getClassificationSummary().getId())
.collect(Collectors.toSet());
if (attachmentSummaries != null && !attachmentSummaries.isEmpty()) {
for (AttachmentSummaryImpl att : attachmentSummaries) {
classificationIdSet.add(att.getClassificationSummary().getId());
}
}
LOGGER.debug("exit from findClassificationsForTasksAndAttachments()");
return queryClassificationsForTasksAndAttachments(classificationIdSet);
}
private List findClassificationForTaskImplAndAttachments(
TaskImpl task, List attachmentImpls) {
LOGGER.debug("entry to transferBulk()");
Set classificationIdSet =
new HashSet<>(Collections.singletonList(task.getClassificationSummary().getId()));
if (attachmentImpls != null && !attachmentImpls.isEmpty()) {
for (AttachmentImpl att : attachmentImpls) {
classificationIdSet.add(att.getClassificationSummary().getId());
}
}
LOGGER.debug("exit from findClassificationForTaskImplAndAttachments()");
return queryClassificationsForTasksAndAttachments(classificationIdSet);
}
private List queryClassificationsForTasksAndAttachments(
Set classificationIdSet) {
String[] classificationIdArray = classificationIdSet.toArray(new String[0]);
LOGGER.debug(
"getClassificationsForTasksAndAttachments() about to query classifications and exit");
// perform classification query
return this.classificationService
.createClassificationQuery()
.idIn(classificationIdArray)
.list();
}
private void addWorkbasketSummariesToTaskSummaries(List taskSummaries) {
LOGGER.debug("entry to addWorkbasketSummariesToTaskSummaries()");
if (taskSummaries == null || taskSummaries.isEmpty()) {
return;
}
// calculate parameters for workbasket query: workbasket keys
String[] workbasketIdArray =
taskSummaries.stream()
.map(t -> t.getWorkbasketSummary().getId())
.distinct()
.toArray(String[]::new);
LOGGER.debug("addWorkbasketSummariesToTaskSummaries() about to query workbaskets");
WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery();
query.setUsedToAugmentTasks(true);
List workbaskets = query.idIn(workbasketIdArray).list();
Iterator taskIterator = taskSummaries.iterator();
while (taskIterator.hasNext()) {
TaskSummaryImpl task = taskIterator.next();
String workbasketId = task.getWorkbasketSummaryImpl().getId();
WorkbasketSummary workbasketSummary =
workbaskets.stream()
.filter(x -> workbasketId != null && workbasketId.equals(x.getId()))
.findFirst()
.orElse(null);
if (workbasketSummary == null) {
LOGGER.warn("Could not find a Workbasket for task {}.", task.getId());
taskIterator.remove();
continue;
}
task.setWorkbasketSummary(workbasketSummary);
}
LOGGER.debug("exit from addWorkbasketSummariesToTaskSummaries()");
}
private void addAttachmentSummariesToTaskSummaries(
List taskSummaries,
List attachmentSummaries,
List classifications) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to addAttachmentSummariesToTaskSummaries(taskSummaries = {}, "
+ "attachmentSummaries = {}, classifications = {})",
LoggerUtils.listToString(taskSummaries),
LoggerUtils.listToString(attachmentSummaries),
LoggerUtils.listToString(classifications));
}
if (taskSummaries == null || taskSummaries.isEmpty()) {
return;
}
// augment attachment summaries by classification summaries
// Note:
// the mapper sets for each Attachment summary the property classificationSummary.key from the
// CLASSIFICATION_KEY property in the DB
addClassificationSummariesToAttachmentSummaries(
attachmentSummaries, taskSummaries, classifications);
// assign attachment summaries to task summaries
for (TaskSummaryImpl task : taskSummaries) {
for (AttachmentSummaryImpl attachment : attachmentSummaries) {
if (attachment.getTaskId() != null && attachment.getTaskId().equals(task.getId())) {
task.addAttachmentSummary(attachment);
}
}
}
LOGGER.debug("exit from addAttachmentSummariesToTaskSummaries()");
}
private void addClassificationSummariesToAttachmentSummaries(
List attachmentSummaries,
List taskSummaries,
List classifications) {
LOGGER.debug("entry to addClassificationSummariesToAttachmentSummaries()");
// prereq: in each attachmentSummary, the classificationSummary.key property is set.
if (attachmentSummaries == null
|| attachmentSummaries.isEmpty()
|| taskSummaries == null
|| taskSummaries.isEmpty()) {
LOGGER.debug("exit from addClassificationSummariesToAttachmentSummaries()");
return;
}
// iterate over all attachment summaries an add the appropriate classification summary to each
for (AttachmentSummaryImpl att : attachmentSummaries) {
String classificationId = att.getClassificationSummary().getId();
ClassificationSummary classificationSummary =
classifications.stream()
.filter(x -> classificationId != null && classificationId.equals(x.getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + att);
}
att.setClassificationSummary(classificationSummary);
}
LOGGER.debug("exit from addClassificationSummariesToAttachmentSummaries()");
}
private List addClassificationSummariesToAttachments(
List attachmentImpls, List classifications) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to addClassificationSummariesToAttachments(targetWbId = {}, taskIds = {})",
LoggerUtils.listToString(attachmentImpls),
LoggerUtils.listToString(classifications));
}
if (attachmentImpls == null || attachmentImpls.isEmpty()) {
LOGGER.debug("exit from addClassificationSummariesToAttachments()");
return new ArrayList<>();
}
List result = new ArrayList<>();
for (AttachmentImpl att : attachmentImpls) {
// find the associated task to use the correct domain
ClassificationSummary classificationSummary =
classifications.stream()
.filter(c -> c != null && c.getId().equals(att.getClassificationSummary().getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + att);
}
att.setClassificationSummary(classificationSummary);
result.add(att);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("exit from addClassificationSummariesToAttachments(), returning {}", result);
}
return result;
}
private TaskImpl initUpdatedTask(
Map customFieldsToUpdate, CustomPropertySelector fieldSelector)
throws InvalidArgumentException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to initUpdatedTask(customFieldsToUpdate = {}, fieldSelector = {})",
LoggerUtils.mapToString(customFieldsToUpdate),
fieldSelector);
}
TaskImpl newTask = new TaskImpl();
newTask.setModified(Instant.now());
for (Map.Entry entry : customFieldsToUpdate.entrySet()) {
String key = entry.getKey();
fieldSelector.setCustomProperty(key, true);
newTask.setCustomAttribute(key, entry.getValue());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("exit from initUpdatedTask(), returning {}", newTask);
}
return newTask;
}
private void validateCustomFields(Map customFieldsToUpdate)
throws InvalidArgumentException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to validateCustomFields(customFieldsToUpdate = {})",
LoggerUtils.mapToString(customFieldsToUpdate));
}
if (customFieldsToUpdate == null || customFieldsToUpdate.isEmpty()) {
throw new InvalidArgumentException(
"The customFieldsToUpdate argument to updateTasks must not be empty.");
}
for (Map.Entry entry : customFieldsToUpdate.entrySet()) {
String key = entry.getKey();
if (!ALLOWED_KEYS.contains(key)) {
throw new InvalidArgumentException(
"The customFieldsToUpdate argument to updateTasks contains invalid key " + key);
}
}
LOGGER.debug("exit from validateCustomFields()");
}
private List getTasksToChange(List taskIds) {
return createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
}
private List getTasksToChange(ObjectReference selectionCriteria) {
return createTaskQuery()
.primaryObjectReferenceCompanyIn(selectionCriteria.getCompany())
.primaryObjectReferenceSystemIn(selectionCriteria.getSystem())
.primaryObjectReferenceSystemInstanceIn(selectionCriteria.getSystemInstance())
.primaryObjectReferenceTypeIn(selectionCriteria.getType())
.primaryObjectReferenceValueIn(selectionCriteria.getValue())
.list();
}
private void standardUpdateActions(TaskImpl oldTaskImpl, TaskImpl newTaskImpl)
throws InvalidArgumentException, InvalidStateException, ClassificationNotFoundException {
if (oldTaskImpl.getExternalId() == null
|| !(oldTaskImpl.getExternalId().equals(newTaskImpl.getExternalId()))) {
throw new InvalidArgumentException(
"A task's external Id cannot be changed via update of the task");
}
String newWorkbasketKey = newTaskImpl.getWorkbasketKey();
if (newWorkbasketKey != null && !newWorkbasketKey.equals(oldTaskImpl.getWorkbasketKey())) {
throw new InvalidArgumentException(
"A task's Workbasket cannot be changed via update of the task");
}
if (newTaskImpl.getClassificationSummary() == null) {
newTaskImpl.setClassificationSummary(oldTaskImpl.getClassificationSummary());
}
updateClassificationSummary(newTaskImpl, oldTaskImpl);
TaskImpl newTaskImpl1 =
serviceLevelHandler.updatePrioPlannedDueOfTask(newTaskImpl, oldTaskImpl, false);
// if no business process id is provided, use the id of the old task.
if (newTaskImpl1.getBusinessProcessId() == null) {
newTaskImpl1.setBusinessProcessId(oldTaskImpl.getBusinessProcessId());
}
// owner can only be changed if task is in state ready
boolean isOwnerChanged = !Objects.equals(newTaskImpl1.getOwner(), oldTaskImpl.getOwner());
if (isOwnerChanged && oldTaskImpl.getState() != TaskState.READY) {
throw new InvalidStateException(
String.format(TASK_WITH_ID_IS_NOT_READY, oldTaskImpl.getId(), oldTaskImpl.getState()));
}
}
private void updateClassificationSummary(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
throws ClassificationNotFoundException {
ClassificationSummary oldClassificationSummary = oldTaskImpl.getClassificationSummary();
ClassificationSummary newClassificationSummary = newTaskImpl.getClassificationSummary();
if (newClassificationSummary == null) {
newClassificationSummary = oldClassificationSummary;
}
if (!oldClassificationSummary.getKey().equals(newClassificationSummary.getKey())) {
Classification newClassification =
this.classificationService.getClassification(
newClassificationSummary.getKey(), newTaskImpl.getWorkbasketSummary().getDomain());
newClassificationSummary = newClassification.asSummary();
newTaskImpl.setClassificationSummary(newClassificationSummary);
}
}
private void createTasksCompletedEvents(List taskSummaries) {
taskSummaries.forEach(
task ->
historyEventProducer.createEvent(
new CompletedEvent(task, CurrentUserContext.getUserid())));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy