taskana/TaskServiceImpl.java

2145 lines
82 KiB
Java

package pro.taskana.task.internal;
import static java.util.function.Predicate.not;
import static pro.taskana.common.internal.util.CheckedFunction.wrap;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
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.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.ibatis.exceptions.PersistenceException;
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.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.InternalTaskanaEngine;
import pro.taskana.common.internal.util.CheckedConsumer;
import pro.taskana.common.internal.util.CollectionUtil;
import pro.taskana.common.internal.util.EnumUtil;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.common.internal.util.ObjectAttributeChangeDetector;
import pro.taskana.common.internal.util.Pair;
import pro.taskana.spi.history.api.events.task.TaskCancelledEvent;
import pro.taskana.spi.history.api.events.task.TaskClaimCancelledEvent;
import pro.taskana.spi.history.api.events.task.TaskClaimedEvent;
import pro.taskana.spi.history.api.events.task.TaskCompletedEvent;
import pro.taskana.spi.history.api.events.task.TaskCreatedEvent;
import pro.taskana.spi.history.api.events.task.TaskRequestChangesEvent;
import pro.taskana.spi.history.api.events.task.TaskRequestReviewEvent;
import pro.taskana.spi.history.api.events.task.TaskTerminatedEvent;
import pro.taskana.spi.history.api.events.task.TaskUpdatedEvent;
import pro.taskana.spi.history.internal.HistoryEventManager;
import pro.taskana.spi.priority.internal.PriorityServiceManager;
import pro.taskana.spi.task.internal.AfterRequestChangesManager;
import pro.taskana.spi.task.internal.AfterRequestReviewManager;
import pro.taskana.spi.task.internal.BeforeRequestChangesManager;
import pro.taskana.spi.task.internal.BeforeRequestReviewManager;
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
import pro.taskana.spi.task.internal.ReviewRequiredManager;
import pro.taskana.task.api.CallbackState;
import pro.taskana.task.api.TaskCommentQuery;
import pro.taskana.task.api.TaskCustomField;
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.InvalidCallbackStateException;
import pro.taskana.task.api.exceptions.InvalidOwnerException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.NotAuthorizedOnTaskCommentException;
import pro.taskana.task.api.exceptions.ObjectReferencePersistenceException;
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.models.Attachment;
import pro.taskana.task.api.models.AttachmentSummary;
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.ObjectReferenceImpl;
import pro.taskana.task.internal.models.TaskImpl;
import pro.taskana.task.internal.models.TaskSummaryImpl;
import pro.taskana.user.api.models.User;
import pro.taskana.user.internal.UserMapper;
import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.NotAuthorizedOnWorkbasketException;
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 {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskServiceImpl.class);
private final InternalTaskanaEngine taskanaEngine;
private final WorkbasketService workbasketService;
private final ClassificationService classificationService;
private final TaskMapper taskMapper;
private final TaskTransferrer taskTransferrer;
private final TaskCommentServiceImpl taskCommentService;
private final ServiceLevelHandler serviceLevelHandler;
private final AttachmentHandler attachmentHandler;
private final AttachmentMapper attachmentMapper;
private final ObjectReferenceMapper objectReferenceMapper;
private final ObjectReferenceHandler objectReferenceHandler;
private final UserMapper userMapper;
private final HistoryEventManager historyEventManager;
private final CreateTaskPreprocessorManager createTaskPreprocessorManager;
private final PriorityServiceManager priorityServiceManager;
private final ReviewRequiredManager reviewRequiredManager;
private final BeforeRequestReviewManager beforeRequestReviewManager;
private final AfterRequestReviewManager afterRequestReviewManager;
private final BeforeRequestChangesManager beforeRequestChangesManager;
private final AfterRequestChangesManager afterRequestChangesManager;
public TaskServiceImpl(
InternalTaskanaEngine taskanaEngine,
TaskMapper taskMapper,
TaskCommentMapper taskCommentMapper,
AttachmentMapper attachmentMapper,
ObjectReferenceMapper objectReferenceMapper,
UserMapper userMapper) {
this.taskanaEngine = taskanaEngine;
this.taskMapper = taskMapper;
this.workbasketService = taskanaEngine.getEngine().getWorkbasketService();
this.attachmentMapper = attachmentMapper;
this.objectReferenceMapper = objectReferenceMapper;
this.userMapper = userMapper;
this.classificationService = taskanaEngine.getEngine().getClassificationService();
this.historyEventManager = taskanaEngine.getHistoryEventManager();
this.createTaskPreprocessorManager = taskanaEngine.getCreateTaskPreprocessorManager();
this.priorityServiceManager = taskanaEngine.getPriorityServiceManager();
this.reviewRequiredManager = taskanaEngine.getReviewRequiredManager();
this.beforeRequestReviewManager = taskanaEngine.getBeforeRequestReviewManager();
this.afterRequestReviewManager = taskanaEngine.getAfterRequestReviewManager();
this.beforeRequestChangesManager = taskanaEngine.getBeforeRequestChangesManager();
this.afterRequestChangesManager = taskanaEngine.getAfterRequestChangesManager();
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
this.taskCommentService =
new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, userMapper, this);
this.serviceLevelHandler =
new ServiceLevelHandler(taskanaEngine, taskMapper, attachmentMapper, this);
this.attachmentHandler = new AttachmentHandler(attachmentMapper, classificationService);
this.objectReferenceHandler = new ObjectReferenceHandler(objectReferenceMapper);
}
@Override
public Task claim(String taskId)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return claim(taskId, false);
}
@Override
public Task forceClaim(String taskId)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return claim(taskId, true);
}
@Override
public Task cancelClaim(String taskId)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return this.cancelClaim(taskId, false);
}
@Override
public Task forceCancelClaim(String taskId)
throws TaskNotFoundException,
InvalidTaskStateException,
InvalidOwnerException
try {
return this.cancelClaim(taskId, true);
} catch (NotAuthorizedOnWorkbasketException e) {
throw new SystemException(
"this should not have happened. You've discovered a new bug! :D", e);
}
}
@Override
public Task requestReview(String taskId)
throws InvalidTaskStateException,
TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException {
return requestReview(taskId, false);
}
@Override
public Task forceRequestReview(String taskId)
throws InvalidTaskStateException,
TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException {
return requestReview(taskId, true);
}
@Override
public Task requestChanges(String taskId)
throws InvalidTaskStateException,
TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException {
return requestChanges(taskId, false);
}
@Override
public Task forceRequestChanges(String taskId)
throws InvalidTaskStateException,
TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException {
return requestChanges(taskId, true);
}
@Override
public Task completeTask(String taskId)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return completeTask(taskId, false);
}
@Override
public Task forceCompleteTask(String taskId)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return completeTask(taskId, true);
}
@Override
public Task createTask(Task taskToCreate)
throws WorkbasketNotFoundException,
ClassificationNotFoundException,
TaskAlreadyExistException,
InvalidArgumentException,
AttachmentPersistenceException,
ObjectReferencePersistenceException,
NotAuthorizedOnWorkbasketException {
if (createTaskPreprocessorManager.isEnabled()) {
taskToCreate = createTaskPreprocessorManager.processTaskBeforeCreation(taskToCreate);
}
TaskImpl task = (TaskImpl) taskToCreate;
try {
taskanaEngine.openConnection();
if (task.getId() != null && !task.getId().isEmpty()) {
throw new InvalidArgumentException("taskId must be empty when creating a task");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task {} cannot be found, so it can be created.", task.getId());
}
Workbasket workbasket;
if (task.getWorkbasketSummary() != null && 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);
} else {
throw new InvalidArgumentException("Cannot create a Task outside a Workbasket");
}
}
if (workbasket.isMarkedForDeletion()) {
throw new WorkbasketNotFoundException(workbasket.getId());
}
task.setWorkbasketSummary(workbasket.asSummary());
task.setDomain(workbasket.getDomain());
if (!taskanaEngine.getEngine().isUserInRole(TaskanaRole.TASK_ROUTER)) {
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());
ObjectReferenceImpl.validate(task.getPrimaryObjRef(), "primary ObjectReference", "Task");
standardSettingsOnTaskCreation(task, classification);
setCallbackStateOnTaskCreation(task);
priorityServiceManager.calculatePriorityOfTask(task).ifPresent(task::setPriority);
try {
this.taskMapper.insert(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Method createTask() created Task '{}'.", task.getId());
}
if (historyEventManager.isEnabled()) {
String details =
ObjectAttributeChangeDetector.determineChangesInAttributes(newTask(), task);
historyEventManager.createEvent(
new TaskCreatedEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
taskanaEngine.getEngine().getCurrentUserContext().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("violated")
|| msg.contains("verletzt"))
&& msg.contains("external_id")) {
throw new TaskAlreadyExistException(task.getExternalId());
} else {
throw e;
}
}
return task;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public Task getTask(String id) throws NotAuthorizedOnWorkbasketException, TaskNotFoundException {
try {
taskanaEngine.openConnection();
TaskImpl resultTask = taskMapper.findById(id);
if (resultTask != null) {
WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery();
query.setUsedToAugmentTasks(true);
String workbasketId = resultTask.getWorkbasketSummary().getId();
List<WorkbasketSummary> workbaskets = query.idIn(workbasketId).list();
if (workbaskets.isEmpty()) {
throw new NotAuthorizedOnWorkbasketException(
taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
workbasketId,
WorkbasketPermission.READ);
} else {
resultTask.setWorkbasketSummary(workbaskets.get(0));
}
List<AttachmentImpl> attachmentImpls =
attachmentMapper.findAttachmentsByTaskId(resultTask.getId());
if (attachmentImpls == null) {
attachmentImpls = new ArrayList<>();
}
List<ObjectReferenceImpl> secondaryObjectReferences =
objectReferenceMapper.findObjectReferencesByTaskId(resultTask.getId());
if (secondaryObjectReferences == null) {
secondaryObjectReferences = new ArrayList<>();
}
Map<String, ClassificationSummary> classificationSummariesById =
findClassificationForTaskImplAndAttachments(resultTask, attachmentImpls);
addClassificationSummariesToAttachments(attachmentImpls, classificationSummariesById);
resultTask.setAttachments(new ArrayList<>(attachmentImpls));
resultTask.setSecondaryObjectReferences(new ArrayList<>(secondaryObjectReferences));
String classificationId = resultTask.getClassificationSummary().getId();
ClassificationSummary classification = classificationSummariesById.get(classificationId);
if (classification == null) {
throw new SystemException(
"Could not find a Classification for task " + resultTask.getId());
}
resultTask.setClassificationSummary(classification);
if (resultTask.getOwner() != null
&& !resultTask.getOwner().isEmpty()
&& taskanaEngine.getEngine().getConfiguration().isAddAdditionalUserInfo()) {
User owner = userMapper.findById(resultTask.getOwner());
if (owner != null) {
resultTask.setOwnerLongName(owner.getLongName());
}
}
return resultTask;
} else {
throw new TaskNotFoundException(id);
}
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public Task transfer(String taskId, String destinationWorkbasketId, boolean setTransferFlag)
throws TaskNotFoundException,
WorkbasketNotFoundException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return taskTransferrer.transfer(taskId, destinationWorkbasketId, setTransferFlag);
}
@Override
public Task transfer(String taskId, String workbasketKey, String domain, boolean setTransferFlag)
throws TaskNotFoundException,
WorkbasketNotFoundException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
return taskTransferrer.transfer(taskId, workbasketKey, domain, setTransferFlag);
}
@Override
public Task setTaskRead(String taskId, boolean isRead)
throws TaskNotFoundException, NotAuthorizedOnWorkbasketException {
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
task.setRead(isRead);
task.setModified(Instant.now());
taskMapper.update(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Method setTaskRead() set read property of Task '{}' to {} ", task, isRead);
}
return task;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public TaskQuery createTaskQuery() {
return new TaskQueryImpl(taskanaEngine);
}
@Override
public TaskCommentQuery createTaskCommentQuery() {
return new TaskCommentQueryImpl(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) {
TaskImpl task = new TaskImpl();
WorkbasketSummaryImpl wb = new WorkbasketSummaryImpl();
wb.setKey(workbasketKey);
wb.setDomain(domain);
task.setWorkbasketSummary(wb);
return task;
}
@Override
public TaskComment newTaskComment(String taskId) {
return taskCommentService.newTaskComment(taskId);
}
@Override
public Attachment newAttachment() {
return new AttachmentImpl();
}
@Override
public ObjectReference newObjectReference() {
return new ObjectReferenceImpl();
}
@Override
public ObjectReference newObjectReference(
String company, String system, String systemInstance, String type, String value) {
return new ObjectReferenceImpl(company, system, systemInstance, type, value);
}
@Override
public Task updateTask(Task task)
throws InvalidArgumentException,
TaskNotFoundException,
ConcurrencyException,
AttachmentPersistenceException,
ObjectReferencePersistenceException,
ClassificationNotFoundException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
TaskImpl newTaskImpl = (TaskImpl) task;
try {
taskanaEngine.openConnection();
TaskImpl oldTaskImpl = (TaskImpl) getTask(newTaskImpl.getId());
checkConcurrencyAndSetModified(newTaskImpl, oldTaskImpl);
attachmentHandler.insertAndDeleteAttachmentsOnTaskUpdate(newTaskImpl, oldTaskImpl);
objectReferenceHandler.insertAndDeleteObjectReferencesOnTaskUpdate(newTaskImpl, oldTaskImpl);
ObjectReferenceImpl.validate(
newTaskImpl.getPrimaryObjRef(), "primary ObjectReference", "Task");
standardUpdateActions(oldTaskImpl, newTaskImpl);
priorityServiceManager
.calculatePriorityOfTask(newTaskImpl)
.ifPresent(newTaskImpl::setPriority);
taskMapper.update(newTaskImpl);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Method updateTask() updated task '{}' for user '{}'.", task.getId(), userId);
}
if (historyEventManager.isEnabled()) {
String changeDetails =
ObjectAttributeChangeDetector.determineChangesInAttributes(oldTaskImpl, newTaskImpl);
historyEventManager.createEvent(
new TaskUpdatedEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
userId,
changeDetails));
}
} finally {
taskanaEngine.returnConnection();
}
return task;
}
@Override
public BulkOperationResults<String, TaskanaException> transferTasks(
String destinationWorkbasketId, List<String> taskIds, boolean setTransferFlag)
throws InvalidArgumentException,
WorkbasketNotFoundException,
NotAuthorizedOnWorkbasketException {
return taskTransferrer.transfer(taskIds, destinationWorkbasketId, setTransferFlag);
}
@Override
public BulkOperationResults<String, TaskanaException> transferTasks(
String destinationWorkbasketKey,
String destinationWorkbasketDomain,
List<String> taskIds,
boolean setTransferFlag)
throws InvalidArgumentException,
WorkbasketNotFoundException,
NotAuthorizedOnWorkbasketException {
return taskTransferrer.transfer(
taskIds, destinationWorkbasketKey, destinationWorkbasketDomain, setTransferFlag);
}
@Override
public void deleteTask(String taskId)
throws TaskNotFoundException,
NotAuthorizedException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException,
InvalidCallbackStateException {
deleteTask(taskId, false);
}
@Override
public void forceDeleteTask(String taskId)
throws TaskNotFoundException,
NotAuthorizedException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException,
InvalidCallbackStateException {
deleteTask(taskId, true);
}
@Override
public Optional<Task> selectAndClaim(TaskQuery taskQuery) {
((TaskQueryImpl) taskQuery).selectAndClaimEquals(true);
return taskanaEngine.executeInDatabaseConnection(
() ->
Optional.ofNullable(taskQuery.single()).map(TaskSummary::getId).map(wrap(this::claim)));
}
@Override
public BulkOperationResults<String, TaskanaException> deleteTasks(List<String> taskIds)
throws InvalidArgumentException, NotAuthorizedException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN);
try {
taskanaEngine.openConnection();
if (taskIds == null) {
throw new InvalidArgumentException("List of TaskIds must not be null.");
}
taskIds = new ArrayList<>(taskIds);
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
if (taskIds.isEmpty()) {
return bulkLog;
}
List<MinimalTaskSummary> taskSummaries = taskMapper.findExistingTasks(taskIds, null);
Iterator<String> taskIdIterator = taskIds.iterator();
while (taskIdIterator.hasNext()) {
removeSingleTaskForTaskDeletionById(bulkLog, taskSummaries, taskIdIterator);
}
if (!taskIds.isEmpty()) {
attachmentMapper.deleteMultipleByTaskIds(taskIds);
objectReferenceMapper.deleteMultipleByTaskIds(taskIds);
taskMapper.deleteMultiple(taskIds);
if (taskanaEngine.getEngine().isHistoryEnabled()
&& taskanaEngine
.getEngine()
.getConfiguration()
.isDeleteHistoryEventsOnTaskDeletionEnabled()) {
historyEventManager.deleteEvents(taskIds);
}
}
return bulkLog;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public BulkOperationResults<String, TaskanaException> completeTasks(List<String> taskIds)
throws InvalidArgumentException {
return completeTasks(taskIds, false);
}
@Override
public BulkOperationResults<String, TaskanaException> forceCompleteTasks(List<String> taskIds)
throws InvalidArgumentException {
return completeTasks(taskIds, true);
}
@Override
public List<String> updateTasks(
ObjectReference selectionCriteria, Map<TaskCustomField, String> customFieldsToUpdate)
throws InvalidArgumentException {
ObjectReferenceImpl.validate(selectionCriteria, "ObjectReference", "updateTasks call");
validateCustomFields(customFieldsToUpdate);
TaskCustomPropertySelector fieldSelector = new TaskCustomPropertySelector();
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<TaskSummary> taskSummaries = getTasksToChange(selectionCriteria);
List<String> 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: {} ", changedTasks);
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("updateTasks() found no tasks for update ");
}
}
return changedTasks;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public List<String> updateTasks(
List<String> taskIds, Map<TaskCustomField, String> customFieldsToUpdate)
throws InvalidArgumentException {
validateCustomFields(customFieldsToUpdate);
TaskCustomPropertySelector fieldSelector = new TaskCustomPropertySelector();
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<TaskSummary> taskSummaries = getTasksToChange(taskIds);
List<String> 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: {} ", changedTasks);
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("updateTasks() found no tasks for update ");
}
}
return changedTasks;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public TaskComment createTaskComment(TaskComment taskComment)
throws TaskNotFoundException, InvalidArgumentException, NotAuthorizedOnWorkbasketException {
return taskCommentService.createTaskComment(taskComment);
}
@Override
public TaskComment updateTaskComment(TaskComment taskComment)
throws ConcurrencyException,
TaskCommentNotFoundException,
TaskNotFoundException,
InvalidArgumentException,
NotAuthorizedOnTaskCommentException,
NotAuthorizedOnWorkbasketException {
return taskCommentService.updateTaskComment(taskComment);
}
@Override
public void deleteTaskComment(String taskCommentId)
throws TaskCommentNotFoundException,
TaskNotFoundException,
InvalidArgumentException,
NotAuthorizedOnTaskCommentException,
NotAuthorizedOnWorkbasketException {
taskCommentService.deleteTaskComment(taskCommentId);
}
@Override
public TaskComment getTaskComment(String taskCommentid)
throws TaskCommentNotFoundException,
TaskNotFoundException,
InvalidArgumentException,
NotAuthorizedOnWorkbasketException {
return taskCommentService.getTaskComment(taskCommentid);
}
@Override
public List<TaskComment> getTaskComments(String taskId)
throws TaskNotFoundException, NotAuthorizedOnWorkbasketException {
return taskCommentService.getTaskComments(taskId);
}
@Override
public BulkOperationResults<String, TaskanaException> setCallbackStateForTasks(
List<String> externalIds, CallbackState state) {
try {
taskanaEngine.openConnection();
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
if (externalIds == null || externalIds.isEmpty()) {
return bulkLog;
}
List<MinimalTaskSummary> taskSummaries = taskMapper.findExistingTasks(null, externalIds);
Iterator<String> taskIdIterator = new ArrayList<>(externalIds).iterator();
while (taskIdIterator.hasNext()) {
removeSingleTaskForCallbackStateByExternalId(bulkLog, taskSummaries, taskIdIterator, state);
}
if (!externalIds.isEmpty()) {
taskMapper.setCallbackStateMultiple(externalIds, state);
}
return bulkLog;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public BulkOperationResults<String, TaskanaException> setOwnerOfTasks(
String owner, List<String> taskIds) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
if (taskIds == null || taskIds.isEmpty()) {
return bulkLog;
}
try {
taskanaEngine.openConnection();
Pair<List<MinimalTaskSummary>, BulkLog> existingAndAuthorizedTasks =
getMinimalTaskSummaries(taskIds);
bulkLog.addAllErrors(existingAndAuthorizedTasks.getRight());
Pair<List<String>, BulkLog> taskIdsToUpdate =
filterOutTasksWhichAreInInvalidState(existingAndAuthorizedTasks.getLeft());
bulkLog.addAllErrors(taskIdsToUpdate.getRight());
if (!taskIdsToUpdate.getLeft().isEmpty()) {
taskMapper.setOwnerOfTasks(owner, taskIdsToUpdate.getLeft(), Instant.now());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Received the Request to set owner on {} tasks, actually modified tasks = {}"
+ ", could not set owner on {} tasks.",
taskIds.size(),
taskIdsToUpdate.getLeft().size(),
bulkLog.getFailedIds().size());
}
return bulkLog;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public BulkOperationResults<String, TaskanaException> setPlannedPropertyOfTasks(
Instant planned, List<String> argTaskIds) {
BulkLog bulkLog = new BulkLog();
if (argTaskIds == null || argTaskIds.isEmpty()) {
return bulkLog;
}
try {
taskanaEngine.openConnection();
Pair<List<MinimalTaskSummary>, BulkLog> resultsPair = getMinimalTaskSummaries(argTaskIds);
List<MinimalTaskSummary> tasksToModify = resultsPair.getLeft();
bulkLog.addAllErrors(resultsPair.getRight());
BulkLog errorsFromProcessing =
serviceLevelHandler.setPlannedPropertyOfTasksImpl(planned, tasksToModify);
bulkLog.addAllErrors(errorsFromProcessing);
return bulkLog;
} finally {
taskanaEngine.returnConnection();
}
}
@Override
public Task cancelTask(String taskId)
throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException {
Task cancelledTask;
try {
taskanaEngine.openConnection();
cancelledTask = terminateCancelCommonActions(taskId, TaskState.CANCELLED);
if (historyEventManager.isEnabled()) {
historyEventManager.createEvent(
new TaskCancelledEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
cancelledTask,
taskanaEngine.getEngine().getCurrentUserContext().getUserid()));
}
} finally {
taskanaEngine.returnConnection();
}
return cancelledTask;
}
@Override
public Task terminateTask(String taskId)
throws TaskNotFoundException,
NotAuthorizedException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN);
Task terminatedTask;
try {
taskanaEngine.openConnection();
terminatedTask = terminateCancelCommonActions(taskId, TaskState.TERMINATED);
if (historyEventManager.isEnabled()) {
historyEventManager.createEvent(
new TaskTerminatedEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
terminatedTask,
taskanaEngine.getEngine().getCurrentUserContext().getUserid()));
}
} finally {
taskanaEngine.returnConnection();
}
return terminatedTask;
}
public List<String> findTasksIdsAffectedByClassificationChange(String classificationId) {
// tasks directly affected
List<TaskSummary> tasksAffectedDirectly =
createTaskQuery()
.classificationIdIn(classificationId)
.stateIn(TaskState.READY, TaskState.CLAIMED)
.list();
// tasks indirectly affected via attachments
List<Pair<String, Instant>> affectedPairs =
tasksAffectedDirectly.stream()
.map(t -> Pair.of(t.getId(), t.getPlanned()))
.collect(Collectors.toList());
// tasks indirectly affected via attachments
List<Pair<String, Instant>> taskIdsAndPlannedFromAttachments =
attachmentMapper.findTaskIdsAndPlannedAffectedByClassificationChange(classificationId);
List<String> taskIdsFromAttachments =
taskIdsAndPlannedFromAttachments.stream().map(Pair::getLeft).collect(Collectors.toList());
List<Pair<String, Instant>> filteredTaskIdsAndPlannedFromAttachments =
taskIdsFromAttachments.isEmpty()
? new ArrayList<>()
: taskMapper.filterTaskIdsForReadyAndClaimed(taskIdsFromAttachments);
affectedPairs.addAll(filteredTaskIdsAndPlannedFromAttachments);
// sort all affected tasks according to the planned instant
List<String> 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,
affectedTaskIds);
}
return affectedTaskIds;
}
public void refreshPriorityAndDueDatesOfTasksOnClassificationUpdate(
List<String> taskIds, boolean serviceLevelChanged, boolean priorityChanged) {
Pair<List<MinimalTaskSummary>, BulkLog> resultsPair = getMinimalTaskSummaries(taskIds);
List<MinimalTaskSummary> tasks = resultsPair.getLeft();
try {
taskanaEngine.openConnection();
Set<String> adminAccessIds =
taskanaEngine.getEngine().getConfiguration().getRoleMap().get(TaskanaRole.ADMIN);
if (adminAccessIds.contains(taskanaEngine.getEngine().getCurrentUserContext().getUserid())) {
serviceLevelHandler.refreshPriorityAndDueDatesOfTasks(
tasks, serviceLevelChanged, priorityChanged);
} else {
taskanaEngine
.getEngine()
.runAsAdmin(
() ->
serviceLevelHandler.refreshPriorityAndDueDatesOfTasks(
tasks, serviceLevelChanged, priorityChanged));
}
} finally {
taskanaEngine.returnConnection();
}
}
Pair<List<MinimalTaskSummary>, BulkLog> getMinimalTaskSummaries(Collection<String> argTaskIds) {
BulkLog bulkLog = new BulkLog();
// remove duplicates
Set<String> taskIds = new HashSet<>(argTaskIds);
// get existing tasks
List<MinimalTaskSummary> minimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(addExceptionsForNonExistingTasksToBulkLog(taskIds, minimalTaskSummaries));
Pair<List<MinimalTaskSummary>, BulkLog> filteredPair =
filterTasksAuthorizedForAndLogErrorsForNotAuthorized(minimalTaskSummaries);
bulkLog.addAllErrors(filteredPair.getRight());
return Pair.of(filteredPair.getLeft(), bulkLog);
}
Pair<List<MinimalTaskSummary>, BulkLog> filterTasksAuthorizedForAndLogErrorsForNotAuthorized(
List<MinimalTaskSummary> 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 Pair.of(existingTasks, bulkLog);
} else {
List<String> accessIds = taskanaEngine.getEngine().getCurrentUserContext().getAccessIds();
List<Pair<String, String>> taskAndWorkbasketIdsNotAuthorizedFor =
taskMapper.getTaskAndWorkbasketIdsNotAuthorizedFor(existingTasks, accessIds);
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
for (Pair<String, String> taskAndWorkbasketIds : taskAndWorkbasketIdsNotAuthorizedFor) {
bulkLog.addError(
taskAndWorkbasketIds.getLeft(),
new NotAuthorizedOnWorkbasketException(
userId, taskAndWorkbasketIds.getRight(), WorkbasketPermission.READ));
}
Set<String> taskIdsToRemove =
taskAndWorkbasketIdsNotAuthorizedFor.stream()
.map(Pair::getLeft)
.collect(Collectors.toSet());
List<MinimalTaskSummary> tasksAuthorizedFor =
existingTasks.stream()
.filter(not(t -> taskIdsToRemove.contains(t.getTaskId())))
.collect(Collectors.toList());
return Pair.of(tasksAuthorizedFor, bulkLog);
}
}
BulkLog addExceptionsForNonExistingTasksToBulkLog(
Collection<String> requestTaskIds, List<MinimalTaskSummary> existingMinimalTaskSummaries) {
BulkLog bulkLog = new BulkLog();
Set<String> existingTaskIds =
existingMinimalTaskSummaries.stream()
.map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toSet());
requestTaskIds.stream()
.filter(not(existingTaskIds::contains))
.forEach(taskId -> bulkLog.addError(taskId, new TaskNotFoundException(taskId)));
return bulkLog;
}
List<TaskSummary> augmentTaskSummariesByContainedSummariesWithPartitioning(
List<TaskSummaryImpl> taskSummaries) {
// splitting Augmentation into steps of maximal 32000 tasks
// reason: DB2 has a maximum for parameters in a query
return CollectionUtil.partitionBasedOnSize(taskSummaries, 32000).stream()
.map(this::appendComplexAttributesToTaskSummariesWithoutPartitioning)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private Pair<List<String>, BulkLog> filterOutTasksWhichAreInInvalidState(
Collection<MinimalTaskSummary> minimalTaskSummaries) {
List<String> filteredTasks = new ArrayList<>(minimalTaskSummaries.size());
BulkLog bulkLog = new BulkLog();
for (MinimalTaskSummary taskSummary : minimalTaskSummaries) {
if (!taskSummary.getTaskState().in(TaskState.READY, TaskState.READY_FOR_REVIEW)) {
bulkLog.addError(
taskSummary.getTaskId(),
new InvalidTaskStateException(
taskSummary.getTaskId(),
taskSummary.getTaskState(),
TaskState.READY,
TaskState.READY_FOR_REVIEW));
} else {
filteredTasks.add(taskSummary.getTaskId());
}
}
return Pair.of(filteredTasks, bulkLog);
}
private List<TaskSummaryImpl> appendComplexAttributesToTaskSummariesWithoutPartitioning(
List<TaskSummaryImpl> taskSummaries) {
Set<String> taskIds =
taskSummaries.stream().map(TaskSummaryImpl::getId).collect(Collectors.toSet());
if (taskIds.isEmpty()) {
taskIds = null;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"augmentTaskSummariesByContainedSummariesWithoutPartitioning() with sublist {} "
+ "about to query for attachmentSummaries ",
taskSummaries);
}
List<AttachmentSummaryImpl> attachmentSummaries =
attachmentMapper.findAttachmentSummariesByTaskIds(taskIds);
Map<String, ClassificationSummary> classificationSummariesById =
findClassificationsForTasksAndAttachments(taskSummaries, attachmentSummaries);
addClassificationSummariesToAttachments(attachmentSummaries, classificationSummariesById);
addClassificationSummariesToTaskSummaries(taskSummaries, classificationSummariesById);
addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries);
Map<String, WorkbasketSummary> workbasketSummariesById = findWorkbasketsForTasks(taskSummaries);
List<ObjectReferenceImpl> objectReferences =
objectReferenceMapper.findObjectReferencesByTaskIds(taskIds);
addWorkbasketSummariesToTaskSummaries(taskSummaries, workbasketSummariesById);
addObjectReferencesToTaskSummaries(taskSummaries, objectReferences);
return taskSummaries;
}
private BulkOperationResults<String, TaskanaException> completeTasks(
List<String> taskIds, boolean forced) throws InvalidArgumentException {
try {
taskanaEngine.openConnection();
if (taskIds == null) {
throw new InvalidArgumentException("TaskIds can't be used as NULL-Parameter.");
}
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS);
Stream<TaskSummaryImpl> filteredSummaries =
filterNotExistingTaskIds(taskIds, bulkLog)
.filter(task -> task.getState() != TaskState.COMPLETED)
.filter(
addErrorToBulkLog(TaskServiceImpl::checkIfTaskIsTerminatedOrCancelled, bulkLog));
if (!forced) {
filteredSummaries =
filteredSummaries.filter(
addErrorToBulkLog(this::checkPreconditionsForCompleteTask, bulkLog));
} else {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
String userLongName;
if (taskanaEngine.getEngine().getConfiguration().isAddAdditionalUserInfo()) {
User user = userMapper.findById(userId);
if (user != null) {
userLongName = user.getLongName();
} else {
userLongName = null;
}
} else {
userLongName = null;
}
filteredSummaries =
filteredSummaries.filter(
addErrorToBulkLog(
summary -> {
if (taskIsNotClaimed(summary)) {
checkPreconditionsForClaimTask(summary, true);
claimActionsOnTask(summary, userId, userLongName, now);
}
},
bulkLog));
}
updateTasksToBeCompleted(filteredSummaries, now);
return bulkLog;
} finally {
taskanaEngine.returnConnection();
}
}
private Stream<TaskSummaryImpl> filterNotExistingTaskIds(
List<String> taskIds, BulkOperationResults<String, TaskanaException> bulkLog) {
Map<String, TaskSummaryImpl> taskSummaryMap =
getTasksToChange(taskIds).stream()
.collect(Collectors.toMap(TaskSummary::getId, TaskSummaryImpl.class::cast));
return taskIds.stream()
.map(id -> Pair.of(id, taskSummaryMap.get(id)))
.filter(
pair -> {
if (pair.getRight() != null) {
return true;
}
String taskId = pair.getLeft();
bulkLog.addError(taskId, new TaskNotFoundException(taskId));
return false;
})
.map(Pair::getRight);
}
private static Predicate<TaskSummaryImpl> addErrorToBulkLog(
CheckedConsumer<TaskSummaryImpl, TaskanaException> checkedConsumer,
BulkOperationResults<String, TaskanaException> bulkLog) {
return summary -> {
try {
checkedConsumer.accept(summary);
return true;
} catch (TaskanaException e) {
bulkLog.addError(summary.getId(), e);
return false;
}
};
}
private void 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(newTaskImpl.getId());
}
newTaskImpl.setModified(Instant.now());
}
private TaskImpl terminateCancelCommonActions(String taskId, TaskState targetState)
throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException {
if (taskId == null || taskId.isEmpty()) {
throw new TaskNotFoundException(taskId);
}
TaskImpl task = (TaskImpl) getTask(taskId);
TaskState state = task.getState();
if (state.isEndState()) {
throw new InvalidTaskStateException(taskId, state, TaskState.READY);
}
Instant now = Instant.now();
task.setModified(now);
task.setCompleted(now);
task.setState(targetState);
taskMapper.update(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Task '{}' cancelled by user '{}'.",
taskId,
taskanaEngine.getEngine().getCurrentUserContext().getUserid());
}
return task;
}
private Task claim(String taskId, boolean forceClaim)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
String userLongName = null;
if (taskanaEngine.getEngine().getConfiguration().isAddAdditionalUserInfo()) {
User user = userMapper.findById(userId);
if (user != null) {
userLongName = user.getLongName();
}
}
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
TaskImpl oldTask = duplicateTaskExactly(task);
Instant now = Instant.now();
checkPreconditionsForClaimTask(task, forceClaim);
claimActionsOnTask(task, userId, userLongName, now);
taskMapper.update(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task '{}' claimed by user '{}'.", taskId, userId);
}
if (historyEventManager.isEnabled()) {
String changeDetails =
ObjectAttributeChangeDetector.determineChangesInAttributes(oldTask, task);
historyEventManager.createEvent(
new TaskClaimedEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
userId,
changeDetails));
}
} finally {
taskanaEngine.returnConnection();
}
return task;
}
private Task requestReview(String taskId, boolean force)
throws TaskNotFoundException,
InvalidTaskStateException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
task = (TaskImpl) beforeRequestReviewManager.beforeRequestReview(task);
TaskImpl oldTask = duplicateTaskExactly(task);
if (force && task.getState().isEndState()) {
throw new InvalidTaskStateException(
task.getId(), task.getState(), EnumUtil.allValuesExceptFor(TaskState.END_STATES));
}
if (!force && taskIsNotClaimed(task)) {
throw new InvalidTaskStateException(
task.getId(), task.getState(), TaskState.CLAIMED, TaskState.IN_REVIEW);
}
if (!force && !task.getOwner().equals(userId)) {
throw new InvalidOwnerException(userId, task.getId());
}
task.setState(TaskState.READY_FOR_REVIEW);
task.setOwner(null);
task.setModified(Instant.now());
taskMapper.requestReview(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Requested review for Task '{}' by user '{}'.", taskId, userId);
}
if (historyEventManager.isEnabled()) {
String changeDetails =
ObjectAttributeChangeDetector.determineChangesInAttributes(oldTask, task);
historyEventManager.createEvent(
new TaskRequestReviewEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
userId,
changeDetails));
}
task = (TaskImpl) afterRequestReviewManager.afterRequestReview(task);
} finally {
taskanaEngine.returnConnection();
}
return task;
}
private Task requestChanges(String taskId, boolean force)
throws InvalidTaskStateException,
TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
task = (TaskImpl) beforeRequestChangesManager.beforeRequestChanges(task);
TaskImpl oldTask = duplicateTaskExactly(task);
if (force && task.getState().isEndState()) {
throw new InvalidTaskStateException(
task.getId(), task.getState(), EnumUtil.allValuesExceptFor(TaskState.END_STATES));
}
if (!force && task.getState() != TaskState.IN_REVIEW) {
throw new InvalidTaskStateException(task.getId(), task.getState(), TaskState.IN_REVIEW);
}
if (!force && !task.getOwner().equals(userId)) {
throw new InvalidOwnerException(userId, task.getId());
}
task.setState(TaskState.READY);
task.setOwner(null);
task.setModified(Instant.now());
taskMapper.requestChanges(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Requested changes for Task '{}' by user '{}'.", taskId, userId);
}
if (historyEventManager.isEnabled()) {
String changeDetails =
ObjectAttributeChangeDetector.determineChangesInAttributes(oldTask, task);
historyEventManager.createEvent(
new TaskRequestChangesEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
userId,
changeDetails));
}
task = (TaskImpl) afterRequestChangesManager.afterRequestChanges(task);
} finally {
taskanaEngine.returnConnection();
}
return task;
}
private static void claimActionsOnTask(
TaskSummaryImpl task, String userId, String userLongName, Instant now) {
task.setOwner(userId);
task.setOwnerLongName(userLongName);
task.setModified(now);
task.setClaimed(now);
task.setRead(true);
if (Set.of(TaskState.READY_FOR_REVIEW, TaskState.IN_REVIEW).contains(task.getState())) {
task.setState(TaskState.IN_REVIEW);
} else {
task.setState(TaskState.CLAIMED);
}
}
private static void cancelClaimActionsOnTask(TaskSummaryImpl task, Instant now) {
task.setOwner(null);
task.setModified(now);
task.setClaimed(null);
task.setRead(true);
if (task.getState() == TaskState.IN_REVIEW) {
task.setState(TaskState.READY_FOR_REVIEW);
} else {
task.setState(TaskState.READY);
}
}
private static void completeActionsOnTask(TaskSummaryImpl task, String userId, Instant now) {
task.setCompleted(now);
task.setModified(now);
task.setState(TaskState.COMPLETED);
task.setOwner(userId);
}
private void checkPreconditionsForClaimTask(TaskSummary task, boolean forced)
throws InvalidOwnerException, InvalidTaskStateException {
TaskState state = task.getState();
if (state.isEndState()) {
throw new InvalidTaskStateException(
task.getId(), task.getState(), EnumUtil.allValuesExceptFor(TaskState.END_STATES));
}
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
if (!forced
&& (state == TaskState.CLAIMED || state == TaskState.IN_REVIEW)
&& !task.getOwner().equals(userId)) {
throw new InvalidOwnerException(userId, task.getId());
}
}
private static boolean taskIsNotClaimed(TaskSummary task) {
return task.getClaimed() == null
|| (task.getState() != TaskState.CLAIMED && task.getState() != TaskState.IN_REVIEW);
}
private static void checkIfTaskIsTerminatedOrCancelled(TaskSummary task)
throws InvalidTaskStateException {
if (task.getState().in(TaskState.CANCELLED, TaskState.TERMINATED)) {
throw new InvalidTaskStateException(
task.getId(),
task.getState(),
EnumUtil.allValuesExceptFor(TaskState.CANCELLED, TaskState.TERMINATED));
}
}
private void checkPreconditionsForCompleteTask(TaskSummary task)
throws InvalidOwnerException, InvalidTaskStateException {
if (taskIsNotClaimed(task)) {
throw new InvalidTaskStateException(
task.getId(), task.getState(), TaskState.CLAIMED, TaskState.IN_REVIEW);
} else if (!taskanaEngine
.getEngine()
.getCurrentUserContext()
.getAccessIds()
.contains(task.getOwner())
&& !taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN)) {
throw new InvalidOwnerException(
taskanaEngine.getEngine().getCurrentUserContext().getUserid(), task.getId());
}
}
private Task cancelClaim(String taskId, boolean forceUnclaim)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
TaskImpl oldTask = duplicateTaskExactly(task);
TaskState state = task.getState();
if (state.isEndState()) {
throw new InvalidTaskStateException(
taskId, state, EnumUtil.allValuesExceptFor(TaskState.END_STATES));
}
if ((state == TaskState.CLAIMED || state == TaskState.IN_REVIEW)
&& !forceUnclaim
&& !userId.equals(task.getOwner())) {
throw new InvalidOwnerException(userId, taskId);
}
Instant now = Instant.now();
cancelClaimActionsOnTask(task, now);
taskMapper.update(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task '{}' unclaimed by user '{}'.", taskId, userId);
}
if (historyEventManager.isEnabled()) {
String changeDetails =
ObjectAttributeChangeDetector.determineChangesInAttributes(oldTask, task);
historyEventManager.createEvent(
new TaskClaimCancelledEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
userId,
changeDetails));
}
} finally {
taskanaEngine.returnConnection();
}
return task;
}
private Task completeTask(String taskId, boolean isForced)
throws TaskNotFoundException,
InvalidOwnerException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException {
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) this.getTask(taskId);
if (reviewRequiredManager.reviewRequired(task)) {
return requestReview(taskId);
}
if (task.getState() == TaskState.COMPLETED) {
return task;
}
checkIfTaskIsTerminatedOrCancelled(task);
if (!isForced) {
checkPreconditionsForCompleteTask(task);
} else if (taskIsNotClaimed(task)) {
task = (TaskImpl) this.forceClaim(taskId);
}
Instant now = Instant.now();
completeActionsOnTask(task, userId, now);
taskMapper.update(task);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId);
}
if (historyEventManager.isEnabled()) {
historyEventManager.createEvent(
new TaskCompletedEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
userId));
}
} finally {
taskanaEngine.returnConnection();
}
return task;
}
private void deleteTask(String taskId, boolean forceDelete)
throws TaskNotFoundException,
NotAuthorizedException,
NotAuthorizedOnWorkbasketException,
InvalidTaskStateException,
InvalidCallbackStateException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN);
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
if (!(task.getState().isEndState()) && !forceDelete) {
throw new InvalidTaskStateException(taskId, task.getState(), TaskState.END_STATES);
}
if ((!task.getState().in(TaskState.TERMINATED, TaskState.CANCELLED))
&& CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(task.getCallbackState())) {
throw new InvalidCallbackStateException(
taskId,
task.getCallbackState(),
EnumUtil.allValuesExceptFor(CallbackState.CALLBACK_PROCESSING_REQUIRED));
}
attachmentMapper.deleteMultipleByTaskIds(Collections.singletonList(taskId));
objectReferenceMapper.deleteMultipleByTaskIds(Collections.singletonList(taskId));
taskMapper.delete(taskId);
if (taskanaEngine.getEngine().isHistoryEnabled()
&& taskanaEngine
.getEngine()
.getConfiguration()
.isDeleteHistoryEventsOnTaskDeletionEnabled()) {
historyEventManager.deleteEvents(Collections.singletonList(taskId));
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task {} deleted.", taskId);
}
} finally {
taskanaEngine.returnConnection();
}
}
private void removeSingleTaskForTaskDeletionById(
BulkOperationResults<String, TaskanaException> bulkLog,
List<MinimalTaskSummary> taskSummaries,
Iterator<String> taskIdIterator) {
String currentTaskId = taskIdIterator.next();
if (currentTaskId == null || currentTaskId.equals("")) {
bulkLog.addError("", new TaskNotFoundException(null));
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));
taskIdIterator.remove();
} else if (!(foundSummary.getTaskState().isEndState())) {
bulkLog.addError(
currentTaskId,
new InvalidTaskStateException(
currentTaskId, foundSummary.getTaskState(), TaskState.END_STATES));
taskIdIterator.remove();
} else {
if ((!foundSummary.getTaskState().in(TaskState.CANCELLED, TaskState.TERMINATED))
&& CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(foundSummary.getCallbackState())) {
bulkLog.addError(
currentTaskId,
new InvalidCallbackStateException(
currentTaskId,
foundSummary.getCallbackState(),
EnumUtil.allValuesExceptFor(CallbackState.CALLBACK_PROCESSING_REQUIRED)));
taskIdIterator.remove();
}
}
}
}
private void removeSingleTaskForCallbackStateByExternalId(
BulkOperationResults<String, TaskanaException> bulkLog,
List<MinimalTaskSummary> taskSummaries,
Iterator<String> externalIdIterator,
CallbackState desiredCallbackState) {
String currentExternalId = externalIdIterator.next();
if (currentExternalId == null || currentExternalId.equals("")) {
bulkLog.addError("", new TaskNotFoundException(null));
externalIdIterator.remove();
} else {
Optional<MinimalTaskSummary> foundSummary =
taskSummaries.stream()
.filter(taskSummary -> currentExternalId.equals(taskSummary.getExternalId()))
.findFirst();
if (foundSummary.isPresent()) {
Optional<TaskanaException> invalidStateException =
desiredCallbackStateCanBeSetForFoundSummary(foundSummary.get(), desiredCallbackState);
if (invalidStateException.isPresent()) {
bulkLog.addError(currentExternalId, invalidStateException.get());
externalIdIterator.remove();
}
} else {
bulkLog.addError(currentExternalId, new TaskNotFoundException(currentExternalId));
externalIdIterator.remove();
}
}
}
private Optional<TaskanaException> desiredCallbackStateCanBeSetForFoundSummary(
MinimalTaskSummary foundSummary, CallbackState desiredCallbackState) {
CallbackState currentTaskCallbackState = foundSummary.getCallbackState();
TaskState currentTaskState = foundSummary.getTaskState();
switch (desiredCallbackState) {
case CALLBACK_PROCESSING_COMPLETED:
if (!currentTaskState.isEndState()) {
return Optional.of(
new InvalidTaskStateException(
foundSummary.getTaskId(), foundSummary.getTaskState(), TaskState.END_STATES));
}
break;
case CLAIMED:
if (!currentTaskState.equals(TaskState.CLAIMED)) {
return Optional.of(
new InvalidTaskStateException(
foundSummary.getTaskId(), foundSummary.getTaskState(), TaskState.CLAIMED));
}
if (!currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_REQUIRED)) {
return Optional.of(
new InvalidCallbackStateException(
foundSummary.getTaskId(),
currentTaskCallbackState,
CallbackState.CALLBACK_PROCESSING_REQUIRED));
}
break;
case CALLBACK_PROCESSING_REQUIRED:
if (currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_COMPLETED)) {
return Optional.of(
new InvalidCallbackStateException(
foundSummary.getTaskId(),
currentTaskCallbackState,
EnumUtil.allValuesExceptFor(CallbackState.CALLBACK_PROCESSING_COMPLETED)));
}
break;
default:
return Optional.of(
new InvalidCallbackStateException(
foundSummary.getTaskId(),
currentTaskCallbackState,
CallbackState.CALLBACK_PROCESSING_COMPLETED,
CallbackState.CLAIMED,
CallbackState.CALLBACK_PROCESSING_REQUIRED));
}
return Optional.empty();
}
private void standardSettingsOnTaskCreation(TaskImpl task, Classification classification)
throws InvalidArgumentException,
ClassificationNotFoundException,
AttachmentPersistenceException,
ObjectReferencePersistenceException {
final Instant now = Instant.now();
task.setId(IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK));
if (task.getExternalId() == null) {
task.setExternalId(IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_EXT_TASK));
}
task.setState(TaskState.READY);
task.setCreated(now);
task.setModified(now);
task.setRead(false);
task.setTransferred(false);
String creator = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
if (taskanaEngine.getEngine().getConfiguration().isSecurityEnabled() && creator == null) {
throw new SystemException(
"TaskanaSecurity is enabled, but the current UserId is NULL while creating a Task.");
}
task.setCreator(creator);
// if no business process id is provided, a unique id is created.
if (task.getBusinessProcessId() == null) {
task.setBusinessProcessId(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_BUSINESS_PROCESS));
}
// null in case of manual tasks
if (task.getPlanned() == null && (classification == null || task.getDue() == null)) {
task.setPlanned(now);
}
if (task.getName() == null && classification != null) {
task.setName(classification.getName());
}
if (task.getDescription() == null && classification != null) {
task.setDescription(classification.getDescription());
}
if (task.getOwner() != null
&& taskanaEngine.getEngine().getConfiguration().isAddAdditionalUserInfo()) {
User user = userMapper.findById(task.getOwner());
if (user != null) {
task.setOwnerLongName(user.getLongName());
}
}
setDefaultTaskReceivedDateFromAttachments(task);
attachmentHandler.insertNewAttachmentsOnTaskCreation(task);
objectReferenceHandler.insertNewSecondaryObjectReferencesOnTaskCreation(task);
// This has to be called after the AttachmentHandler because the AttachmentHandler fetches
// the Classifications of the Attachments.
// This is necessary to guarantee that the following calculation is correct.
serviceLevelHandler.updatePrioPlannedDueOfTask(task, null);
}
private void setDefaultTaskReceivedDateFromAttachments(TaskImpl task) {
if (task.getReceived() == null) {
task.getAttachments().stream()
.map(AttachmentSummary::getReceived)
.filter(Objects::nonNull)
.min(Instant::compareTo)
.ifPresent(task::setReceived);
}
}
private void setCallbackStateOnTaskCreation(TaskImpl task) throws InvalidArgumentException {
Map<String, String> 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 updateTasksToBeCompleted(Stream<TaskSummaryImpl> taskSummaries, Instant now) {
List<String> taskIds = new ArrayList<>();
List<String> updateClaimedTaskIds = new ArrayList<>();
List<TaskSummary> taskSummaryList =
taskSummaries
.peek(
summary ->
completeActionsOnTask(
summary,
taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
now))
.peek(summary -> taskIds.add(summary.getId()))
.peek(
summary -> {
if (summary.getClaimed().equals(now)) {
updateClaimedTaskIds.add(summary.getId());
}
})
.collect(Collectors.toList());
TaskSummary claimedReference =
taskSummaryList.stream()
.filter(summary -> updateClaimedTaskIds.contains(summary.getId()))
.findFirst()
.orElse(null);
if (!taskSummaryList.isEmpty()) {
taskMapper.updateCompleted(taskIds, taskSummaryList.get(0));
if (!updateClaimedTaskIds.isEmpty()) {
taskMapper.updateClaimed(updateClaimedTaskIds, claimedReference);
}
if (historyEventManager.isEnabled()) {
createTasksCompletedEvents(taskSummaryList);
}
}
}
private Map<String, WorkbasketSummary> findWorkbasketsForTasks(
List<? extends TaskSummary> taskSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return Collections.emptyMap();
}
Set<String> workbasketIds =
taskSummaries.stream()
.map(TaskSummary::getWorkbasketSummary)
.map(WorkbasketSummary::getId)
.collect(Collectors.toSet());
return queryWorkbasketsForTasks(workbasketIds).stream()
.collect(Collectors.toMap(WorkbasketSummary::getId, Function.identity()));
}
private Map<String, ClassificationSummary> findClassificationsForTasksAndAttachments(
List<? extends TaskSummary> taskSummaries,
List<? extends AttachmentSummaryImpl> attachmentSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return Collections.emptyMap();
}
Set<String> classificationIds =
Stream.concat(
taskSummaries.stream().map(TaskSummary::getClassificationSummary),
attachmentSummaries.stream().map(AttachmentSummary::getClassificationSummary))
.map(ClassificationSummary::getId)
.collect(Collectors.toSet());
return queryClassificationsForTasksAndAttachments(classificationIds).stream()
.collect(Collectors.toMap(ClassificationSummary::getId, Function.identity()));
}
private Map<String, ClassificationSummary> findClassificationForTaskImplAndAttachments(
TaskImpl task, List<AttachmentImpl> attachmentImpls) {
return findClassificationsForTasksAndAttachments(
Collections.singletonList(task), attachmentImpls);
}
private List<ClassificationSummary> queryClassificationsForTasksAndAttachments(
Set<String> classificationIds) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"queryClassificationsForTasksAndAttachments() about to query classifications and exit");
}
return this.classificationService
.createClassificationQuery()
.idIn(classificationIds.toArray(new String[0]))
.list();
}
private List<WorkbasketSummary> queryWorkbasketsForTasks(Set<String> workbasketIds) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("queryWorkbasketsForTasks() about to query workbaskets and exit");
}
// perform classification query
return this.workbasketService
.createWorkbasketQuery()
.idIn(workbasketIds.toArray(new String[0]))
.list();
}
private void addClassificationSummariesToTaskSummaries(
List<TaskSummaryImpl> tasks, Map<String, ClassificationSummary> classificationSummaryById) {
if (tasks == null || tasks.isEmpty()) {
return;
}
for (TaskSummaryImpl task : tasks) {
String classificationId = task.getClassificationSummary().getId();
ClassificationSummary classificationSummary = classificationSummaryById.get(classificationId);
if (classificationSummary == null) {
throw new SystemException(
"Did not find a Classification for task (Id="
+ task.getId()
+ ",Classification="
+ task.getClassificationSummary().getId()
+ ")");
}
task.setClassificationSummary(classificationSummary);
}
}
private void addWorkbasketSummariesToTaskSummaries(
List<TaskSummaryImpl> tasks, Map<String, WorkbasketSummary> workbasketSummaryById) {
if (tasks == null || tasks.isEmpty()) {
return;
}
for (TaskSummaryImpl task : tasks) {
String workbasketId = task.getWorkbasketSummary().getId();
WorkbasketSummary workbasketSummary = workbasketSummaryById.get(workbasketId);
if (workbasketSummary == null) {
throw new SystemException(
"Did not find a Workbasket for task (Id="
+ task.getId()
+ ",Workbasket="
+ task.getWorkbasketSummary().getId()
+ ")");
}
task.setWorkbasketSummary(workbasketSummary);
}
}
private void addAttachmentSummariesToTaskSummaries(
List<TaskSummaryImpl> taskSummaries, List<AttachmentSummaryImpl> attachmentSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return;
}
Map<String, TaskSummaryImpl> taskSummariesById =
taskSummaries.stream()
.collect(
Collectors.toMap(
TaskSummary::getId,
Function.identity(),
// Currently, we still have a bug (TSK-1204), where the TaskQuery#list function
// returns the same task multiple times when that task has more than one
// attachment...Therefore, this MergeFunction is necessary.
(a, b) -> b));
for (AttachmentSummaryImpl attachmentSummary : attachmentSummaries) {
String taskId = attachmentSummary.getTaskId();
TaskSummaryImpl taskSummary = taskSummariesById.get(taskId);
if (taskSummary != null) {
taskSummary.addAttachmentSummary(attachmentSummary);
}
}
}
private void addClassificationSummariesToAttachments(
List<? extends AttachmentSummaryImpl> attachments,
Map<String, ClassificationSummary> classificationSummariesById) {
if (attachments == null || attachments.isEmpty()) {
return;
}
for (AttachmentSummaryImpl attachment : attachments) {
String classificationId = attachment.getClassificationSummary().getId();
ClassificationSummary classificationSummary =
classificationSummariesById.get(classificationId);
if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + attachment);
}
attachment.setClassificationSummary(classificationSummary);
}
}
private void addObjectReferencesToTaskSummaries(
List<TaskSummaryImpl> taskSummaries, List<ObjectReferenceImpl> objectReferences) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return;
}
Map<String, TaskSummaryImpl> taskSummariesById =
taskSummaries.stream()
.collect(
Collectors.toMap(
TaskSummary::getId,
Function.identity(),
// The TaskQuery#list function
// returns the same task multiple times when that task has more than one
// object reference...Therefore, this MergeFunction is necessary.
(a, b) -> b));
for (ObjectReferenceImpl objectReference : objectReferences) {
String taskId = objectReference.getTaskId();
TaskSummaryImpl taskSummary = taskSummariesById.get(taskId);
if (taskSummary != null) {
taskSummary.addSecondaryObjectReference(objectReference);
}
}
}
private TaskImpl initUpdatedTask(
Map<TaskCustomField, String> customFieldsToUpdate, TaskCustomPropertySelector fieldSelector) {
TaskImpl newTask = new TaskImpl();
newTask.setModified(Instant.now());
for (Map.Entry<TaskCustomField, String> entry : customFieldsToUpdate.entrySet()) {
TaskCustomField key = entry.getKey();
fieldSelector.setCustomProperty(key, true);
newTask.setCustomField(key, entry.getValue());
}
return newTask;
}
private void validateCustomFields(Map<TaskCustomField, String> customFieldsToUpdate)
throws InvalidArgumentException {
if (customFieldsToUpdate == null || customFieldsToUpdate.isEmpty()) {
throw new InvalidArgumentException(
"The customFieldsToUpdate argument to updateTasks must not be empty.");
}
}
private List<TaskSummary> getTasksToChange(List<String> taskIds) {
return createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
}
private List<TaskSummary> 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, ClassificationNotFoundException, InvalidTaskStateException {
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());
}
setDefaultTaskReceivedDateFromAttachments(newTaskImpl);
updateClassificationSummary(newTaskImpl, oldTaskImpl);
TaskImpl newTaskImpl1 =
serviceLevelHandler.updatePrioPlannedDueOfTask(newTaskImpl, oldTaskImpl);
// 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 either in state ready or ready_for_review
boolean isOwnerChanged = !Objects.equals(newTaskImpl1.getOwner(), oldTaskImpl.getOwner());
if (isOwnerChanged && !oldTaskImpl.getState().in(TaskState.READY, TaskState.READY_FOR_REVIEW)) {
throw new InvalidTaskStateException(
oldTaskImpl.getId(), oldTaskImpl.getState(), TaskState.READY, TaskState.READY_FOR_REVIEW);
}
}
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<? extends TaskSummary> taskSummaries) {
taskSummaries.forEach(
task ->
historyEventManager.createEvent(
new TaskCompletedEvent(
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
task,
taskanaEngine.getEngine().getCurrentUserContext().getUserid())));
}
private TaskImpl duplicateTaskExactly(TaskImpl task) {
TaskImpl oldTask = task.copy();
oldTask.setId(task.getId());
oldTask.setExternalId(task.getExternalId());
oldTask.setAttachments(task.getAttachments());
oldTask.setSecondaryObjectReferences(task.getSecondaryObjectReferences());
return oldTask;
}
}