TSK-587 Improve Job Support

This commit is contained in:
BerndBreier 2018-06-21 15:29:49 +02:00 committed by Holger Hagen
parent b1cc34a71c
commit a64fb47aa0
10 changed files with 108 additions and 33 deletions

View File

@ -44,8 +44,9 @@ public class TaskanaEngineConfiguration {
private static final String H2_DRIVER = "org.h2.Driver"; private static final String H2_DRIVER = "org.h2.Driver";
private static final String TASKANA_PROPERTIES = "/taskana.properties"; private static final String TASKANA_PROPERTIES = "/taskana.properties";
private static final String TASKANA_ROLES_SEPARATOR = "|"; private static final String TASKANA_ROLES_SEPARATOR = "|";
private static final String TASKANA_JOB_TASK_UPDATES_PER_TRANSACTION = "taskana.job.max.task.updates.per.transaction"; private static final String TASKANA_JOB_TASK_UPDATES_PER_TRANSACTION = "taskana.jobs.taskupdate.batchSize";
private static final String TASKANA_JOB_RETRIES_FOR_FAILED_TASK_UPDATES = "taskana.job.max.retries.for.failed.task.updates"; private static final String TASKANA_JOB_RETRIES_FOR_FAILED_TASK_UPDATES = "taskana.jobs.taskupdate.maxRetries";
private static final String TASKANA_DOMAINS_PROPERTY = "taskana.domains"; private static final String TASKANA_DOMAINS_PROPERTY = "taskana.domains";
private static final String TASKANA_CLASSIFICATION_TYPES_PROPERTY = "taskana.classification.types"; private static final String TASKANA_CLASSIFICATION_TYPES_PROPERTY = "taskana.classification.types";
private static final String TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY = "taskana.classification.categories"; private static final String TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY = "taskana.classification.categories";

View File

@ -79,7 +79,6 @@ public class ClassificationChangedJobExecutor implements SingleJobExecutor {
if (!taskIdBatch.isEmpty()) { if (!taskIdBatch.isEmpty()) {
String taskIds = String.join(",", taskIdBatch); String taskIds = String.join(",", taskIdBatch);
args.put(ClassificationChangedJobExecutor.TASKIDS, taskIds); args.put(ClassificationChangedJobExecutor.TASKIDS, taskIds);
args.put(CLASSIFICATION_ID, classificationId);
args.put(PRIORITY_CHANGED, new Boolean(priorityChanged).toString()); args.put(PRIORITY_CHANGED, new Boolean(priorityChanged).toString());
args.put(SERVICE_LEVEL_CHANGED, new Boolean(serviceLevelChanged).toString()); args.put(SERVICE_LEVEL_CHANGED, new Boolean(serviceLevelChanged).toString());
Job job = new Job(); Job job = new Job();

View File

@ -1187,15 +1187,7 @@ public class TaskServiceImpl implements TaskService {
} }
if (newClassificationSummary == null) { // newClassification is null -> take prio and duration from attachments if (newClassificationSummary == null) { // newClassification is null -> take prio and duration from attachments
if (prioDurationFromAttachments.getDuration() != null) { updateTaskPrioDurationFromAttachments(newTaskImpl, prioDurationFromAttachments);
long days = converter.convertWorkingDaysToDays(newTaskImpl.getPlanned(),
prioDurationFromAttachments.getDuration().toDays());
Instant due = newTaskImpl.getPlanned().plus(Duration.ofDays(days));
newTaskImpl.setDue(due);
}
if (prioDurationFromAttachments.getPrio() > Integer.MIN_VALUE) {
newTaskImpl.setPriority(prioDurationFromAttachments.getPrio());
}
} else { } else {
Classification newClassification = null; Classification newClassification = null;
if (!oldClassificationSummary.getKey().equals(newClassificationSummary.getKey())) { if (!oldClassificationSummary.getKey().equals(newClassificationSummary.getKey())) {
@ -1394,14 +1386,14 @@ public class TaskServiceImpl implements TaskService {
} }
} }
BulkOperationResults<String, Exception> classificationChanged(String taskId, String classificationId) BulkOperationResults<String, Exception> refreshPriorityAndDueDate(String taskId)
throws ClassificationNotFoundException { throws ClassificationNotFoundException {
LOGGER.debug("entry to classificationChanged(taskId = {} , classificationId = {} )", taskId, classificationId); LOGGER.debug("entry to classificationChanged(taskId = {})", taskId);
TaskImpl task = null; TaskImpl task = null;
BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>(); BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>();
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
if (taskId == null || taskId.isEmpty() || classificationId == null || classificationId.isEmpty()) { if (taskId == null || taskId.isEmpty()) {
return bulkLog; return bulkLog;
} }
@ -1414,11 +1406,12 @@ public class TaskServiceImpl implements TaskService {
List<Attachment> attachments = augmentAttachmentsByClassification(attachmentImpls, bulkLog); List<Attachment> attachments = augmentAttachmentsByClassification(attachmentImpls, bulkLog);
task.setAttachments(attachments); task.setAttachments(attachments);
Classification classification = classificationService.getClassification(classificationId); Classification classification = classificationService
.getClassification(task.getClassificationSummary().getId());
task.setClassificationSummary(classification.asSummary()); task.setClassificationSummary(classification.asSummary());
PrioDurationHolder prioDurationFromAttachments = handleAttachmentsOnClassificationUpdate(task); PrioDurationHolder prioDurationFromAttachments = handleAttachmentsOnClassificationUpdate(task);
updateClassificationRelatedProperties(task, task, prioDurationFromAttachments); updatePrioDueDateOnClassificationUpdate(task, prioDurationFromAttachments);
task.setModified(Instant.now()); task.setModified(Instant.now());
taskMapper.update(task); taskMapper.update(task);
@ -1430,6 +1423,54 @@ public class TaskServiceImpl implements TaskService {
} }
private void updatePrioDueDateOnClassificationUpdate(TaskImpl task,
PrioDurationHolder prioDurationFromAttachments) {
ClassificationSummary classificationSummary = task.getClassificationSummary();
if (classificationSummary == null) { // classification is null -> take prio and duration from attachments
updateTaskPrioDurationFromAttachments(task, prioDurationFromAttachments);
} else {
updateTaskPrioDurationFromClassificationAndAttachments(task, prioDurationFromAttachments,
classificationSummary);
}
}
private void updateTaskPrioDurationFromClassificationAndAttachments(TaskImpl task,
PrioDurationHolder prioDurationFromAttachments, ClassificationSummary classificationSummary) {
if (classificationSummary.getServiceLevel() != null) {
Duration durationFromClassification = Duration.parse(classificationSummary.getServiceLevel());
Duration minDuration = prioDurationFromAttachments.getDuration();
if (minDuration != null) {
if (minDuration.compareTo(durationFromClassification) > 0) {
minDuration = durationFromClassification;
}
} else {
minDuration = durationFromClassification;
}
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.getPrio());
task.setPriority(newPriority);
}
private void updateTaskPrioDurationFromAttachments(TaskImpl task, PrioDurationHolder prioDurationFromAttachments) {
if (prioDurationFromAttachments.getDuration() != null) {
long days = converter.convertWorkingDaysToDays(task.getPlanned(),
prioDurationFromAttachments.getDuration().toDays());
Instant due = task.getPlanned().plus(Duration.ofDays(days));
task.setDue(due);
}
if (prioDurationFromAttachments.getPrio() > Integer.MIN_VALUE) {
task.setPriority(prioDurationFromAttachments.getPrio());
}
}
private List<Attachment> augmentAttachmentsByClassification(List<AttachmentImpl> attachmentImpls, private List<Attachment> augmentAttachmentsByClassification(List<AttachmentImpl> attachmentImpls,
BulkOperationResults<String, Exception> bulkLog) { BulkOperationResults<String, Exception> bulkLog) {
List<Attachment> result = new ArrayList<>(); List<Attachment> result = new ArrayList<>();

View File

@ -20,7 +20,6 @@ public class TaskUpdateJobExecutor implements SingleJobExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskUpdateJobExecutor.class); private static final Logger LOGGER = LoggerFactory.getLogger(TaskUpdateJobExecutor.class);
private TaskanaEngineImpl taskanaEngine; private TaskanaEngineImpl taskanaEngine;
private String classificationId;
private List<String> affectedTaskIds; private List<String> affectedTaskIds;
public TaskUpdateJobExecutor() { public TaskUpdateJobExecutor() {
@ -33,7 +32,6 @@ public class TaskUpdateJobExecutor implements SingleJobExecutor {
String taskIdsString = args.get(TASKIDS); String taskIdsString = args.get(TASKIDS);
affectedTaskIds = Arrays.asList(taskIdsString.split(",")); affectedTaskIds = Arrays.asList(taskIdsString.split(","));
classificationId = args.get(CLASSIFICATION_ID);
BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>(); BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>();
bulkLog.addAllErrors(handleAffectedTasks(job)); bulkLog.addAllErrors(handleAffectedTasks(job));
@ -49,7 +47,7 @@ public class TaskUpdateJobExecutor implements SingleJobExecutor {
BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>(); BulkOperationResults<String, Exception> bulkLog = new BulkOperationResults<>();
for (String taskId : affectedTaskIds) { for (String taskId : affectedTaskIds) {
try { try {
bulkLog.addAllErrors(taskService.classificationChanged(taskId, classificationId)); bulkLog.addAllErrors(taskService.refreshPriorityAndDueDate(taskId));
} catch (Exception e) { } catch (Exception e) {
bulkLog.addError(taskId, e); bulkLog.addError(taskId, e);
} }

View File

@ -255,8 +255,14 @@ public class UpdateClassificationAccTest extends AbstractAccTest {
assertTrue(task.getModified().isAfter(before)); assertTrue(task.getModified().isAfter(before));
assertTrue(task.getPriority() == 1000); assertTrue(task.getPriority() == 1000);
long calendarDays = converter.convertWorkingDaysToDays(task.getPlanned(), 15); long calendarDays = converter.convertWorkingDaysToDays(task.getPlanned(), 15);
// the following excluded tasks are affected via attachments. The task or an attachment still has a service
// level below 15 days
// therefore, we cannot compare the due date of these tasks to an SL of 15 days.
if (!taskId.equals("TKI:000000000000000000000000000000000008") if (!taskId.equals("TKI:000000000000000000000000000000000008")
&& !taskId.equals("TKI:000000000000000000000000000000000053")) { && !taskId.equals("TKI:000000000000000000000000000000000000")
&& !taskId.equals("TKI:000000000000000000000000000000000053")
&& !taskId.equals("TKI:000000000000000000000000000000000054")
&& !taskId.equals("TKI:000000000000000000000000000000000055")) {
assertTrue(task.getDue().equals(task.getPlanned().plus(Duration.ofDays(calendarDays)))); assertTrue(task.getDue().equals(task.getPlanned().plus(Duration.ofDays(calendarDays))));
} }

View File

@ -8,5 +8,5 @@ taskana.domains= Domain_A , DOMAIN_B
taskana.classification.types= TASK , document taskana.classification.types= TASK , document
taskana.classification.categories= EXTERNAL , manual, autoMAtic ,Process taskana.classification.categories= EXTERNAL , manual, autoMAtic ,Process
taskana.job.max.task.updates.per.transaction=50 taskana.jobs.taskupdate.maxRetries=3
taskana.job.max.retries.for.failed.task.updates=3 taskana.jobs.taskupdate.batchSize=50

View File

@ -32,7 +32,7 @@ public class JobScheduler {
@Autowired @Autowired
TaskanaTransactionProvider<BulkOperationResults<String, Exception>> springTransactionProvider; TaskanaTransactionProvider<BulkOperationResults<String, Exception>> springTransactionProvider;
@Scheduled(fixedRate = 60000) @Scheduled(cron = "${taskana.jobscheduler.cron}")
public void triggerJobs() { public void triggerJobs() {
boolean otherJobActive = jobRunning.getAndSet(true); boolean otherJobActive = jobRunning.getAndSet(true);
if (!otherJobActive) { // only one job should be active at any time if (!otherJobActive) { // only one job should be active at any time

View File

@ -26,5 +26,7 @@ taskana.ldap.groupSearchFilterValue=groupOfUniqueNames
taskana.ldap.groupNameAttribute=cn taskana.ldap.groupNameAttribute=cn
taskana.ldap.minSearchForLength=3 taskana.ldap.minSearchForLength=3
taskana.ldap.maxNumberOfReturnedAccessIds=50 taskana.ldap.maxNumberOfReturnedAccessIds=50
####### JobScheduler cron expression that specifies when the JobSchedler runs
taskana.jobscheduler.cron=0 * * * * *
####### cache static resources properties ####### cache static resources properties
spring.resources.cache.cachecontrol.cache-private=true spring.resources.cache.cachecontrol.cache-private=true

View File

@ -6,5 +6,5 @@ taskana.domains=DOMAIN_A,DOMAIN_B,DOMAIN_C
taskana.classification.types=TASK,DOCUMENT taskana.classification.types=TASK,DOCUMENT
taskana.classification.categories= EXTERNAL , manual, autoMAtic ,Process taskana.classification.categories= EXTERNAL , manual, autoMAtic ,Process
taskana.job.max.task.updates.per.transaction=5 taskana.jobs.taskupdate.maxRetries=3
taskana.job.max.retries.for.failed.task.updates=3 taskana.jobs.taskupdate.batchSize=50

View File

@ -13,6 +13,7 @@ import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import org.json.JSONObject; import org.json.JSONObject;
@ -25,8 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.hal.Jackson2HalModule; import org.springframework.hateoas.hal.Jackson2HalModule;
@ -38,6 +39,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -48,7 +51,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import pro.taskana.Classification; import pro.taskana.Classification;
import pro.taskana.Task; import pro.taskana.Task;
import pro.taskana.exceptions.InvalidArgumentException; import pro.taskana.exceptions.InvalidArgumentException;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.rest.resource.ClassificationResource; import pro.taskana.rest.resource.ClassificationResource;
import pro.taskana.rest.resource.ClassificationSummaryResource; import pro.taskana.rest.resource.ClassificationSummaryResource;
import pro.taskana.rest.resource.TaskResource; import pro.taskana.rest.resource.TaskResource;
@ -56,7 +58,8 @@ import pro.taskana.rest.resource.assembler.ClassificationResourceAssembler;
import pro.taskana.rest.resource.assembler.TaskResourceAssembler; import pro.taskana.rest.resource.assembler.TaskResourceAssembler;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"}) @SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT,
properties = {"devMode=true"})
public class ClassificationControllerIntTest { public class ClassificationControllerIntTest {
@Autowired @Autowired
@ -65,6 +68,9 @@ public class ClassificationControllerIntTest {
@Autowired @Autowired
private TaskResourceAssembler taskResourceAssembler; private TaskResourceAssembler taskResourceAssembler;
@Autowired
Environment env;
private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationControllerIntTest.class); private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationControllerIntTest.class);
String server = "http://127.0.0.1:"; String server = "http://127.0.0.1:";
RestTemplate template; RestTemplate template;
@ -237,7 +243,7 @@ public class ClassificationControllerIntTest {
@Test @Test
public void testUpdateClassificationPrioServiceLevel() public void testUpdateClassificationPrioServiceLevel()
throws IOException, InterruptedException, NotAuthorizedException, InvalidArgumentException { throws IOException, InterruptedException, InvalidArgumentException {
// 1st step: get old classification : // 1st step: get old classification :
Instant before = Instant.now(); Instant before = Instant.now();
@ -273,9 +279,31 @@ public class ClassificationControllerIntTest {
assertEquals(200, con.getResponseCode()); assertEquals(200, con.getResponseCode());
con.disconnect(); con.disconnect();
// wait a minute to give JobScheduler a chance to run // wait taskana.jobscheduler.delay + 5 seconds to give JobScheduler a chance to run
LOGGER.info("About to sleep for 70 seconds to give JobScheduler a chance to process the classification change"); String cron = env.getProperty("taskana.jobscheduler.cron");
Thread.sleep(70000); CronTrigger trigger = new CronTrigger(cron);
TriggerContext context = new TriggerContext() {
@Override
public Date lastScheduledExecutionTime() {
return null;
}
@Override
public Date lastActualExecutionTime() {
return null;
}
@Override
public Date lastCompletionTime() {
return null;
}
};
long delay = trigger.nextExecutionTime(context).getTime() - (new Date()).getTime() + 2000;
LOGGER.info("About to sleep for " + delay / 1000
+ " seconds to give JobScheduler a chance to process the classification change");
Thread.sleep(delay);
LOGGER.info("Sleeping ended. Continuing .... "); LOGGER.info("Sleeping ended. Continuing .... ");
// verify the classification modified timestamp is after 'before' // verify the classification modified timestamp is after 'before'