TSK-1143 UpdateTask with Planned sometimes fails

This commit is contained in:
BerndBreier 2020-02-28 10:45:51 +01:00 committed by Holger Hagen
parent 630006de82
commit 1291287419
8 changed files with 667 additions and 647 deletions

View File

@ -39,7 +39,7 @@ public class TaskRefreshJob extends AbstractTaskanaJob {
TaskServiceImpl taskService = (TaskServiceImpl) taskanaEngineImpl.getTaskService();
for (String taskId : affectedTaskIds) {
try {
taskService.refreshPriorityAndDueDate(taskId);
taskService.refreshPriorityAndDueDateOnClassificationUpdate(taskId);
} catch (Exception e) {
LOGGER.warn(
"Task {} could not be refreshed because of exception: {}", taskId, e.getMessage());

View File

@ -0,0 +1,264 @@
package pro.taskana.task.internal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
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.ClassificationSummary;
import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
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.internal.models.AttachmentImpl;
import pro.taskana.task.internal.models.TaskImpl;
public class AttachmentHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(AttachmentHandler.class);
private static final String ID_PREFIX_ATTACHMENT = "TAI";
private final AttachmentMapper attachmentMapper;
private final ClassificationService classificationService;
AttachmentHandler(
AttachmentMapper attachmentMapper, ClassificationService classificationService) {
this.attachmentMapper = attachmentMapper;
this.classificationService = classificationService;
}
public List<Attachment> augmentAttachmentsByClassification(
List<AttachmentImpl> attachmentImpls,
BulkOperationResults<String, Exception> bulkLog) {
LOGGER.debug("entry to augmentAttachmentsByClassification()");
List<Attachment> result = new ArrayList<>();
if (attachmentImpls == null || attachmentImpls.isEmpty()) {
return result;
}
List<ClassificationSummary> classifications =
classificationService
.createClassificationQuery()
.idIn(
attachmentImpls.stream()
.map(t -> t.getClassificationSummary().getId())
.distinct()
.toArray(String[]::new))
.list();
for (AttachmentImpl att : attachmentImpls) {
ClassificationSummary classificationSummary =
classifications.stream()
.filter(cl -> cl.getId().equals(att.getClassificationSummary().getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
String id = att.getClassificationSummary().getId();
bulkLog.addError(
att.getClassificationSummary().getId(),
new ClassificationNotFoundException(
id,
String.format(
"When processing task updates due to change "
+ "of classification, the classification with id %s was not found",
id)));
} else {
att.setClassificationSummary(classificationSummary);
result.add(att);
}
}
LOGGER.debug("exit from augmentAttachmentsByClassification()");
return result;
}
void insertAndDeleteAttachmentsOnTaskUpdate(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
throws AttachmentPersistenceException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to insertAndDeleteAttachmentsOnTaskUpdate(oldTaskImpl = {}, newTaskImpl = {})",
oldTaskImpl,
newTaskImpl);
}
List<Attachment> newAttachments =
newTaskImpl.getAttachments().stream().filter(Objects::nonNull).collect(Collectors.toList());
newTaskImpl.setAttachments(newAttachments);
deleteRemovedAttachmentsOnTaskUpdate(newTaskImpl, oldTaskImpl);
insertNewAttachmentsOnTaskUpdate(newTaskImpl, oldTaskImpl);
updateModifiedAttachmentsOnTaskUpdate(newTaskImpl, oldTaskImpl);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"exit from insertAndDeleteAttachmentsOnTaskUpdate(oldTaskImpl = {}, newTaskImpl = {})",
oldTaskImpl,
newTaskImpl);
}
}
void updateModifiedAttachmentsOnTaskUpdate(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) {
List<Attachment> newAttachments = newTaskImpl.getAttachments();
List<Attachment> oldAttachments = oldTaskImpl.getAttachments();
if (newAttachments != null
&& !newAttachments.isEmpty()
&& oldAttachments != null
&& !oldAttachments.isEmpty()) {
final Map<String, Attachment> oldAttachmentMap =
oldAttachments.stream()
.collect(Collectors.toMap(AttachmentSummary::getId, Function.identity()));
newAttachments.forEach(
a -> {
if (oldAttachmentMap.containsKey(a.getId())
&& !a.equals(oldAttachmentMap.get(a.getId()))) {
attachmentMapper.update((AttachmentImpl) a);
}
});
}
}
void insertNewAttachmentsOnTaskUpdate(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
throws AttachmentPersistenceException {
List<String> oldAttachmentIds =
oldTaskImpl.getAttachments().stream()
.map(AttachmentSummary::getId)
.collect(Collectors.toList());
List<AttachmentPersistenceException> exceptions = new ArrayList<>();
newTaskImpl
.getAttachments()
.forEach(
a -> {
if (!oldAttachmentIds.contains(a.getId())) {
try {
insertNewAttachmentOnTaskUpdate(newTaskImpl, a);
} catch (AttachmentPersistenceException excpt) {
exceptions.add(excpt);
LOGGER.warn("attempted to insert attachment {} and caught exception", a, excpt);
}
}
});
if (!exceptions.isEmpty()) {
throw exceptions.get(0);
}
}
void insertNewAttachmentsOnTaskCreation(TaskImpl task, Instant now)
throws InvalidArgumentException {
List<Attachment> attachments = task.getAttachments();
if (attachments != null) {
for (Attachment attachment : attachments) {
AttachmentImpl attachmentImpl = (AttachmentImpl) attachment;
attachmentImpl.setId(IdGenerator.generateWithPrefix(ID_PREFIX_ATTACHMENT));
attachmentImpl.setTaskId(task.getId());
attachmentImpl.setCreated(now);
attachmentImpl.setModified(now);
ObjectReference objRef = attachmentImpl.getObjectReference();
validateObjectReference(objRef, "ObjectReference", "Attachment");
attachmentMapper.insert(attachmentImpl);
}
}
}
void deleteRemovedAttachmentsOnTaskUpdate(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to deleteRemovedAttachmentsOnTaskUpdate(oldTaskImpl = {}, newTaskImpl = {})",
oldTaskImpl,
newTaskImpl);
}
final List<Attachment> newAttachments = newTaskImpl.getAttachments();
List<String> newAttachmentIds = new ArrayList<>();
if (newAttachments != null && !newAttachments.isEmpty()) {
newAttachmentIds =
newAttachments.stream().map(Attachment::getId).collect(Collectors.toList());
}
List<Attachment> oldAttachments = oldTaskImpl.getAttachments();
if (oldAttachments != null && !oldAttachments.isEmpty()) {
final List<String> newAttIds = newAttachmentIds;
oldAttachments.forEach(
a -> {
if (!newAttIds.contains(a.getId())) {
attachmentMapper.deleteAttachment(a.getId());
LOGGER.debug(
"TaskService.updateTask() for TaskId={} DELETED an Attachment={}.",
newTaskImpl.getId(),
a);
}
});
}
LOGGER.debug("exit from deleteRemovedAttachmentsOnTaskUpdate()");
}
void insertNewAttachmentOnTaskUpdate(TaskImpl newTaskImpl, Attachment attachment)
throws AttachmentPersistenceException {
LOGGER.debug("entry to insertNewAttachmentOnTaskUpdate()");
AttachmentImpl attachmentImpl = (AttachmentImpl) attachment;
initAttachment(attachmentImpl, newTaskImpl);
try {
attachmentMapper.insert(attachmentImpl);
LOGGER.debug(
"TaskService.updateTask() for TaskId={} INSERTED an Attachment={}.",
newTaskImpl.getId(),
attachmentImpl);
} catch (PersistenceException e) {
throw new AttachmentPersistenceException(
"Cannot insert the Attachement "
+ attachmentImpl.getId()
+ " for Task "
+ newTaskImpl.getId()
+ " because it already exists.",
e.getCause());
}
LOGGER.debug("exit from insertNewAttachmentOnTaskUpdate(), returning");
}
void initAttachment(AttachmentImpl attachment, Task newTask) {
LOGGER.debug("entry to initAttachment()");
if (attachment.getId() == null) {
attachment.setId(IdGenerator.generateWithPrefix(ID_PREFIX_ATTACHMENT));
}
if (attachment.getCreated() == null) {
attachment.setCreated(Instant.now());
}
if (attachment.getModified() == null) {
attachment.setModified(attachment.getCreated());
}
if (attachment.getTaskId() == null) {
attachment.setTaskId(newTask.getId());
}
LOGGER.debug("exit from initAttachment()");
}
void validateObjectReference(ObjectReference objRef, String objRefType, String objName)
throws InvalidArgumentException {
LOGGER.debug("entry to validateObjectReference()");
// check that all values in the ObjectReference are set correctly
if (objRef == null) {
throw new InvalidArgumentException(objRefType + " of " + objName + " must not be null");
} else if (objRef.getCompany() == null || objRef.getCompany().length() == 0) {
throw new InvalidArgumentException(
String.format("Company of %s of %s must not be empty", objRefType, objName));
} else if (objRef.getSystem() == null || objRef.getSystem().length() == 0) {
throw new InvalidArgumentException(
String.format("System of %s of %s must not be empty", objRefType, objName));
} else if (objRef.getSystemInstance() == null || objRef.getSystemInstance().length() == 0) {
throw new InvalidArgumentException(
String.format("SystemInstance of %s of %s must not be empty", objRefType, objName));
} else if (objRef.getType() == null || objRef.getType().length() == 0) {
throw new InvalidArgumentException(
String.format("Type of %s of %s must not be empty", objRefType, objName));
} else if (objRef.getValue() == null || objRef.getValue().length() == 0) {
throw new InvalidArgumentException(
String.format("Value of %s of %s must not be empty", objRefType, objName));
}
LOGGER.debug("exit from validateObjectReference()");
}
}

View File

@ -6,6 +6,7 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -26,6 +27,8 @@ import pro.taskana.common.internal.util.DaysToWorkingDaysConverter;
import pro.taskana.common.internal.util.Pair;
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.AttachmentSummary;
import pro.taskana.task.internal.models.AttachmentSummaryImpl;
import pro.taskana.task.internal.models.MinimalTaskSummary;
import pro.taskana.task.internal.models.TaskImpl;
@ -42,11 +45,10 @@ class ServiceLevelHandler {
private final AttachmentMapper attachmentMapper;
private DaysToWorkingDaysConverter converter;
public ServiceLevelHandler(
ServiceLevelHandler(
InternalTaskanaEngine taskanaEngine,
TaskMapper taskMapper,
AttachmentMapper attachmentMapper) {
super();
this.taskanaEngine = taskanaEngine;
this.taskMapper = taskMapper;
this.attachmentMapper = attachmentMapper;
@ -66,13 +68,12 @@ class ServiceLevelHandler {
// - For each task iterate through all referenced classifications and find minimum ServiceLevel
// - collect the results into a map Duration -> List of tasks
// - for each duration in this map update due date of all associated tasks
public BulkOperationResults<String, TaskanaException> setPlannedPropertyOfTasksImpl(
Instant planned, List<String> argTaskIds) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
public BulkLog setPlannedPropertyOfTasksImpl(Instant planned, List<String> argTaskIds) {
BulkLog bulkLog = new BulkLog();
if (argTaskIds == null || argTaskIds.isEmpty()) {
return bulkLog;
}
Pair<List<MinimalTaskSummary>, BulkOperationResults<String, TaskanaException>> resultsPair =
Pair<List<MinimalTaskSummary>, BulkLog> resultsPair =
filterTasksForExistenceAndAuthorization(argTaskIds);
List<MinimalTaskSummary> existingTasksAuthorizedFor = resultsPair.getLeft();
bulkLog.addAllErrors(resultsPair.getRight());
@ -85,29 +86,172 @@ class ServiceLevelHandler {
Map<Duration, List<TaskDuration>> tasksPerDuration =
getTasksPerDuration(
existingTasksAuthorizedFor, attachments, allInvolvedClassificationsWithDuration);
bulkLog.addAllErrors(updateAffectedTasks(planned, tasksPerDuration));
BulkLog updateResult = updatePlannedPropertyOfAffectedTasks(planned, tasksPerDuration);
bulkLog.addAllErrors(updateResult);
return bulkLog;
}
BulkOperationResults<String, TaskanaException> addExceptionsForNonExistingTasks(
BulkLog addExceptionsForNonExistingTasksToBulkLog(
List<String> requestTaskIds, List<MinimalTaskSummary> existingMinimalTaskSummaries) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
BulkLog bulkLog = new BulkLog();
List<String> nonExistingTaskIds = new ArrayList<>(requestTaskIds);
List<String> existingTaskIds =
existingMinimalTaskSummaries.stream()
.map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toList());
nonExistingTaskIds.removeAll(existingTaskIds);
for (String taskId : nonExistingTaskIds) {
bulkLog.addError(taskId, new TaskNotFoundException(taskId, "Task was not found"));
}
nonExistingTaskIds.forEach(
taskId ->
bulkLog.addError(taskId, new TaskNotFoundException(taskId, "Task was not found")));
return bulkLog;
}
private BulkOperationResults<String, TaskanaException> updateAffectedTasks(
TaskImpl updatePrioPlannedDueOfTask(
TaskImpl newTaskImpl, TaskImpl oldTaskImpl, boolean forRefreshOnClassificationUpdate)
throws InvalidArgumentException {
boolean onlyPriority = false;
if (newTaskImpl.getClassificationSummary() == null
|| newTaskImpl.getClassificationSummary().getServiceLevel() == null) {
onlyPriority = true;
}
if (isPriorityAndDurationAlreadyCorrect(newTaskImpl, oldTaskImpl)) {
return newTaskImpl;
}
if (newTaskImpl.getPlanned() == null && newTaskImpl.getDue() == null) {
newTaskImpl.setPlanned(Instant.now());
}
DurationPrioHolder durationPrioHolder = determineTaskPrioDuration(newTaskImpl, onlyPriority);
newTaskImpl.setPriority(durationPrioHolder.getPriority());
if (onlyPriority) {
return newTaskImpl;
}
// classification update
if (forRefreshOnClassificationUpdate) {
newTaskImpl.setDue(newPlannedDueInstant(newTaskImpl, durationPrioHolder.getDuration(), true));
return newTaskImpl;
}
// creation of new task
if (oldTaskImpl == null) {
return updatePlannedDueOnCreationOfNewTask(newTaskImpl, durationPrioHolder);
} else {
return updatePlannedDueOnTaskUpdate(newTaskImpl, oldTaskImpl, durationPrioHolder);
}
}
Instant newPlannedDueInstant(TaskImpl task, Duration duration, boolean fromPlannedToDue) {
if (fromPlannedToDue) {
long days = converter.convertWorkingDaysToDays(task.getPlanned(), duration.toDays());
return task.getPlanned().plus(Duration.ofDays(days));
} else {
long days = converter.convertWorkingDaysToDays(task.getDue(), -duration.toDays());
return task.getDue().plus(Duration.ofDays(days));
}
}
DurationPrioHolder determineTaskPrioDuration(TaskImpl newTaskImpl, boolean onlyPriority) {
Set<ClassificationSummary> classificationsInvolved = getInvolvedClassifications(newTaskImpl);
List<ClassificationWithServiceLevelResolved> resolvedClassifications = new ArrayList<>();
if (onlyPriority) {
for (ClassificationSummary c : classificationsInvolved) {
resolvedClassifications.add(
new ClassificationWithServiceLevelResolved(c.getId(), MAX_DURATION, 0));
}
} else {
resolvedClassifications =
resolveDurationsInClassifications(new ArrayList<>(classificationsInvolved));
}
return getFinalPrioDurationOfTask(resolvedClassifications, onlyPriority);
}
Pair<List<MinimalTaskSummary>, BulkLog> filterTasksForExistenceAndAuthorization(
List<String> argTaskIds) {
BulkLog bulkLog = new BulkLog();
// remove duplicates
List<String> taskIds = argTaskIds.stream().distinct().collect(Collectors.toList());
// 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 new Pair<>(filteredPair.getLeft(), bulkLog);
}
Pair<List<MinimalTaskSummary>, BulkLog> filterTasksAuthorizedForAndLogErrorsForNotAuthorized(
List<MinimalTaskSummary> existingTasks) {
BulkLog bulkLog = new BulkLog();
// check authorization only for non-admin users
if (taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN)) {
return new Pair<>(existingTasks, bulkLog);
} else {
List<String> taskIds =
existingTasks.stream().map(MinimalTaskSummary::getTaskId).collect(Collectors.toList());
List<String> accessIds = CurrentUserContext.getAccessIds();
List<String> 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<MinimalTaskSummary> tasksAuthorizedFor =
existingTasks.stream()
.filter(t -> taskIds.contains(t.getTaskId()))
.collect(Collectors.toList());
return new Pair<>(tasksAuthorizedFor, bulkLog);
}
}
private TaskImpl updatePlannedDueOnTaskUpdate(
TaskImpl newTaskImpl, TaskImpl oldTaskImpl, DurationPrioHolder durationPrioHolder)
throws InvalidArgumentException {
// case 1: no change of planned / due, but change of an attachment or classification
if (newTaskImpl.getDue().equals(oldTaskImpl.getDue())
&& newTaskImpl.getPlanned().equals(oldTaskImpl.getPlanned())) {
newTaskImpl.setDue(newPlannedDueInstant(newTaskImpl, durationPrioHolder.getDuration(), true));
} else if ((newTaskImpl.getDue().equals(oldTaskImpl.getDue()))) {
// case 2: planned was changed
newTaskImpl.setDue(newPlannedDueInstant(newTaskImpl, durationPrioHolder.getDuration(), true));
} else { // case 3: due was changed
Instant planned = newPlannedDueInstant(newTaskImpl, durationPrioHolder.getDuration(), false);
if (newTaskImpl.getPlanned() != null && !planned.equals(newTaskImpl.getPlanned())) {
throw new InvalidArgumentException(
"Cannot update a task with given planned "
+ "and due date not matching the service level");
}
newTaskImpl.setPlanned(planned);
}
return newTaskImpl;
}
private TaskImpl updatePlannedDueOnCreationOfNewTask(
TaskImpl newTaskImpl, DurationPrioHolder durationPrioHolder) throws InvalidArgumentException {
if (newTaskImpl.getDue() != null) { // due is specified: calculate back and check correctnes
Instant planned = newPlannedDueInstant(newTaskImpl, durationPrioHolder.getDuration(), false);
if (newTaskImpl.getPlanned() != null && !planned.equals(newTaskImpl.getPlanned())) {
throw new InvalidArgumentException(
"Cannot create a task with given planned "
+ "and due date not matching the service level");
}
newTaskImpl.setPlanned(planned);
} else { // task.due is null: calculate forward from planned
newTaskImpl.setDue(newPlannedDueInstant(newTaskImpl, durationPrioHolder.getDuration(), true));
}
return newTaskImpl;
}
private BulkLog updatePlannedPropertyOfAffectedTasks(
Instant planned, Map<Duration, List<TaskDuration>> tasksPerDuration) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
BulkLog bulkLog = new BulkLog();
TaskImpl referenceTask = new TaskImpl();
referenceTask.setPlanned(planned);
for (Map.Entry<Duration, List<TaskDuration>> entry : tasksPerDuration.entrySet()) {
@ -119,32 +263,18 @@ class ServiceLevelHandler {
referenceTask.setModified(Instant.now());
long numTasksUpdated = taskMapper.updateTaskDueDates(taskIdsToUpdate, referenceTask);
if (numTasksUpdated != taskIdsToUpdate.size()) {
bulkLog.addAllErrors(
BulkLog checkResult =
checkResultsOfTasksUpdateAndAddErrorsToBulkLog(
taskIdsToUpdate, referenceTask, numTasksUpdated));
taskIdsToUpdate, referenceTask, numTasksUpdated);
bulkLog.addAllErrors(checkResult);
}
}
return bulkLog;
}
private Pair<List<MinimalTaskSummary>, BulkOperationResults<String, TaskanaException>>
filterTasksForExistenceAndAuthorization(List<String> argTaskIds) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
// remove duplicates
List<String> taskIds = argTaskIds.stream().distinct().collect(Collectors.toList());
// get existing tasks
List<MinimalTaskSummary> minimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(addExceptionsForNonExistingTasks(taskIds, minimalTaskSummaries));
Pair<List<MinimalTaskSummary>, BulkOperationResults<String, TaskanaException>> filteredPair =
filterTasksAuthorizedForAndLogErrorsForNotAuthorized(minimalTaskSummaries);
bulkLog.addAllErrors(filteredPair.getRight());
return new Pair<>(filteredPair.getLeft(), bulkLog);
}
private BulkOperationResults<String, TaskanaException>
checkResultsOfTasksUpdateAndAddErrorsToBulkLog(
List<String> taskIdsToUpdate, TaskImpl referenceTask, long numTasksUpdated) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
private BulkLog checkResultsOfTasksUpdateAndAddErrorsToBulkLog(
List<String> taskIdsToUpdate, TaskImpl referenceTask, long numTasksUpdated) {
BulkLog bulkLog = new BulkLog();
long numErrors = taskIdsToUpdate.size() - numTasksUpdated;
long numErrorsLogged = 0;
if (numErrors > 0) {
@ -162,7 +292,7 @@ class ServiceLevelHandler {
if (numErrorsNotLogged != 0) {
for (int i = 1; i <= numErrorsNotLogged; i++) {
bulkLog.addError(
String.format("UnknownTaskId%s", i),
String.format("UnknownTaskId%s", Integer.valueOf(i)),
new UpdateFailedException("Update of unknown task failed"));
}
}
@ -230,7 +360,9 @@ class ServiceLevelHandler {
List<ClassificationWithServiceLevelResolved> result = new ArrayList<>();
for (ClassificationSummary classification : allInvolvedClassifications) {
Duration serviceLevel = Duration.parse(classification.getServiceLevel());
result.add(new ClassificationWithServiceLevelResolved(classification.getId(), serviceLevel));
result.add(
new ClassificationWithServiceLevelResolved(
classification.getId(), serviceLevel, classification.getPriority()));
}
return result;
}
@ -245,11 +377,9 @@ class ServiceLevelHandler {
String[] taskIdsAuthorizedForArray = new String[existingTaskIdsAuthorizedFor.size()];
taskIdsAuthorizedForArray = existingTaskIdsAuthorizedFor.toArray(taskIdsAuthorizedForArray);
if (existingTaskIdsAuthorizedFor.isEmpty()) {
return new ArrayList<>();
} else {
return attachmentMapper.findAttachmentSummariesByTaskIds(taskIdsAuthorizedForArray);
}
return existingTaskIdsAuthorizedFor.isEmpty()
? new ArrayList<>()
: attachmentMapper.findAttachmentSummariesByTaskIds(taskIdsAuthorizedForArray);
}
private List<ClassificationSummary> findAllInvolvedClassifications(
@ -279,41 +409,92 @@ class ServiceLevelHandler {
}
}
private Pair<List<MinimalTaskSummary>, BulkOperationResults<String, TaskanaException>>
filterTasksAuthorizedForAndLogErrorsForNotAuthorized(List<MinimalTaskSummary> existingTasks) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
// check authorization only for non-admin users
if (taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN)) {
return new Pair<>(existingTasks, bulkLog);
} else {
List<String> taskIds =
existingTasks.stream().map(MinimalTaskSummary::getTaskId).collect(Collectors.toList());
List<String> accessIds = CurrentUserContext.getAccessIds();
List<String> 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));
private DurationPrioHolder getFinalPrioDurationOfTask(
List<ClassificationWithServiceLevelResolved> cl, boolean onlyPriority) {
Duration duration = MAX_DURATION;
int priority = Integer.MIN_VALUE;
for (ClassificationWithServiceLevelResolved classification : cl) {
Duration actualDuration = classification.getDurationFromClassification();
if (!onlyPriority && duration.compareTo(actualDuration) > 0) {
duration = actualDuration;
}
taskIds.removeAll(taskIdsNotAuthorizedFor);
List<MinimalTaskSummary> tasksAuthorizedFor =
existingTasks.stream()
.filter(t -> taskIds.contains(t.getTaskId()))
.collect(Collectors.toList());
return new Pair<>(tasksAuthorizedFor, bulkLog);
if (classification.getPriority() > priority) {
priority = classification.getPriority();
}
}
return new DurationPrioHolder(duration, priority);
}
private Set<ClassificationSummary> getInvolvedClassifications(TaskImpl taskImpl) {
Set<ClassificationSummary> classifications =
taskImpl.getAttachments() == null
? new HashSet<>()
: taskImpl.getAttachments().stream()
.map(Attachment::getClassificationSummary)
.collect(Collectors.toSet());
classifications.add(taskImpl.getClassificationSummary());
return classifications;
}
private boolean isPriorityAndDurationAlreadyCorrect(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) {
if (oldTaskImpl != null) {
final boolean isClassificationKeyChanged =
newTaskImpl.getClassificationKey() != null
&& (oldTaskImpl.getClassificationKey() == null
|| !newTaskImpl
.getClassificationKey()
.equals(oldTaskImpl.getClassificationKey()));
final boolean isClassificationIdChanged =
newTaskImpl.getClassificationId() != null
&& (oldTaskImpl.getClassificationId() == null
|| !newTaskImpl.getClassificationId().equals(oldTaskImpl.getClassificationId()));
return oldTaskImpl.getPlanned().equals(newTaskImpl.getPlanned())
&& oldTaskImpl.getDue().equals(newTaskImpl.getDue())
&& !isClassificationKeyChanged
&& !isClassificationIdChanged
&& areAttachmentsUnchanged(newTaskImpl, oldTaskImpl);
} else {
return false;
}
}
static class ClassificationWithServiceLevelResolved {
private String classificationId;
private Duration duration;
private boolean areAttachmentsUnchanged(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) {
List<String> oldAttachmentIds =
oldTaskImpl.getAttachments().stream()
.map(AttachmentSummary::getId)
.collect(Collectors.toList());
List<String> newAttachmentIds =
newTaskImpl.getAttachments().stream()
.map(AttachmentSummary::getId)
.collect(Collectors.toList());
Set<String> oldClassificationIds =
oldTaskImpl.getAttachments().stream()
.map(Attachment::getClassificationSummary)
.map(ClassificationSummary::getId)
.collect(Collectors.toSet());
Set<String> newClassificationIds =
newTaskImpl.getAttachments().stream()
.map(Attachment::getClassificationSummary)
.map(ClassificationSummary::getId)
.collect(Collectors.toSet());
ClassificationWithServiceLevelResolved(String id, Duration serviceLevel) {
return oldAttachmentIds.size() == newAttachmentIds.size()
&& newAttachmentIds.containsAll(oldAttachmentIds)
&& oldClassificationIds.size() == newClassificationIds.size()
&& newClassificationIds.containsAll(oldClassificationIds);
}
static class ClassificationWithServiceLevelResolved {
private final int priority;
private final String classificationId;
private final Duration duration;
ClassificationWithServiceLevelResolved(String id, Duration serviceLevel, int priority) {
this.classificationId = id;
this.duration = serviceLevel;
this.priority = priority;
}
String getClassificationId() {
@ -323,12 +504,16 @@ class ServiceLevelHandler {
Duration getDurationFromClassification() {
return duration;
}
int getPriority() {
return priority;
}
}
static class TaskDuration {
private String taskId;
private Duration duration;
private final String taskId;
private final Duration duration;
TaskDuration(String id, Duration serviceLevel) {
this.taskId = id;
@ -343,4 +528,28 @@ class ServiceLevelHandler {
return duration;
}
}
static class BulkLog extends BulkOperationResults<String, TaskanaException> {
BulkLog() {
super();
}
}
static class DurationPrioHolder {
private Duration duration;
private int priority;
DurationPrioHolder(Duration duration, int priority) {
this.duration = duration;
this.priority = priority;
}
Duration getDuration() {
return duration;
}
int getPriority() {
return priority;
}
}
}

View File

@ -1,6 +1,5 @@
package pro.taskana.task.internal;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
@ -31,7 +30,6 @@ 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.DaysToWorkingDaysConverter;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.common.internal.util.Pair;
import pro.taskana.spi.history.api.events.task.ClaimCancelledEvent;
@ -55,6 +53,7 @@ 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;
@ -86,15 +85,12 @@ public class TaskServiceImpl implements TaskService {
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_ATTACHMENT = "TAI";
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 String MUST_NOT_BE_EMPTY = " must not be empty";
private static final Set<String> ALLOWED_KEYS =
IntStream.rangeClosed(1, 16).mapToObj(String::valueOf).collect(Collectors.toSet());
private static final Duration MAX_DURATION = Duration.ofSeconds(Long.MAX_VALUE, 999_999_999);
private DaysToWorkingDaysConverter converter;
private InternalTaskanaEngine taskanaEngine;
private WorkbasketService workbasketService;
@ -104,7 +100,8 @@ public class TaskServiceImpl implements TaskService {
private HistoryEventProducer historyEventProducer;
private TaskTransferrer taskTransferrer;
private TaskCommentServiceImpl taskCommentService;
private ServiceLevelHandler serviceLevelHander;
private ServiceLevelHandler serviceLevelHandler;
private AttachmentHandler attachmentHandler;
public TaskServiceImpl(
InternalTaskanaEngine taskanaEngine,
@ -112,12 +109,6 @@ public class TaskServiceImpl implements TaskService {
TaskCommentMapper taskCommentMapper,
AttachmentMapper attachmentMapper) {
super();
try {
this.converter = DaysToWorkingDaysConverter.initialize();
} catch (InvalidArgumentException e) {
throw new SystemException(
"Internal error. Cannot initialize DaysToWorkingDaysConverter", e.getCause());
}
this.taskanaEngine = taskanaEngine;
this.taskMapper = taskMapper;
this.workbasketService = taskanaEngine.getEngine().getWorkbasketService();
@ -126,7 +117,8 @@ public class TaskServiceImpl implements TaskService {
this.historyEventProducer = taskanaEngine.getHistoryEventProducer();
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
this.taskCommentService = new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, this);
this.serviceLevelHander = new ServiceLevelHandler(taskanaEngine, taskMapper, attachmentMapper);
this.serviceLevelHandler = new ServiceLevelHandler(taskanaEngine, taskMapper, attachmentMapper);
this.attachmentHandler = new AttachmentHandler(attachmentMapper, classificationService);
}
@Override
@ -223,9 +215,9 @@ public class TaskServiceImpl implements TaskService {
Classification classification =
this.classificationService.getClassification(classificationKey, workbasket.getDomain());
task.setClassificationSummary(classification.asSummary());
validateObjectReference(task.getPrimaryObjRef(), "primary ObjectReference", TASK);
PrioDurationHolder prioDurationFromAttachments = handleAttachments(task);
standardSettings(task, classification, prioDurationFromAttachments);
attachmentHandler.validateObjectReference(
task.getPrimaryObjRef(), "primary ObjectReference", TASK);
standardSettings(task, classification);
setCallbackStateOnTaskCreation(task);
try {
this.taskMapper.insert(task);
@ -409,8 +401,8 @@ public class TaskServiceImpl implements TaskService {
@Override
public Task updateTask(Task task)
throws InvalidArgumentException, TaskNotFoundException, ConcurrencyException,
ClassificationNotFoundException, NotAuthorizedException, AttachmentPersistenceException,
InvalidStateException {
NotAuthorizedException, AttachmentPersistenceException, InvalidStateException,
ClassificationNotFoundException {
String userId = CurrentUserContext.getUserid();
LOGGER.debug("entry to updateTask(task = {}, userId = {})", task, userId);
TaskImpl newTaskImpl = (TaskImpl) task;
@ -418,9 +410,7 @@ public class TaskServiceImpl implements TaskService {
try {
taskanaEngine.openConnection();
oldTaskImpl = (TaskImpl) getTask(newTaskImpl.getId());
PrioDurationHolder prioDurationFromAttachments =
handleAttachmentsOnTaskUpdate(oldTaskImpl, newTaskImpl);
standardUpdateActions(oldTaskImpl, newTaskImpl, prioDurationFromAttachments);
standardUpdateActions(oldTaskImpl, newTaskImpl);
taskMapper.update(newTaskImpl);
LOGGER.debug("Method updateTask() updated task '{}' for user '{}'.", task.getId(), userId);
@ -532,7 +522,8 @@ public class TaskServiceImpl implements TaskService {
selectionCriteria,
customFieldsToUpdate);
}
validateObjectReference(selectionCriteria, "ObjectReference", "updateTasks call");
attachmentHandler.validateObjectReference(
selectionCriteria, "ObjectReference", "updateTasks call");
validateCustomFields(customFieldsToUpdate);
CustomPropertySelector fieldSelector = new CustomPropertySelector();
TaskImpl updated = initUpdatedTask(customFieldsToUpdate, fieldSelector);
@ -689,10 +680,14 @@ public class TaskServiceImpl implements TaskService {
try {
taskanaEngine.openConnection();
// use only elements we are authorized for
Pair<List<String>, BulkOperationResults<String, TaskanaException>> resultsPair =
filterForAuthorizedTasks(taskIds);
Pair<List<MinimalTaskSummary>, BulkLog> resultsPair =
serviceLevelHandler.filterTasksForExistenceAndAuthorization(taskIds);
// set the Owner of these tasks we are authorized for
taskIds = resultsPair.getLeft();
List<MinimalTaskSummary> existingMinimalTaskSummaries = resultsPair.getLeft();
taskIds =
existingMinimalTaskSummaries.stream()
.map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toList());
bulkLog.addAllErrors(resultsPair.getRight());
if (taskIds.isEmpty()) {
return bulkLog;
@ -702,13 +697,7 @@ public class TaskServiceImpl implements TaskService {
return bulkLog;
} else {
// check the outcome
List<MinimalTaskSummary> existingMinimalTaskSummaries =
taskMapper.findExistingTasks(taskIds, null);
// add exceptions for non existing tasks
bulkLog.addAllErrors(
serviceLevelHander.addExceptionsForNonExistingTasks(
taskIds, existingMinimalTaskSummaries));
// add exceptions of all remaining tasks whose owners were not set
existingMinimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(
addExceptionsForTasksWhoseOwnerWasNotSet(owner, existingMinimalTaskSummaries));
if (LOGGER.isDebugEnabled()) {
@ -739,7 +728,7 @@ public class TaskServiceImpl implements TaskService {
}
try {
taskanaEngine.openConnection();
return serviceLevelHander.setPlannedPropertyOfTasksImpl(planned, argTaskIds);
return serviceLevelHandler.setPlannedPropertyOfTasksImpl(planned, argTaskIds);
} finally {
LOGGER.debug("exit from setPlannedPropertyOfTasks");
taskanaEngine.returnConnection();
@ -780,7 +769,8 @@ public class TaskServiceImpl implements TaskService {
return affectedTaskIds;
}
public void refreshPriorityAndDueDate(String taskId) throws ClassificationNotFoundException {
public void refreshPriorityAndDueDateOnClassificationUpdate(String taskId)
throws ClassificationNotFoundException {
LOGGER.debug("entry to refreshPriorityAndDueDate(taskId = {})", taskId);
TaskImpl task;
BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>();
@ -796,19 +786,21 @@ public class TaskServiceImpl implements TaskService {
if (attachmentImpls == null) {
attachmentImpls = new ArrayList<>();
}
List<Attachment> attachments = augmentAttachmentsByClassification(attachmentImpls, bulkLog);
List<Attachment> attachments =
attachmentHandler.augmentAttachmentsByClassification(attachmentImpls, bulkLog);
task.setAttachments(attachments);
Classification classification =
classificationService.getClassification(task.getClassificationSummary().getId());
task.setClassificationSummary(classification.asSummary());
PrioDurationHolder prioDurationFromAttachments =
handleAttachmentsOnClassificationUpdate(task);
updatePrioDueDateOnClassificationUpdate(task, prioDurationFromAttachments);
ClassificationSummary classificationSummary =
classificationService
.getClassification(task.getClassificationKey(), task.getDomain())
.asSummary();
task.setClassificationSummary(classificationSummary);
task = serviceLevelHandler.updatePrioPlannedDueOfTask(task, null, true);
task.setModified(Instant.now());
taskMapper.update(task);
} catch (InvalidArgumentException e) {
LOGGER.error("Internal System error. This situation should not happen. Caught exceptio ", e);
} finally {
taskanaEngine.returnConnection();
LOGGER.debug("exit from refreshPriorityAndDueDate(). ");
@ -895,7 +887,7 @@ public class TaskServiceImpl implements TaskService {
for (MinimalTaskSummary taskSummary : existingMinimalTaskSummaries) {
if (!owner.equals(taskSummary.getOwner())) { // owner was not set
if (!taskSummary.getTaskState().equals(TaskState.READY)) { // due to invalid state
if (!TaskState.READY.equals(taskSummary.getTaskState())) { // due to invalid state
bulkLog.addError(
taskSummary.getTaskId(),
new InvalidStateException(
@ -914,27 +906,6 @@ public class TaskServiceImpl implements TaskService {
return bulkLog;
}
private Pair<List<String>, BulkOperationResults<String, TaskanaException>>
filterForAuthorizedTasks(List<String> taskIds) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
List<String> accessIds = CurrentUserContext.getAccessIds();
List<String> tasksAuthorizedFor = new ArrayList<>(taskIds);
// check authorization only for non-admin users
if (!taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN)) {
List<String> tasksNotAuthorizedFor =
taskMapper.filterTaskIdsNotAuthorizedFor(taskIds, accessIds);
tasksAuthorizedFor.removeAll(tasksNotAuthorizedFor);
String currentUserId = CurrentUserContext.getUserid();
for (String taskId : tasksNotAuthorizedFor) {
bulkLog.addError(
taskId,
new NotAuthorizedException(
String.format("Current user not authorized for task %s.", taskId), currentUserId));
}
}
return new Pair<>(tasksAuthorizedFor, bulkLog);
}
private Task claim(String taskId, boolean forceClaim)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
@ -1178,8 +1149,7 @@ public class TaskServiceImpl implements TaskService {
}
}
private void standardSettings(
TaskImpl task, Classification classification, PrioDurationHolder prioDurationFromAttachments)
private void standardSettings(TaskImpl task, Classification classification)
throws InvalidArgumentException {
LOGGER.debug("entry to standardSettings()");
final Instant now = Instant.now();
@ -1206,40 +1176,11 @@ public class TaskServiceImpl implements TaskService {
}
// null in case of manual tasks
if (classification == null) {
if (task.getPlanned() == null) {
task.setPlanned(now);
}
} else {
// do some Classification specific stuff (servicelevel).
// get duration in days from planned to due
PrioDurationHolder finalPrioDuration =
getNewPrioDuration(
prioDurationFromAttachments,
classification.getPriority(),
classification.getServiceLevel());
Duration finalDuration = finalPrioDuration.getLeft();
if (finalDuration != null && !MAX_DURATION.equals(finalDuration)) {
// if we have a due date we need to go x days backwards,
// else we take the planned date (or now as fallback) and add x Days
if (task.getDue() != null) {
long days = converter.convertWorkingDaysToDays(task.getDue(), -finalDuration.toDays());
// days < 0 -> so we ne need to add, not substract
Instant planned = task.getDue().plus(Duration.ofDays(days));
if (task.getPlanned() != null && !task.getPlanned().equals(planned)) {
throw new InvalidArgumentException(
"Cannot create a task with given planned "
+ "and due date not matching the service level");
}
task.setPlanned(planned);
} else {
task.setPlanned(task.getPlanned() == null ? now : task.getPlanned());
long days = converter.convertWorkingDaysToDays(task.getPlanned(), finalDuration.toDays());
Instant due = task.getPlanned().plus(Duration.ofDays(days));
task.setDue(due);
}
}
task.setPriority(finalPrioDuration.getRight());
if (task.getPlanned() == null && (classification == null || task.getDue() == null)) {
task.setPlanned(now);
}
if (classification != null) {
task = serviceLevelHandler.updatePrioPlannedDueOfTask(task, null, false);
}
if (task.getName() == null && classification != null) {
@ -1249,19 +1190,7 @@ public class TaskServiceImpl implements TaskService {
if (task.getDescription() == null && classification != null) {
task.setDescription(classification.getDescription());
}
// insert Attachments if needed
List<Attachment> attachments = task.getAttachments();
if (attachments != null) {
for (Attachment attachment : attachments) {
AttachmentImpl attachmentImpl = (AttachmentImpl) attachment;
attachmentImpl.setId(IdGenerator.generateWithPrefix(ID_PREFIX_ATTACHMENT));
attachmentImpl.setTaskId(task.getId());
attachmentImpl.setCreated(now);
attachmentImpl.setModified(now);
attachmentMapper.insert(attachmentImpl);
}
}
attachmentHandler.insertNewAttachmentsOnTaskCreation(task, now);
LOGGER.debug("exit from standardSettings()");
}
@ -1632,86 +1561,11 @@ public class TaskServiceImpl implements TaskService {
.list();
}
private void validateObjectReference(ObjectReference objRef, String objRefType, String objName)
throws InvalidArgumentException {
LOGGER.debug("entry to validateObjectReference()");
// check that all values in the ObjectReference are set correctly
if (objRef == null) {
throw new InvalidArgumentException(objRefType + " of " + objName + " must not be null");
} else if (objRef.getCompany() == null || objRef.getCompany().length() == 0) {
throw new InvalidArgumentException(
"Company of " + objRefType + " of " + objName + MUST_NOT_BE_EMPTY);
} else if (objRef.getSystem() == null || objRef.getSystem().length() == 0) {
throw new InvalidArgumentException(
"System of " + objRefType + " of " + objName + MUST_NOT_BE_EMPTY);
} else if (objRef.getSystemInstance() == null || objRef.getSystemInstance().length() == 0) {
throw new InvalidArgumentException(
"SystemInstance of " + objRefType + " of " + objName + MUST_NOT_BE_EMPTY);
} else if (objRef.getType() == null || objRef.getType().length() == 0) {
throw new InvalidArgumentException(
"Type of " + objRefType + " of " + objName + MUST_NOT_BE_EMPTY);
} else if (objRef.getValue() == null || objRef.getValue().length() == 0) {
throw new InvalidArgumentException(
"Value of" + objRefType + " of " + objName + MUST_NOT_BE_EMPTY);
}
LOGGER.debug("exit from validateObjectReference()");
}
private PrioDurationHolder handleAttachments(TaskImpl task) throws InvalidArgumentException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("entry to handleAttachments(task = {})", task);
}
List<Attachment> attachments = task.getAttachments();
if (attachments == null || attachments.isEmpty()) {
return new PrioDurationHolder(null, Integer.MIN_VALUE);
}
PrioDurationHolder actualPrioDuration = new PrioDurationHolder(MAX_DURATION, Integer.MIN_VALUE);
Iterator<Attachment> i = attachments.iterator();
while (i.hasNext()) {
Attachment attachment = i.next();
if (attachment == null) {
i.remove();
} else {
actualPrioDuration = handleNonNullAttachment(actualPrioDuration, attachment);
}
}
if (MAX_DURATION.equals(actualPrioDuration.getLeft())) {
actualPrioDuration = new PrioDurationHolder(null, actualPrioDuration.getRight());
}
LOGGER.debug("exit from handleAttachments(), returning {}", actualPrioDuration);
return actualPrioDuration;
}
private PrioDurationHolder handleNonNullAttachment(
PrioDurationHolder actualPrioDuration, Attachment attachment)
throws InvalidArgumentException {
ObjectReference objRef = attachment.getObjectReference();
validateObjectReference(objRef, "ObjectReference", "Attachment");
if (attachment.getClassificationSummary() == null) {
throw new InvalidArgumentException(
"Classification of attachment " + attachment + " must not be null");
} else {
ClassificationSummary classificationSummary = attachment.getClassificationSummary();
if (classificationSummary != null) {
actualPrioDuration =
getNewPrioDuration(
actualPrioDuration,
classificationSummary.getPriority(),
classificationSummary.getServiceLevel());
}
}
return actualPrioDuration;
}
private void standardUpdateActions(
TaskImpl oldTaskImpl, TaskImpl newTaskImpl, PrioDurationHolder prioDurationFromAttachments)
throws InvalidArgumentException, ConcurrencyException, ClassificationNotFoundException,
InvalidStateException {
validateObjectReference(newTaskImpl.getPrimaryObjRef(), "primary ObjectReference", TASK);
private void standardUpdateActions(TaskImpl oldTaskImpl, TaskImpl newTaskImpl)
throws InvalidArgumentException, ConcurrencyException, InvalidStateException,
AttachmentPersistenceException, ClassificationNotFoundException {
attachmentHandler.validateObjectReference(
newTaskImpl.getPrimaryObjRef(), "primary ObjectReference", TASK);
// TODO: not safe to rely only on different timestamps.
// With fast execution below 1ms there will be no concurrencyException
if (oldTaskImpl.getModified() != null
@ -1739,6 +1593,16 @@ public class TaskServiceImpl implements TaskService {
newTaskImpl.setPlanned(oldTaskImpl.getPlanned());
}
if (newTaskImpl.getClassificationSummary() == null) {
newTaskImpl.setClassificationSummary(oldTaskImpl.getClassificationSummary());
}
attachmentHandler.insertAndDeleteAttachmentsOnTaskUpdate(newTaskImpl, oldTaskImpl);
updateClassificationSummary(newTaskImpl, oldTaskImpl);
newTaskImpl = serviceLevelHandler.updatePrioPlannedDueOfTask(newTaskImpl, oldTaskImpl, false);
// if no business process id is provided, use the id of the old task.
if (newTaskImpl.getBusinessProcessId() == null) {
newTaskImpl.setBusinessProcessId(oldTaskImpl.getBusinessProcessId());
@ -1751,393 +1615,24 @@ public class TaskServiceImpl implements TaskService {
String.format(TASK_WITH_ID_IS_NOT_READY, oldTaskImpl.getId(), oldTaskImpl.getState()));
}
updateClassificationRelatedProperties(oldTaskImpl, newTaskImpl, prioDurationFromAttachments);
newTaskImpl.setModified(Instant.now());
}
private void updateClassificationRelatedProperties(
TaskImpl oldTaskImpl, TaskImpl newTaskImpl, PrioDurationHolder prioDurationFromAttachments)
private void updateClassificationSummary(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
throws ClassificationNotFoundException {
LOGGER.debug("entry to updateClassificationRelatedProperties()");
// insert Classification specifications if Classification is given.
ClassificationSummary oldClassificationSummary = oldTaskImpl.getClassificationSummary();
ClassificationSummary newClassificationSummary = newTaskImpl.getClassificationSummary();
if (newClassificationSummary == null) {
newClassificationSummary = oldClassificationSummary;
}
if (newClassificationSummary
== null) { // newClassification is null -> take prio and duration from attachments
updateTaskPrioDurationFromAttachments(newTaskImpl, prioDurationFromAttachments);
} else {
updateTaskPrioDurationFromClassification(
newTaskImpl,
prioDurationFromAttachments,
oldClassificationSummary,
newClassificationSummary);
}
LOGGER.debug("exit from updateClassificationRelatedProperties()");
}
private void updateTaskPrioDurationFromClassification(
TaskImpl newTaskImpl,
PrioDurationHolder prioDurationFromAttachments,
ClassificationSummary oldClassificationSummary,
ClassificationSummary newClassificationSummary)
throws ClassificationNotFoundException {
LOGGER.debug("entry to updateTaskPrioDurationFromClassification()");
Classification newClassification = null;
if (!oldClassificationSummary.getKey().equals(newClassificationSummary.getKey())) {
newClassification =
Classification newClassification =
this.classificationService.getClassification(
newClassificationSummary.getKey(), newTaskImpl.getWorkbasketSummary().getDomain());
newClassificationSummary = newClassification.asSummary();
newTaskImpl.setClassificationSummary(newClassificationSummary);
}
Duration minDuration = calculateDuration(prioDurationFromAttachments, newClassificationSummary);
if (minDuration != null) {
long days =
converter.convertWorkingDaysToDays(newTaskImpl.getPlanned(), minDuration.toDays());
Instant due = newTaskImpl.getPlanned().plus(Duration.ofDays(days));
newTaskImpl.setDue(due);
}
if (newTaskImpl.getName() == null) {
newTaskImpl.setName(newClassificationSummary.getName());
}
if (newTaskImpl.getDescription() == null && newClassification != null) {
newTaskImpl.setDescription(newClassification.getDescription());
}
int newPriority =
Math.max(newClassificationSummary.getPriority(), prioDurationFromAttachments.getRight());
newTaskImpl.setPriority(newPriority);
LOGGER.debug("exit from updateTaskPrioDurationFromClassification()");
}
private PrioDurationHolder handleAttachmentsOnTaskUpdate(
TaskImpl oldTaskImpl, TaskImpl newTaskImpl) throws AttachmentPersistenceException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to handleAttachmentsOnTaskUpdate(oldTaskImpl = {}, newTaskImpl = {})",
oldTaskImpl,
newTaskImpl);
}
PrioDurationHolder prioDuration = new PrioDurationHolder(MAX_DURATION, Integer.MIN_VALUE);
// Iterator for removing invalid current values directly. OldAttachments can be ignored.
Iterator<Attachment> i = newTaskImpl.getAttachments().iterator();
while (i.hasNext()) {
Attachment attachment = i.next();
if (attachment != null) {
prioDuration =
handlePrioDurationOfOneAttachmentOnTaskUpdate(
oldTaskImpl, newTaskImpl, prioDuration, attachment);
} else {
i.remove();
}
}
// DELETE, when an Attachment was only represented before
deleteAttachmentOnTaskUpdate(oldTaskImpl, newTaskImpl);
if (MAX_DURATION.equals(prioDuration.getLeft())) {
prioDuration = new PrioDurationHolder(null, prioDuration.getRight());
}
LOGGER.debug("exit from handleAttachmentsOnTaskUpdate()");
return prioDuration;
}
private void deleteAttachmentOnTaskUpdate(TaskImpl oldTaskImpl, TaskImpl newTaskImpl) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"entry to deleteAttachmentOnTaskUpdate(oldTaskImpl = {}, newTaskImpl = {})",
oldTaskImpl,
newTaskImpl);
}
for (Attachment oldAttachment : oldTaskImpl.getAttachments()) {
if (oldAttachment != null) {
boolean isRepresented = false;
for (Attachment newAttachment : newTaskImpl.getAttachments()) {
if (newAttachment != null && oldAttachment.getId().equals(newAttachment.getId())) {
isRepresented = true;
break;
}
}
if (!isRepresented) {
attachmentMapper.deleteAttachment(oldAttachment.getId());
LOGGER.debug(
"TaskService.updateTask() for TaskId={} DELETED an Attachment={}.",
newTaskImpl.getId(),
oldAttachment);
}
}
}
LOGGER.debug("exit from deleteAttachmentOnTaskUpdate()");
}
private PrioDurationHolder handlePrioDurationOfOneAttachmentOnTaskUpdate(
TaskImpl oldTaskImpl,
TaskImpl newTaskImpl,
PrioDurationHolder prioDuration,
Attachment attachment)
throws AttachmentPersistenceException {
LOGGER.debug("entry to handlePrioDurationOfOneAttachmentOnTaskUpdate()");
boolean wasAlreadyPresent = false;
if (attachment.getId() != null) {
for (Attachment oldAttachment : oldTaskImpl.getAttachments()) {
if (oldAttachment != null && attachment.getId().equals(oldAttachment.getId())) {
wasAlreadyPresent = true;
if (!attachment.equals(oldAttachment)) {
prioDuration =
handlePrioDurationOfOneNewAttachmentOnTaskUpdate(
newTaskImpl, prioDuration, attachment);
break;
}
}
}
}
// ADD, when ID not set or not found in elements
if (!wasAlreadyPresent) {
prioDuration = handleNewAttachmentOnTaskUpdate(newTaskImpl, prioDuration, attachment);
}
LOGGER.debug(
"exit from handlePrioDurationOfOneAttachmentOnTaskUpdate(), returning {}", prioDuration);
return prioDuration;
}
private PrioDurationHolder handlePrioDurationOfOneNewAttachmentOnTaskUpdate(
TaskImpl newTaskImpl, PrioDurationHolder prioDuration, Attachment attachment) {
LOGGER.debug("entry to handlePrioDurationOfOneNewAttachmentOnTaskUpdate()");
AttachmentImpl temp = (AttachmentImpl) attachment;
ClassificationSummary classification = attachment.getClassificationSummary();
if (classification != null) {
prioDuration =
getNewPrioDuration(
prioDuration, classification.getPriority(), classification.getServiceLevel());
}
temp.setModified(Instant.now());
attachmentMapper.update(temp);
LOGGER.debug(
"TaskService.updateTask() for TaskId={} UPDATED an Attachment={}.",
newTaskImpl.getId(),
attachment);
LOGGER.debug(
"exit from handlePrioDurationOfOneNewAttachmentOnTaskUpdate(), returning {}", prioDuration);
return prioDuration;
}
private PrioDurationHolder handleNewAttachmentOnTaskUpdate(
TaskImpl newTaskImpl, PrioDurationHolder prioDuration, Attachment attachment)
throws AttachmentPersistenceException {
LOGGER.debug("entry to handleNewAttachmentOnTaskUpdate()");
AttachmentImpl attachmentImpl = (AttachmentImpl) attachment;
initAttachment(attachmentImpl, newTaskImpl);
ClassificationSummary classification = attachment.getClassificationSummary();
if (classification != null) {
prioDuration =
getNewPrioDuration(
prioDuration, classification.getPriority(), classification.getServiceLevel());
}
try {
attachmentMapper.insert(attachmentImpl);
LOGGER.debug(
"TaskService.updateTask() for TaskId={} INSERTED an Attachment={}.",
newTaskImpl.getId(),
attachmentImpl);
} catch (PersistenceException e) {
throw new AttachmentPersistenceException(
"Cannot insert the Attachement "
+ attachmentImpl.getId()
+ " for Task "
+ newTaskImpl.getId()
+ " because it already exists.",
e.getCause());
}
LOGGER.debug("exit from handleNewAttachmentOnTaskUpdate(), returning {}", prioDuration);
return prioDuration;
}
private PrioDurationHolder handleAttachmentsOnClassificationUpdate(Task task) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("entry to handleAttachmentsOnClassificationUpdate(task = {})", task);
}
PrioDurationHolder prioDuration = new PrioDurationHolder(MAX_DURATION, Integer.MIN_VALUE);
// Iterator for removing invalid current values directly. OldAttachments can be ignored.
for (Attachment attachment : task.getAttachments()) {
if (attachment != null) {
ClassificationSummary classification = attachment.getClassificationSummary();
if (classification != null) {
prioDuration =
getNewPrioDuration(
prioDuration, classification.getPriority(), classification.getServiceLevel());
}
}
}
if (MAX_DURATION.equals(prioDuration.getLeft())) {
prioDuration = new PrioDurationHolder(null, prioDuration.getRight());
}
LOGGER.debug("exit from handleAttachmentsOnClassificationUpdate(), returning {}", prioDuration);
return prioDuration;
}
private PrioDurationHolder getNewPrioDuration(
PrioDurationHolder prioDurationHolder,
int prioFromClassification,
String serviceLevelFromClassification) {
LOGGER.debug(
"entry to getNewPrioDuration(prioDurationHolder = {}, prioFromClassification = {}, "
+ "serviceLevelFromClassification = {})",
prioDurationHolder,
prioFromClassification,
serviceLevelFromClassification);
Duration minDuration = prioDurationHolder.getLeft();
int maxPrio = prioDurationHolder.getRight();
if (serviceLevelFromClassification != null) {
Duration currentDuration = Duration.parse(serviceLevelFromClassification);
if (prioDurationHolder.getLeft() != null) {
if (prioDurationHolder.getLeft().compareTo(currentDuration) > 0) {
minDuration = currentDuration;
}
} else {
minDuration = currentDuration;
}
}
if (prioFromClassification > maxPrio) {
maxPrio = prioFromClassification;
}
PrioDurationHolder pair = new PrioDurationHolder(minDuration, maxPrio);
LOGGER.debug("exit from getNewPrioDuration(), returning {}", pair);
return pair;
}
private void initAttachment(AttachmentImpl attachment, Task newTask) {
LOGGER.debug("entry to initAttachment()");
if (attachment.getId() == null) {
attachment.setId(IdGenerator.generateWithPrefix(ID_PREFIX_ATTACHMENT));
}
if (attachment.getCreated() == null) {
attachment.setCreated(Instant.now());
}
if (attachment.getModified() == null) {
attachment.setModified(attachment.getCreated());
}
if (attachment.getTaskId() == null) {
attachment.setTaskId(newTask.getId());
}
LOGGER.debug("exit from initAttachment()");
}
private void updatePrioDueDateOnClassificationUpdate(
TaskImpl task, PrioDurationHolder prioDurationFromAttachments) {
LOGGER.debug("entry to updatePrioDueDateOnClassificationUpdate()");
ClassificationSummary classificationSummary = task.getClassificationSummary();
if (classificationSummary
== null) { // classification is null -> take prio and duration from attachments
updateTaskPrioDurationFromAttachments(task, prioDurationFromAttachments);
} else {
updateTaskPrioDurationFromClassificationAndAttachments(
task, prioDurationFromAttachments, classificationSummary);
}
LOGGER.debug("exit from updatePrioDueDateOnClassificationUpdate()");
}
private void updateTaskPrioDurationFromClassificationAndAttachments(
TaskImpl task,
PrioDurationHolder prioDurationFromAttachments,
ClassificationSummary classificationSummary) {
LOGGER.debug("entry to updateTaskPrioDurationFromClassificationAndAttachments()");
Duration minDuration = calculateDuration(prioDurationFromAttachments, classificationSummary);
if (minDuration != null) {
long days = converter.convertWorkingDaysToDays(task.getPlanned(), minDuration.toDays());
Instant due = task.getPlanned().plus(Duration.ofDays(days));
task.setDue(due);
}
int newPriority =
Math.max(classificationSummary.getPriority(), prioDurationFromAttachments.getRight());
task.setPriority(newPriority);
LOGGER.debug("exit from updateTaskPrioDurationFromClassificationAndAttachments()");
}
private void updateTaskPrioDurationFromAttachments(
TaskImpl task, PrioDurationHolder prioDurationFromAttachments) {
LOGGER.debug("entry to updateTaskPrioDurationFromAttachments()");
if (prioDurationFromAttachments.getLeft() != null) {
long days =
converter.convertWorkingDaysToDays(
task.getPlanned(), prioDurationFromAttachments.getLeft().toDays());
Instant due = task.getPlanned().plus(Duration.ofDays(days));
task.setDue(due);
}
if (prioDurationFromAttachments.getRight() > Integer.MIN_VALUE) {
task.setPriority(prioDurationFromAttachments.getRight());
}
LOGGER.debug("exit from updateTaskPrioDurationFromAttachments()");
}
private List<Attachment> augmentAttachmentsByClassification(
List<AttachmentImpl> attachmentImpls, BulkOperationResults<String, Exception> bulkLog) {
LOGGER.debug("entry to augmentAttachmentsByClassification()");
List<Attachment> result = new ArrayList<>();
if (attachmentImpls == null || attachmentImpls.isEmpty()) {
return result;
}
List<ClassificationSummary> classifications =
classificationService
.createClassificationQuery()
.idIn(
attachmentImpls.stream()
.map(t -> t.getClassificationSummary().getId())
.distinct()
.toArray(String[]::new))
.list();
for (AttachmentImpl att : attachmentImpls) {
ClassificationSummary classificationSummary =
classifications.stream()
.filter(cl -> cl.getId().equals(att.getClassificationSummary().getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
String id = att.getClassificationSummary().getId();
bulkLog.addError(
att.getClassificationSummary().getId(),
new ClassificationNotFoundException(
id,
String.format(
"When processing task updates due to change "
+ "of classification, the classification with id %s was not found",
id)));
} else {
att.setClassificationSummary(classificationSummary);
result.add(att);
}
}
LOGGER.debug("exit from augmentAttachmentsByClassification()");
return result;
}
private void createTasksCompletedEvents(List<TaskSummary> taskSummaries) {
@ -2146,11 +1641,4 @@ public class TaskServiceImpl implements TaskService {
historyEventProducer.createEvent(
new CompletedEvent(task, CurrentUserContext.getUserid())));
}
private static class PrioDurationHolder extends Pair<Duration, Integer> {
PrioDurationHolder(Duration left, Integer right) {
super(left, right);
}
}
}

View File

@ -254,6 +254,10 @@ public class TaskImpl extends TaskSummaryImpl implements Task {
}
}
public String getClassificationId() {
return classificationSummary == null ? null : classificationSummary.getId();
}
protected boolean canEqual(Object other) {
return (other instanceof TaskImpl);
}

View File

@ -59,8 +59,8 @@ public class UpdateObjectsUseUtcTimeStampsAccTest extends AbstractAccTest {
Task task = taskService.getTask("TKI:000000000000000000000000000000000000");
TaskImpl ti = (TaskImpl) task;
task.setPlanned(Instant.now().plus(Duration.ofHours(17)));
task.setDue(Instant.now().plus(Duration.ofHours(27)));
ti.setCompleted(Instant.now().plus(Duration.ofHours(27)));
task.setDue(Instant.now().plus(Duration.ofHours(41)));
ti.setCompleted(Instant.now().plus(Duration.ofHours(40)));
TimeZone originalZone = TimeZone.getDefault();
Task updatedTask = taskService.updateTask(task);

View File

@ -10,6 +10,8 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -28,6 +30,7 @@ import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary;
/** Acceptance test for all "create task" scenarios. */
@ExtendWith(JaasExtension.class)
@ -256,4 +259,39 @@ public class SetPlannedAccTest extends AbstractAccTest {
taskanaEngine.getTaskService().setPlannedPropertyOfTasks(planned, new ArrayList<>());
assertThat(results.containsErrors()).isFalse();
}
@WithAccessId(
userName = "admin",
groupNames = {"group_2"})
@Test
void testSetPlannedPropertyOnAllTasks()
throws NotAuthorizedException, TaskNotFoundException, SQLException {
Instant planned = getInstant("2020-05-03T07:00:00");
List<TaskSummary> allTasks = taskService.createTaskQuery().list();
// Now update each task with updateTask() and new planned
final List<TaskSummary> individuallyUpdatedTasks = new ArrayList<>();
allTasks.forEach(t -> individuallyUpdatedTasks.add(getUpdatedTaskSummary(t, planned)));
// reset DB and do the same with bulk update
resetDb(false);
List<String> taskIds = allTasks.stream().map(TaskSummary::getId).collect(Collectors.toList());
BulkOperationResults<String, TaskanaException> bulkLog =
taskService.setPlannedPropertyOfTasks(planned, taskIds);
// check that there was no error and compare the result of the 2 operations
assertThat(bulkLog.containsErrors()).isFalse();
Map<String, Instant> bulkUpdatedTaskMap =
taskService.createTaskQuery().list().stream()
.collect(Collectors.toMap(TaskSummary::getId, TaskSummary::getDue));
individuallyUpdatedTasks.forEach(
t -> assertThat(t.getDue().equals(bulkUpdatedTaskMap.get(t.getId()))));
}
private TaskSummary getUpdatedTaskSummary(TaskSummary t, Instant planned) {
try {
Task task = taskService.getTask(t.getId());
task.setPlanned(planned);
return taskService.updateTask(task);
} catch (Exception e) {
return null;
}
}
}

View File

@ -112,8 +112,8 @@ class UpdateTaskAccTest extends AbstractAccTest {
@Test
void testThrowsExceptionIfTaskHasAlreadyBeenUpdated()
throws NotAuthorizedException, InvalidArgumentException, ClassificationNotFoundException,
TaskNotFoundException, ConcurrencyException, AttachmentPersistenceException,
InvalidStateException, InterruptedException {
TaskNotFoundException, ConcurrencyException, AttachmentPersistenceException,
InvalidStateException, InterruptedException {
TaskService taskService = taskanaEngine.getTaskService();
Task task = taskService.getTask("TKI:000000000000000000000000000000000000");
@ -336,4 +336,21 @@ class UpdateTaskAccTest extends AbstractAccTest {
assertThat(retrievedUpdatedTask.getCallbackInfo()).isEqualTo(callbackInfo);
}
@WithAccessId(
userName = "user_1_2",
groupNames = {"group_1"})
@Test
void testUpdatePlannedAndDue()
throws NotAuthorizedException, TaskNotFoundException, ClassificationNotFoundException,
InvalidArgumentException, InvalidStateException, ConcurrencyException,
AttachmentPersistenceException {
TaskService taskService = taskanaEngine.getTaskService();
Task task = taskService.getTask("TKI:000000000000000000000000000000000030");
task.setPlanned(Instant.now());
task.setPlanned(getInstant("2020-04-21T07:00:00"));
task.setDue(getInstant("2020-04-21T10:00:00"));
assertThatThrownBy(() -> taskService.updateTask(task))
.isInstanceOf(InvalidArgumentException.class);
}
}