diff --git a/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java b/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java index 4ee187bff..4125eaafe 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java +++ b/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java @@ -51,6 +51,7 @@ public class TaskanaEngineConfiguration { private static final String TASKANA_JOB_TASK_CLEANUP_RUN_EVERY = "taskana.jobs.cleanup.runEvery"; private static final String TASKANA_JOB_TASK_CLEANUP_FIRST_RUN = "taskana.jobs.cleanup.firstRunAt"; private static final String TASKANA_JOB_TASK_CLEANUP_MINIMUM_AGE = "taskana.jobs.cleanup.minimumAge"; + private static final String TASKANA_JOB_TASK_CLEANUP_ALL_COMPLETED_SAME_PARENTE_BUSINESS = "taskana.jobs.cleanup.allCompletedSameParentBusiness"; private static final String TASKANA_DOMAINS_PROPERTY = "taskana.domains"; private static final String TASKANA_CLASSIFICATION_TYPES_PROPERTY = "taskana.classification.types"; @@ -88,6 +89,7 @@ public class TaskanaEngineConfiguration { private Instant taskCleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z"); private Duration taskCleanupJobRunEvery = Duration.parse("P1D"); private Duration taskCleanupJobMinimumAge = Duration.parse("P14D"); + private boolean taskCleanupJobAllCompletedSameParentBusiness = true; // List of configured domain names protected List domains = new ArrayList(); @@ -202,12 +204,24 @@ public class TaskanaEngineConfiguration { } } + String taskCleanupJobAllCompletedSameParentBusinessProperty = props.getProperty(TASKANA_JOB_TASK_CLEANUP_ALL_COMPLETED_SAME_PARENTE_BUSINESS); + if (taskCleanupJobAllCompletedSameParentBusinessProperty != null && !taskCleanupJobAllCompletedSameParentBusinessProperty.isEmpty()) { + try { + taskCleanupJobAllCompletedSameParentBusiness = Boolean.parseBoolean(taskCleanupJobAllCompletedSameParentBusinessProperty); + } catch (Exception e) { + LOGGER.warn("Could not parse taskCleanupJobAllCompletedSameParentBusinessProperty ({}). Using default. Exception: {} ", + taskCleanupJobAllCompletedSameParentBusinessProperty, e.getMessage()); + } + } + LOGGER.debug("Configured number of task updates per transaction: {}", jobBatchSize); LOGGER.debug("Number of retries of failed task updates: {}", maxNumberOfJobRetries); LOGGER.debug("TaskCleanupJob configuration: first run at {}", taskCleanupJobFirstRun); LOGGER.debug("TaskCleanupJob configuration: runs every {}", taskCleanupJobRunEvery); LOGGER.debug("TaskCleanupJob configuration: minimum age of tasks to be cleanup up is {}", - taskCleanupJobMinimumAge); + taskCleanupJobMinimumAge); + LOGGER.debug("TaskCleanupJob configuration: all completed task with the same parent business property id {}", + taskCleanupJobAllCompletedSameParentBusiness); } private void initDomains(Properties props) { @@ -467,6 +481,14 @@ public class TaskanaEngineConfiguration { return taskCleanupJobMinimumAge; } + public void setTaskCleanupJobAllCompletedSameParentBusiness(boolean taskCleanupJobAllCompletedSameParentBusiness) { + this.taskCleanupJobAllCompletedSameParentBusiness = taskCleanupJobAllCompletedSameParentBusiness; + } + + public boolean isTaskCleanupJobAllCompletedSameParentBusiness() { + return taskCleanupJobAllCompletedSameParentBusiness; + } + public String getSchemaName() { return schemaName; } diff --git a/lib/taskana-core/src/main/java/pro/taskana/jobs/TaskCleanupJob.java b/lib/taskana-core/src/main/java/pro/taskana/jobs/TaskCleanupJob.java index d3155df31..0688bbca6 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/jobs/TaskCleanupJob.java +++ b/lib/taskana-core/src/main/java/pro/taskana/jobs/TaskCleanupJob.java @@ -2,16 +2,16 @@ package pro.taskana.jobs; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import pro.taskana.BulkOperationResults; -import pro.taskana.TaskSummary; -import pro.taskana.TaskanaEngine; -import pro.taskana.TimeInterval; +import pro.taskana.*; import pro.taskana.exceptions.InvalidArgumentException; import pro.taskana.exceptions.TaskanaException; import pro.taskana.transaction.TaskanaTransactionProvider; @@ -23,11 +23,14 @@ public class TaskCleanupJob extends AbstractTaskanaJob { private static final Logger LOGGER = LoggerFactory.getLogger(TaskCleanupJob.class); + private static BaseQuery.SortDirection asc = BaseQuery.SortDirection.ASCENDING; + // Parameter private Instant firstRun; private Duration runEvery; private Duration minimumAge; private int batchSize; + private boolean allCompletedSameParentBusiness; public TaskCleanupJob(TaskanaEngine taskanaEngine, TaskanaTransactionProvider txProvider, ScheduledJob scheduledJob) { @@ -36,6 +39,7 @@ public class TaskCleanupJob extends AbstractTaskanaJob { runEvery = taskanaEngine.getConfiguration().getTaskCleanupJobRunEvery(); minimumAge = taskanaEngine.getConfiguration().getTaskCleanupJobMinimumAge(); batchSize = taskanaEngine.getConfiguration().getMaxNumberOfTaskUpdatesPerTransaction(); + allCompletedSameParentBusiness = taskanaEngine.getConfiguration().isTaskCleanupJobAllCompletedSameParentBusiness(); } @Override @@ -61,10 +65,40 @@ public class TaskCleanupJob extends AbstractTaskanaJob { } private List getTasksCompletedBefore(Instant untilDate) { - return taskanaEngineImpl.getTaskService() - .createTaskQuery() - .completedWithin(new TimeInterval(null, untilDate)) - .list(); + List taskList = taskanaEngineImpl.getTaskService() + .createTaskQuery() + .completedWithin(new TimeInterval(null, untilDate)) + .orderByBusinessProcessId(asc) + .list(); + + if (allCompletedSameParentBusiness) { + Map numberParentTasksShouldHave = new HashMap<>(); + Map countParentTask = new HashMap<>(); + for (TaskSummary task : taskList) { + numberParentTasksShouldHave.put(task.getParentBusinessProcessId(), taskanaEngineImpl.getTaskService().createTaskQuery().parentBusinessProcessIdIn(task.getParentBusinessProcessId()).count()); + countParentTask.merge(task.getParentBusinessProcessId(), 1L, Long::sum); + } + + List idsList = new ArrayList<>(); + numberParentTasksShouldHave.forEach((k, v) -> { + if (v.compareTo(countParentTask.get(k)) == 0) { + idsList.add(k); + } + }); + + if (idsList.isEmpty()) { + return new ArrayList<>(); + } + + String[] ids = new String[idsList.size()]; + ids = idsList.toArray(ids); + taskList = taskanaEngineImpl.getTaskService() + .createTaskQuery() + .parentBusinessProcessIdIn(ids) + .list(); + } + + return taskList; } private int deleteTasksTransactionally(List tasksToBeDeleted) { diff --git a/lib/taskana-core/src/test/java/acceptance/jobs/TaskCleanupJobAccTest.java b/lib/taskana-core/src/test/java/acceptance/jobs/TaskCleanupJobAccTest.java index c8d2aee68..f7266fe6e 100644 --- a/lib/taskana-core/src/test/java/acceptance/jobs/TaskCleanupJobAccTest.java +++ b/lib/taskana-core/src/test/java/acceptance/jobs/TaskCleanupJobAccTest.java @@ -10,6 +10,7 @@ import org.junit.runner.RunWith; import acceptance.AbstractAccTest; import pro.taskana.Task; import pro.taskana.TaskService; +import pro.taskana.TaskSummary; import pro.taskana.exceptions.ClassificationNotFoundException; import pro.taskana.exceptions.InvalidArgumentException; import pro.taskana.exceptions.InvalidOwnerException; @@ -22,6 +23,9 @@ import pro.taskana.jobs.TaskCleanupJob; import pro.taskana.security.JAASRunner; import pro.taskana.security.WithAccessId; +import java.util.ArrayList; +import java.util.List; + /** * Acceptance test for all "jobs tasks runner" scenarios. */ @@ -38,23 +42,45 @@ public class TaskCleanupJobAccTest extends AbstractAccTest { @WithAccessId(userName = "admin") @Test public void shouldCleanCompletedTasksUntilDate() throws Exception { - long totalTasksCount = taskService.createTaskQuery().count(); assertEquals(72, totalTasksCount); + taskanaEngine.getConfiguration().setTaskCleanupJobAllCompletedSameParentBusiness(false); + TaskCleanupJob job = new TaskCleanupJob(taskanaEngine, null, null); job.run(); totalTasksCount = taskService.createTaskQuery().count(); assertEquals(66, totalTasksCount); - } @WithAccessId(userName = "admin") @Test - public void shouldNotCleanCompleteTasksAfterDefinedDay() - throws Exception { + public void shouldCleanCompletedTasksUntilDateWithSameParentBussiness() throws Exception { + long totalTasksCount = taskService.createTaskQuery().count(); + assertEquals(67, totalTasksCount); + taskanaEngine.getConfiguration().setTaskCleanupJobAllCompletedSameParentBusiness(true); + + List tasks = taskService.createTaskQuery().parentBusinessProcessIdIn("DOC_0000000000000000006").list(); + List ids = new ArrayList<>(); + tasks.forEach(item -> { + if (item.getCompleted() == null) { + ids.add(item.getTaskId()); + } + }); + taskService.deleteTasks(ids); + + TaskCleanupJob job = new TaskCleanupJob(taskanaEngine, null, null); + job.run(); + + totalTasksCount = taskService.createTaskQuery().count(); + assertEquals(66, totalTasksCount); + } + + @WithAccessId(userName = "admin") + @Test + public void shouldNotCleanCompleteTasksAfterDefinedDay() throws Exception { Task createdTask = createAndCompleteTask(); TaskCleanupJob job = new TaskCleanupJob(taskanaEngine, null, null); @@ -62,7 +88,6 @@ public class TaskCleanupJobAccTest extends AbstractAccTest { Task completedCreatedTask = taskService.getTask(createdTask.getId()); assertNotNull(completedCreatedTask); - } private Task createAndCompleteTask() throws NotAuthorizedException, WorkbasketNotFoundException, diff --git a/lib/taskana-core/src/test/resources/sql/task.sql b/lib/taskana-core/src/test/resources/sql/task.sql index 662500145..1c710e9f3 100644 --- a/lib/taskana-core/src/test/resources/sql/task.sql +++ b/lib/taskana-core/src/test/resources/sql/task.sql @@ -5,7 +5,7 @@ INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000002', '2018-01-29 INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000003', '2018-01-29 15:55:03', null , null , '2018-01-29 15:55:03', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000003' , 'DOC_0000000000000000003' , null , '00' , 'PASystem' , '00' , 'VNR' , '11223344' , false , false , null , null , 'efg' , null , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000004', '2018-01-29 15:55:04', null , null , '2018-01-29 15:55:04', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000004' , 'DOC_0000000000000000004' , null , '00' , 'PASystem' , '00' , 'VNR' , '11223344' , false , false , null , null , null , 'ade' , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000005', '2018-01-29 15:55:05', null , null , '2018-01-29 15:55:05', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000005' , 'DOC_0000000000000000005' , null , '00' , 'PASystem' , '00' , 'VNR' , '11223344' , false , false , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); -INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000006', '2018-01-29 15:55:06', null , null , '2018-01-29 15:55:06', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000006' , 'DOC_0000000000000000006' , null , '00' , 'PASystem' , '00' , 'VNR' , '11223344' , false , false , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); +INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000006', '2018-01-29 15:55:06', null , '2018-01-30 16:55:06', '2018-01-29 15:55:06', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000006' , 'DOC_0000000000000000006' , null , '00' , 'PASystem' , '00' , 'VNR' , '11223344' , false , false , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000007', '2018-01-29 15:55:07', null , null , '2018-01-29 15:55:07', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000007' , 'DOC_0000000000000000007' , null , '00' , 'PASystem' , '00' , 'VNR' , '11223344' , false , false , null , null , null , null , 'ffg' , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000008', '2018-01-29 15:55:08', null , null , '2018-01-29 15:55:08', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000008' , 'DOC_0000000000000000008' , null , '00' , 'PASystem' , '00' , 'VNR' , '22334455' , false , false , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000009', '2018-01-29 15:55:09', null , null , '2018-01-29 15:55:09', '2018-01-29 15:55:00', '2018-01-30 15:55:00', 'Widerruf' , 'creator_user_id' , 'Widerruf' , null , 2 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000001' , 'GPK_KSC' , 'DOMAIN_A', 'PI_0000000000009' , 'DOC_0000000000000000009' , null , '00' , 'PASystem' , '00' , 'VNR' , '22334455' , false , false , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , 'abc' , null , null ); diff --git a/lib/taskana-spring-example/src/main/resources/taskana.properties b/lib/taskana-spring-example/src/main/resources/taskana.properties index 997700533..8bf9196d8 100644 --- a/lib/taskana-spring-example/src/main/resources/taskana.properties +++ b/lib/taskana-spring-example/src/main/resources/taskana.properties @@ -11,3 +11,4 @@ taskana.jobs.cleanup.schedule=0 0 3 * * * taskana.jobs.cleanup.runEvery=P1D taskana.jobs.cleanup.firstRunAt=2018-07-25T08:00:00Z taskana.jobs.cleanup.minimumAge=P14D +taskana.jobs.cleanup.allCompletedSameParentBusiness=true