TSK-681 Create workbasket cleanup job
This commit is contained in:
parent
e62e0a5ac7
commit
279589a5df
|
@ -20,7 +20,6 @@ public interface WorkbasketQuery extends BaseQuery<WorkbasketSummary> {
|
|||
/**
|
||||
* Add your keys to your query. The keys are compared case-insensitively to the keys of workbaskets with the IN
|
||||
* operator.
|
||||
*
|
||||
* @param key
|
||||
* the keys as Strings
|
||||
* @return the query
|
||||
|
@ -482,4 +481,14 @@ public interface WorkbasketQuery extends BaseQuery<WorkbasketSummary> {
|
|||
* @return the query
|
||||
*/
|
||||
WorkbasketQuery orgLevel4Like(String... orgLevel4);
|
||||
|
||||
/**
|
||||
* Add to your query if the Workbasket shall be marked for deletion.
|
||||
*
|
||||
* @param deletionFlag
|
||||
* a simple flag showing if deletion flag is activated
|
||||
* @return the query
|
||||
*/
|
||||
WorkbasketQuery deletionFlagEquals(Boolean deletionFlag);
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import pro.taskana.exceptions.DomainNotFoundException;
|
|||
import pro.taskana.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.exceptions.InvalidWorkbasketException;
|
||||
import pro.taskana.exceptions.NotAuthorizedException;
|
||||
import pro.taskana.exceptions.TaskanaException;
|
||||
import pro.taskana.exceptions.WorkbasketAlreadyExistException;
|
||||
import pro.taskana.exceptions.WorkbasketInUseException;
|
||||
import pro.taskana.exceptions.WorkbasketNotFoundException;
|
||||
|
@ -319,6 +320,18 @@ public interface WorkbasketService {
|
|||
boolean deleteWorkbasket(String workbasketId)
|
||||
throws NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException, InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Deletes a list of workbaskets.
|
||||
*
|
||||
* @param workbasketsIds
|
||||
* the ids of the workbaskets to delete.
|
||||
* @return the result of the operations with Id and Exception for each failed workbasket deletion.
|
||||
* @throws InvalidArgumentException
|
||||
* if the WorkbasketId parameter is NULL
|
||||
*/
|
||||
BulkOperationResults<String, TaskanaException> deleteWorkbaskets(List<String> workbasketsIds)
|
||||
throws NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException, InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Returns the distribution sources for a given workbasket.
|
||||
*
|
||||
|
|
|
@ -48,9 +48,9 @@ public class TaskanaEngineConfiguration {
|
|||
private static final String TASKANA_ROLES_SEPARATOR = "|";
|
||||
private static final String TASKANA_JOB_BATCHSIZE = "taskana.jobs.batchSize";
|
||||
private static final String TASKANA_JOB_RETRIES = "taskana.jobs.maxRetries";
|
||||
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_CLEANUP_RUN_EVERY = "taskana.jobs.cleanup.runEvery";
|
||||
private static final String TASKANA_JOB_CLEANUP_FIRST_RUN = "taskana.jobs.cleanup.firstRunAt";
|
||||
private static final String TASKANA_JOB_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";
|
||||
|
@ -86,9 +86,9 @@ public class TaskanaEngineConfiguration {
|
|||
private int maxNumberOfJobRetries = 3;
|
||||
|
||||
// Properties for the cleanup job
|
||||
private Instant taskCleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
|
||||
private Duration taskCleanupJobRunEvery = Duration.parse("P1D");
|
||||
private Duration taskCleanupJobMinimumAge = Duration.parse("P14D");
|
||||
private Instant cleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
|
||||
private Duration cleanupJobRunEvery = Duration.parse("P1D");
|
||||
private Duration cleanupJobMinimumAge = Duration.parse("P14D");
|
||||
private boolean taskCleanupJobAllCompletedSameParentBusiness = true;
|
||||
|
||||
// List of configured domain names
|
||||
|
@ -174,30 +174,30 @@ public class TaskanaEngineConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
String taskCleanupJobFirstRunProperty = props.getProperty(TASKANA_JOB_TASK_CLEANUP_FIRST_RUN);
|
||||
String taskCleanupJobFirstRunProperty = props.getProperty(TASKANA_JOB_CLEANUP_FIRST_RUN);
|
||||
if (taskCleanupJobFirstRunProperty != null && !taskCleanupJobFirstRunProperty.isEmpty()) {
|
||||
try {
|
||||
taskCleanupJobFirstRun = Instant.parse(taskCleanupJobFirstRunProperty);
|
||||
cleanupJobFirstRun = Instant.parse(taskCleanupJobFirstRunProperty);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not parse taskCleanupJobFirstRunProperty ({}). Using default. Exception: {} ",
|
||||
taskCleanupJobFirstRunProperty, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String taskCleanupJobRunEveryProperty = props.getProperty(TASKANA_JOB_TASK_CLEANUP_RUN_EVERY);
|
||||
String taskCleanupJobRunEveryProperty = props.getProperty(TASKANA_JOB_CLEANUP_RUN_EVERY);
|
||||
if (taskCleanupJobRunEveryProperty != null && !taskCleanupJobRunEveryProperty.isEmpty()) {
|
||||
try {
|
||||
taskCleanupJobRunEvery = Duration.parse(taskCleanupJobRunEveryProperty);
|
||||
cleanupJobRunEvery = Duration.parse(taskCleanupJobRunEveryProperty);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not parse taskCleanupJobRunEveryProperty ({}). Using default. Exception: {} ",
|
||||
taskCleanupJobRunEveryProperty, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String taskCleanupJobMinimumAgeProperty = props.getProperty(TASKANA_JOB_TASK_CLEANUP_MINIMUM_AGE);
|
||||
String taskCleanupJobMinimumAgeProperty = props.getProperty(TASKANA_JOB_CLEANUP_MINIMUM_AGE);
|
||||
if (taskCleanupJobMinimumAgeProperty != null && !taskCleanupJobMinimumAgeProperty.isEmpty()) {
|
||||
try {
|
||||
taskCleanupJobMinimumAge = Duration.parse(taskCleanupJobMinimumAgeProperty);
|
||||
cleanupJobMinimumAge = Duration.parse(taskCleanupJobMinimumAgeProperty);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not parse taskCleanupJobMinimumAgeProperty ({}). Using default. Exception: {} ",
|
||||
taskCleanupJobMinimumAgeProperty, e.getMessage());
|
||||
|
@ -218,12 +218,12 @@ public class TaskanaEngineConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Configured number of task updates per transaction: {}", jobBatchSize);
|
||||
LOGGER.debug("Configured number of task and workbasket 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);
|
||||
LOGGER.debug("CleanupJob configuration: first run at {}", cleanupJobFirstRun);
|
||||
LOGGER.debug("CleanupJob configuration: runs every {}", cleanupJobRunEvery);
|
||||
LOGGER.debug("CleanupJob configuration: minimum age of tasks to be cleanup up is {}",
|
||||
cleanupJobMinimumAge);
|
||||
LOGGER.debug("TaskCleanupJob configuration: all completed task with the same parent business property id {}",
|
||||
taskCleanupJobAllCompletedSameParentBusiness);
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ public class TaskanaEngineConfiguration {
|
|||
return this.propertiesFileName;
|
||||
}
|
||||
|
||||
public int getMaxNumberOfTaskUpdatesPerTransaction() {
|
||||
public int getMaxNumberOfUpdatesPerTransaction() {
|
||||
return jobBatchSize;
|
||||
}
|
||||
|
||||
|
@ -475,16 +475,16 @@ public class TaskanaEngineConfiguration {
|
|||
this.classificationCategoriesByTypeMap = classificationCategoriesByType;
|
||||
}
|
||||
|
||||
public Instant getTaskCleanupJobFirstRun() {
|
||||
return taskCleanupJobFirstRun;
|
||||
public Instant getCleanupJobFirstRun() {
|
||||
return cleanupJobFirstRun;
|
||||
}
|
||||
|
||||
public Duration getTaskCleanupJobRunEvery() {
|
||||
return taskCleanupJobRunEvery;
|
||||
public Duration getCleanupJobRunEvery() {
|
||||
return cleanupJobRunEvery;
|
||||
}
|
||||
|
||||
public Duration getTaskCleanupJobMinimumAge() {
|
||||
return taskCleanupJobMinimumAge;
|
||||
public Duration getCleanupJobMinimumAge() {
|
||||
return cleanupJobMinimumAge;
|
||||
}
|
||||
|
||||
public void setTaskCleanupJobAllCompletedSameParentBusiness(boolean taskCleanupJobAllCompletedSameParentBusiness) {
|
||||
|
|
|
@ -67,6 +67,8 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
|
|||
private String[] orgLevel3Like;
|
||||
private String[] orgLevel4In;
|
||||
private String[] orgLevel4Like;
|
||||
private boolean isDeletionFlagActivated;
|
||||
|
||||
private TaskanaEngineImpl taskanaEngine;
|
||||
private List<String> orderBy;
|
||||
private List<String> orderColumns;
|
||||
|
@ -272,6 +274,12 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkbasketQuery deletionFlagEquals(Boolean deletionFlag) {
|
||||
this.isDeletionFlagActivated = deletionFlag;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkbasketQuery orderByName(SortDirection sortDirection) {
|
||||
return addOrderCriteria("NAME", sortDirection);
|
||||
|
@ -396,7 +404,7 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
|
|||
this.columnName = columnName;
|
||||
handleCallerRolesAndAccessIds();
|
||||
this.orderBy.clear();
|
||||
this.addOrderCriteria(columnName, sortDirection);
|
||||
//this.addOrderCriteria(columnName, sortDirection);
|
||||
result = taskanaEngine.getSqlSession().selectList(LINK_TO_VALUEMAPPER, this);
|
||||
return result;
|
||||
} finally {
|
||||
|
@ -587,6 +595,10 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
|
|||
return orgLevel4Like;
|
||||
}
|
||||
|
||||
public boolean isDeletionFlagActivated() {
|
||||
return isDeletionFlagActivated;
|
||||
}
|
||||
|
||||
public String[] getOwnerLike() {
|
||||
return ownerLike;
|
||||
}
|
||||
|
@ -688,6 +700,8 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
|
|||
builder.append(Arrays.toString(orgLevel4In));
|
||||
builder.append(", orgLevel4Like=");
|
||||
builder.append(Arrays.toString(orgLevel4Like));
|
||||
builder.append(", deletionFlag=");
|
||||
builder.append(isDeletionFlagActivated);
|
||||
builder.append(", orderBy=");
|
||||
builder.append(orderBy);
|
||||
builder.append(", joinWithAccessList=");
|
||||
|
|
|
@ -3,12 +3,13 @@ package pro.taskana.impl;
|
|||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pro.taskana.BulkOperationResults;
|
||||
import pro.taskana.TaskState;
|
||||
import pro.taskana.TaskanaEngine;
|
||||
import pro.taskana.TaskanaRole;
|
||||
|
@ -23,6 +24,7 @@ import pro.taskana.exceptions.DomainNotFoundException;
|
|||
import pro.taskana.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.exceptions.InvalidWorkbasketException;
|
||||
import pro.taskana.exceptions.NotAuthorizedException;
|
||||
import pro.taskana.exceptions.TaskanaException;
|
||||
import pro.taskana.exceptions.WorkbasketAlreadyExistException;
|
||||
import pro.taskana.exceptions.WorkbasketInUseException;
|
||||
import pro.taskana.exceptions.WorkbasketNotFoundException;
|
||||
|
@ -725,7 +727,6 @@ public class WorkbasketServiceImpl implements WorkbasketService {
|
|||
throws NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException, InvalidArgumentException {
|
||||
LOGGER.debug("entry to deleteWorkbasket(workbasketId = {})", workbasketId);
|
||||
taskanaEngine.checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
|
||||
HashMap response = new HashMap();
|
||||
try {
|
||||
taskanaEngine.openConnection();
|
||||
if (workbasketId == null || workbasketId.isEmpty()) {
|
||||
|
@ -779,19 +780,80 @@ public class WorkbasketServiceImpl implements WorkbasketService {
|
|||
WorkbasketImpl workbasket = workbasketMapper.findById(workbasketId);
|
||||
workbasket.setMarkedForDeletion(true);
|
||||
workbasketMapper.update(workbasket);
|
||||
distributionTargetMapper.deleteAllDistributionTargetsBySourceId(workbasketId);
|
||||
distributionTargetMapper.deleteAllDistributionTargetsByTargetId(workbasketId);
|
||||
workbasketAccessMapper.deleteAllAccessItemsForWorkbasketId(workbasketId);
|
||||
deleteWorkbasketTablesReferences(workbasketId);
|
||||
} finally {
|
||||
taskanaEngine.returnConnection();
|
||||
LOGGER.debug("exit from markWorkbasketForDeletion(workbasketId = {}).", workbasketId);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteWorkbasketTablesReferences(String workbasketId) {
|
||||
// delete workbasket and sub-tables
|
||||
distributionTargetMapper.deleteAllDistributionTargetsBySourceId(workbasketId);
|
||||
distributionTargetMapper.deleteAllDistributionTargetsByTargetId(workbasketId);
|
||||
workbasketAccessMapper.deleteAllAccessItemsForWorkbasketId(workbasketId);
|
||||
}
|
||||
|
||||
public BulkOperationResults<String, TaskanaException> deleteWorkbaskets(List<String> workbasketsIds)
|
||||
throws NotAuthorizedException, InvalidArgumentException, WorkbasketInUseException, WorkbasketNotFoundException {
|
||||
LOGGER.debug("entry to deleteWorkbaskets(workbasketId = {})", LoggerUtils.listToString(workbasketsIds));
|
||||
|
||||
taskanaEngine.checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
|
||||
|
||||
try {
|
||||
taskanaEngine.openConnection();
|
||||
if (workbasketsIds == null || workbasketsIds.isEmpty()) {
|
||||
throw new InvalidArgumentException("List of WorkbasketIds must not be null.");
|
||||
}
|
||||
|
||||
List<String> existingWorkbasketIds = workbasketMapper.findExistingWorkbaskets(workbasketsIds);
|
||||
BulkOperationResults<String, TaskanaException> bulkLog = cleanNonExistingWorkbasketExists(
|
||||
existingWorkbasketIds,
|
||||
workbasketsIds);
|
||||
|
||||
if (!existingWorkbasketIds.isEmpty()) {
|
||||
Iterator<String> iterator = existingWorkbasketIds.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
deleteWorkbasket(iterator.next());
|
||||
}
|
||||
}
|
||||
return bulkLog;
|
||||
} finally {
|
||||
LOGGER.debug("exit from deleteWorkbaskets()");
|
||||
taskanaEngine.returnConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkbasketAccessItemQuery createWorkbasketAccessItemQuery() throws NotAuthorizedException {
|
||||
taskanaEngine.checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN);
|
||||
return new WorkbasketAccessItemQueryImpl(this.taskanaEngine);
|
||||
}
|
||||
|
||||
private BulkOperationResults<String, TaskanaException> cleanNonExistingWorkbasketExists(
|
||||
List<String> existingWorkbasketIds,
|
||||
List<String> workbasketIds) {
|
||||
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
|
||||
Iterator<String> workbasketIdIterator = existingWorkbasketIds.iterator();
|
||||
while (workbasketIdIterator.hasNext()) {
|
||||
String currentWorkbasketId = workbasketIdIterator.next();
|
||||
if (currentWorkbasketId == null || currentWorkbasketId.equals("")) {
|
||||
bulkLog.addError("",
|
||||
new InvalidArgumentException("IDs with EMPTY or NULL value are not allowed."));
|
||||
workbasketIdIterator.remove();
|
||||
} else {
|
||||
String foundSummary = workbasketIds.stream()
|
||||
.filter(workbasketId -> currentWorkbasketId.equals(workbasketId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (foundSummary == null) {
|
||||
bulkLog.addError(currentWorkbasketId, new WorkbasketNotFoundException(currentWorkbasketId,
|
||||
"Workbasket with id " + currentWorkbasketId + " was not found."));
|
||||
workbasketIdIterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
return bulkLog;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ public abstract class AbstractTaskanaJob implements TaskanaJob {
|
|||
return new TaskRefreshJob(engine, txProvider, job);
|
||||
case TASKCLEANUPJOB:
|
||||
return new TaskCleanupJob(engine, txProvider, job);
|
||||
case WORKBASKETCLEANUPJOB:
|
||||
return new WorkbasketCleanupJob(engine, txProvider, job);
|
||||
default:
|
||||
throw new TaskanaException(
|
||||
"No matching job found for " + job.getType() + " of ScheduledJob " + job.getJobId() + ".");
|
||||
|
|
|
@ -82,7 +82,7 @@ public class ClassificationChangedJob extends AbstractTaskanaJob {
|
|||
}
|
||||
|
||||
private void scheduleTaskRefreshJobs(Set<String> affectedTaskIds) {
|
||||
int batchSize = taskanaEngineImpl.getConfiguration().getMaxNumberOfTaskUpdatesPerTransaction();
|
||||
int batchSize = taskanaEngineImpl.getConfiguration().getMaxNumberOfUpdatesPerTransaction();
|
||||
List<List<String>> affectedTaskBatches = partition(affectedTaskIds, batchSize);
|
||||
LOGGER.debug("Creating {} TaskRefreshJobs out of {} affected tasks with a maximum number of {} tasks each. ",
|
||||
affectedTaskBatches.size(), affectedTaskIds.size(), batchSize);
|
||||
|
|
|
@ -63,11 +63,9 @@ public class JobRunner {
|
|||
}
|
||||
|
||||
private ScheduledJob lockJobTransactionally(ScheduledJob job) {
|
||||
ScheduledJob lockedJob = null;
|
||||
ScheduledJob lockedJob;
|
||||
if (txProvider != null) {
|
||||
lockedJob = (ScheduledJob) txProvider.executeInTransaction(() -> {
|
||||
return lockJob(job);
|
||||
});
|
||||
lockedJob = (ScheduledJob) txProvider.executeInTransaction(() -> lockJob(job));
|
||||
} else {
|
||||
lockedJob = lockJob(job);
|
||||
}
|
||||
|
@ -101,25 +99,9 @@ public class JobRunner {
|
|||
jobService.deleteJob(scheduledJob);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// transaction was rolled back -> split job into 2 half sized jobs
|
||||
LOGGER.warn(
|
||||
"Processing of job " + scheduledJob.getJobId() + " failed. Trying to split it up into two pieces...",
|
||||
e);
|
||||
// rescheduleBisectedJob(bulkLog, job);
|
||||
// List<String> objectIds;
|
||||
// if (job.getType().equals(ScheduledJob.Type.UPDATETASKSJOB)) {
|
||||
// String taskIdsAsString = job.getArguments().get(SingleJobExecutor.TASKIDS);
|
||||
// objectIds = Arrays.asList(taskIdsAsString.split(","));
|
||||
// } else if (job.getType().equals(ScheduledJob.Type.CLASSIFICATIONCHANGEDJOB)) {
|
||||
// String classificationId = job.getArguments().get(SingleJobExecutor.CLASSIFICATION_ID);
|
||||
// objectIds = Arrays.asList(classificationId);
|
||||
// } else {
|
||||
// throw new SystemException("Unknown Jobtype " + job.getType() + " encountered.");
|
||||
// }
|
||||
// for (String objectId : objectIds) {
|
||||
// bulkLog.addError(objectId, e);
|
||||
// }
|
||||
// setJobFailed(job, bulkLog);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ public class ScheduledJob {
|
|||
public enum Type {
|
||||
CLASSIFICATIONCHANGEDJOB,
|
||||
UPDATETASKSJOB,
|
||||
TASKCLEANUPJOB;
|
||||
TASKCLEANUPJOB,
|
||||
WORKBASKETCLEANUPJOB;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,10 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
|
|||
public TaskCleanupJob(TaskanaEngine taskanaEngine, TaskanaTransactionProvider<Object> txProvider,
|
||||
ScheduledJob scheduledJob) {
|
||||
super(taskanaEngine, txProvider, scheduledJob);
|
||||
firstRun = taskanaEngine.getConfiguration().getTaskCleanupJobFirstRun();
|
||||
runEvery = taskanaEngine.getConfiguration().getTaskCleanupJobRunEvery();
|
||||
minimumAge = taskanaEngine.getConfiguration().getTaskCleanupJobMinimumAge();
|
||||
batchSize = taskanaEngine.getConfiguration().getMaxNumberOfTaskUpdatesPerTransaction();
|
||||
firstRun = taskanaEngine.getConfiguration().getCleanupJobFirstRun();
|
||||
runEvery = taskanaEngine.getConfiguration().getCleanupJobRunEvery();
|
||||
minimumAge = taskanaEngine.getConfiguration().getCleanupJobMinimumAge();
|
||||
batchSize = taskanaEngine.getConfiguration().getMaxNumberOfUpdatesPerTransaction();
|
||||
allCompletedSameParentBusiness = taskanaEngine.getConfiguration()
|
||||
.isTaskCleanupJobAllCompletedSameParentBusiness();
|
||||
}
|
||||
|
@ -62,10 +62,11 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
|
|||
totalNumberOfTasksCompleted += deleteTasksTransactionally(tasksCompletedBefore.subList(0, upperLimit));
|
||||
tasksCompletedBefore.subList(0, upperLimit).clear();
|
||||
}
|
||||
scheduleNextCleanupJob();
|
||||
LOGGER.info("Job ended successfully. {} tasks deleted.", totalNumberOfTasksCompleted);
|
||||
} catch (Exception e) {
|
||||
throw new TaskanaException("Error while processing TaskCleanupJob.", e);
|
||||
} finally {
|
||||
scheduleNextCleanupJob();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,11 +81,10 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
|
|||
Map<String, Long> numberParentTasksShouldHave = new HashMap<>();
|
||||
Map<String, Long> countParentTask = new HashMap<>();
|
||||
for (TaskSummary task : taskList) {
|
||||
numberParentTasksShouldHave.put(task.getParentBusinessProcessId(),
|
||||
taskanaEngineImpl.getTaskService()
|
||||
.createTaskQuery()
|
||||
.parentBusinessProcessIdIn(task.getParentBusinessProcessId())
|
||||
.count());
|
||||
numberParentTasksShouldHave.put(task.getParentBusinessProcessId(), taskanaEngineImpl.getTaskService()
|
||||
.createTaskQuery()
|
||||
.parentBusinessProcessIdIn(task.getParentBusinessProcessId())
|
||||
.count());
|
||||
countParentTask.merge(task.getParentBusinessProcessId(), 1L, Long::sum);
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
|
|||
return tasksIdsToBeDeleted.size() - results.getFailedIds().size();
|
||||
}
|
||||
|
||||
public void scheduleNextCleanupJob() {
|
||||
private void scheduleNextCleanupJob() {
|
||||
LOGGER.debug("Entry to scheduleNextCleanupJob.");
|
||||
ScheduledJob job = new ScheduledJob();
|
||||
job.setType(ScheduledJob.Type.TASKCLEANUPJOB);
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
package pro.taskana.jobs;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pro.taskana.BaseQuery;
|
||||
import pro.taskana.BulkOperationResults;
|
||||
import pro.taskana.TaskService;
|
||||
import pro.taskana.TaskanaEngine;
|
||||
import pro.taskana.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.exceptions.NotAuthorizedException;
|
||||
import pro.taskana.exceptions.TaskanaException;
|
||||
import pro.taskana.exceptions.WorkbasketInUseException;
|
||||
import pro.taskana.exceptions.WorkbasketNotFoundException;
|
||||
import pro.taskana.impl.util.LoggerUtils;
|
||||
import pro.taskana.transaction.TaskanaTransactionProvider;
|
||||
|
||||
/**
|
||||
* Job to cleanup completed workbaskets after a period of time if there are no pending tasks associated to the workbasket.
|
||||
*/
|
||||
public class WorkbasketCleanupJob extends AbstractTaskanaJob {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TaskCleanupJob.class);
|
||||
|
||||
// Parameter
|
||||
private Instant firstRun;
|
||||
private Duration runEvery;
|
||||
private int batchSize;
|
||||
|
||||
public WorkbasketCleanupJob(TaskanaEngine taskanaEngine,
|
||||
TaskanaTransactionProvider<Object> txProvider, ScheduledJob job) {
|
||||
super(taskanaEngine, txProvider, job);
|
||||
firstRun = taskanaEngine.getConfiguration().getCleanupJobFirstRun();
|
||||
runEvery = taskanaEngine.getConfiguration().getCleanupJobRunEvery();
|
||||
batchSize = taskanaEngine.getConfiguration().getMaxNumberOfUpdatesPerTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws TaskanaException {
|
||||
LOGGER.info("Running job to delete all workbaskets marked for deletion");
|
||||
try {
|
||||
List<String> workbasketsMarkedForDeletion = getWorkbasketsMarkedForDeletion();
|
||||
int totalNumberOfWorkbasketDeleted = 0;
|
||||
while (workbasketsMarkedForDeletion.size() > 0) {
|
||||
int upperLimit = batchSize;
|
||||
if (upperLimit > workbasketsMarkedForDeletion.size()) {
|
||||
upperLimit = workbasketsMarkedForDeletion.size();
|
||||
}
|
||||
totalNumberOfWorkbasketDeleted += deleteWorkbasketsTransactionally(
|
||||
workbasketsMarkedForDeletion.subList(0, upperLimit));
|
||||
workbasketsMarkedForDeletion.subList(0, upperLimit).clear();
|
||||
}
|
||||
LOGGER.info("Job ended successfully. {} workbaskets deleted.", totalNumberOfWorkbasketDeleted);
|
||||
} catch (Exception e) {
|
||||
throw new TaskanaException("Error while processing WorkbasketCleanupJob.", e);
|
||||
} finally {
|
||||
scheduleNextCleanupJob();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getWorkbasketsMarkedForDeletion() throws InvalidArgumentException {
|
||||
List<String> workbasketList = taskanaEngineImpl.getWorkbasketService()
|
||||
.createWorkbasketQuery()
|
||||
.deletionFlagEquals(true)
|
||||
.listValues("ID", BaseQuery.SortDirection.ASCENDING);
|
||||
workbasketList = excludeWorkbasketWithPendingTasks(workbasketList);
|
||||
|
||||
return workbasketList;
|
||||
}
|
||||
|
||||
private List<String> excludeWorkbasketWithPendingTasks(List<String> workbasketList)
|
||||
throws InvalidArgumentException {
|
||||
TaskService taskService = taskanaEngineImpl.getTaskService();
|
||||
ArrayList<String> workbasketDeletionList = new ArrayList<>();
|
||||
ArrayList<String> workbasketWithNonCompletedTasksList = new ArrayList<>();
|
||||
|
||||
if (!workbasketList.isEmpty()) {
|
||||
Iterator<String> iterator = workbasketList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
String workbasketId = iterator.next();
|
||||
if (taskService.allTasksCompletedByWorkbasketId(workbasketId)) {
|
||||
workbasketDeletionList.add(workbasketId);
|
||||
} else {
|
||||
workbasketWithNonCompletedTasksList.add(workbasketId);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.info("workbasket marked for deletion with non completed tasks {}.",
|
||||
LoggerUtils.listToString(workbasketWithNonCompletedTasksList));
|
||||
return workbasketDeletionList;
|
||||
}
|
||||
|
||||
private int deleteWorkbasketsTransactionally(List<String> workbasketsToBeDeleted) {
|
||||
int deletedWorkbasketsCount = 0;
|
||||
if (txProvider != null) {
|
||||
Integer count = (Integer) txProvider.executeInTransaction(() -> {
|
||||
try {
|
||||
return new Integer(deleteWorkbaskets(workbasketsToBeDeleted));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not delete workbaskets.", e);
|
||||
return new Integer(0);
|
||||
}
|
||||
});
|
||||
return count.intValue();
|
||||
} else {
|
||||
try {
|
||||
deletedWorkbasketsCount = deleteWorkbaskets(workbasketsToBeDeleted);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not delete workbaskets.", e);
|
||||
}
|
||||
}
|
||||
return deletedWorkbasketsCount;
|
||||
}
|
||||
|
||||
private int deleteWorkbaskets(List<String> workbasketsToBeDeleted)
|
||||
throws InvalidArgumentException, NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException {
|
||||
|
||||
BulkOperationResults<String, TaskanaException> results = taskanaEngineImpl.getWorkbasketService()
|
||||
.deleteWorkbaskets(workbasketsToBeDeleted);
|
||||
LOGGER.debug("{} workbasket deleted.", workbasketsToBeDeleted.size() - results.getFailedIds().size());
|
||||
for (String failedId : results.getFailedIds()) {
|
||||
LOGGER.warn("Workbasket with id {} could not be deleted. Reason: {}", failedId,
|
||||
results.getErrorForId(failedId));
|
||||
}
|
||||
return workbasketsToBeDeleted.size() - results.getFailedIds().size();
|
||||
}
|
||||
|
||||
private void scheduleNextCleanupJob() {
|
||||
LOGGER.debug("Entry to scheduleNextCleanupJob.");
|
||||
ScheduledJob job = new ScheduledJob();
|
||||
job.setType(ScheduledJob.Type.WORKBASKETCLEANUPJOB);
|
||||
job.setDue(getNextDueForWorkbasketCleanupJob());
|
||||
taskanaEngineImpl.getJobService().createJob(job);
|
||||
LOGGER.debug("Exit from scheduleNextCleanupJob.");
|
||||
}
|
||||
|
||||
private Instant getNextDueForWorkbasketCleanupJob() {
|
||||
Instant nextRunAt = firstRun;
|
||||
while (nextRunAt.isBefore(Instant.now())) {
|
||||
nextRunAt = nextRunAt.plus(runEvery);
|
||||
}
|
||||
LOGGER.info("Scheduling next run of the WorkbasketCleanupJob for {}", nextRunAt);
|
||||
return nextRunAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the WorkbasketCleanupJob schedule. <br>
|
||||
* All scheduled cleanup jobs are cancelled/deleted and a new one is scheduled.
|
||||
*
|
||||
* @param taskanaEngine
|
||||
*/
|
||||
public static void initializeSchedule(TaskanaEngine taskanaEngine) {
|
||||
WorkbasketCleanupJob job = new WorkbasketCleanupJob(taskanaEngine, null, null);
|
||||
job.scheduleNextCleanupJob();
|
||||
}
|
||||
}
|
|
@ -1167,6 +1167,7 @@ public interface QueryMapper {
|
|||
+ "<if test='orgLevel3Like != null'>AND (<foreach item='item' collection='orgLevel3Like' separator=' OR ' >UPPER(w.ORG_LEVEL_3) LIKE #{item}</foreach>)</if> "
|
||||
+ "<if test='orgLevel4In != null'>AND w.ORG_LEVEL_4 IN(<foreach item='item' collection='orgLevel4In' separator=',' >#{item}</foreach>)</if> "
|
||||
+ "<if test='orgLevel4Like != null'>AND (<foreach item='item' collection='orgLevel4Like' separator=' OR ' >UPPER(w.ORG_LEVEL_4) LIKE #{item}</foreach>)</if> "
|
||||
+ "<if test='isDeletionFlagActivated != null'>AND w.DELETION_FLAG = #{isDeletionFlagActivated}</if> "
|
||||
+ "<if test = 'joinWithAccessList'> "
|
||||
+ "<if test = 'checkReadPermission'> "
|
||||
+ "AND (a.MAX_READ = 1 "
|
||||
|
|
|
@ -65,9 +65,10 @@ public interface WorkbasketMapper {
|
|||
@Result(property = "markedForDeletion", column = "MARKED_FOR_DELETION")})
|
||||
WorkbasketImpl findByKeyAndDomain(@Param("key") String key, @Param("domain") String domain);
|
||||
|
||||
@Select("<script>SELECT ID, KEY, NAME, DESCRIPTION, OWNER, DOMAIN, TYPE, CUSTOM_1, CUSTOM_2, CUSTOM_3, CUSTOM_4, ORG_LEVEL_1, ORG_LEVEL_2, ORG_LEVEL_3, ORG_LEVEL_4 FROM WORKBASKET WHERE ID IN (SELECT TARGET_ID FROM DISTRIBUTION_TARGETS WHERE SOURCE_ID = #{id}) "
|
||||
+ "<if test=\"_databaseId == 'db2'\">with UR </if> "
|
||||
+ "</script>")
|
||||
@Select(
|
||||
"<script>SELECT ID, KEY, NAME, DESCRIPTION, OWNER, DOMAIN, TYPE, CUSTOM_1, CUSTOM_2, CUSTOM_3, CUSTOM_4, ORG_LEVEL_1, ORG_LEVEL_2, ORG_LEVEL_3, ORG_LEVEL_4 FROM WORKBASKET WHERE ID IN (SELECT TARGET_ID FROM DISTRIBUTION_TARGETS WHERE SOURCE_ID = #{id}) "
|
||||
+ "<if test=\"_databaseId == 'db2'\">with UR </if> "
|
||||
+ "</script>")
|
||||
@Results(value = {
|
||||
@Result(property = "id", column = "ID"),
|
||||
@Result(property = "key", column = "KEY"),
|
||||
|
@ -86,10 +87,11 @@ public interface WorkbasketMapper {
|
|||
@Result(property = "orgLevel4", column = "ORG_LEVEL_4")})
|
||||
List<WorkbasketSummaryImpl> findDistributionTargets(@Param("id") String id);
|
||||
|
||||
@Select("<script>SELECT ID, KEY, NAME, DESCRIPTION, OWNER, DOMAIN, TYPE, CUSTOM_1, CUSTOM_2, CUSTOM_3, CUSTOM_4, ORG_LEVEL_1, ORG_LEVEL_2, ORG_LEVEL_3, ORG_LEVEL_4 FROM WORKBASKET "
|
||||
+ " WHERE ID IN (SELECT SOURCE_ID FROM DISTRIBUTION_TARGETS WHERE TARGET_ID = #{id}) "
|
||||
+ "<if test=\"_databaseId == 'db2'\">with UR </if> "
|
||||
+ "</script>")
|
||||
@Select(
|
||||
"<script>SELECT ID, KEY, NAME, DESCRIPTION, OWNER, DOMAIN, TYPE, CUSTOM_1, CUSTOM_2, CUSTOM_3, CUSTOM_4, ORG_LEVEL_1, ORG_LEVEL_2, ORG_LEVEL_3, ORG_LEVEL_4 FROM WORKBASKET "
|
||||
+ " WHERE ID IN (SELECT SOURCE_ID FROM DISTRIBUTION_TARGETS WHERE TARGET_ID = #{id}) "
|
||||
+ "<if test=\"_databaseId == 'db2'\">with UR </if> "
|
||||
+ "</script>")
|
||||
@Results(value = {
|
||||
@Result(property = "id", column = "ID"),
|
||||
@Result(property = "key", column = "KEY"),
|
||||
|
@ -150,6 +152,12 @@ public interface WorkbasketMapper {
|
|||
@Result(property = "orgLevel4", column = "ORG_LEVEL_4")})
|
||||
List<WorkbasketSummaryImpl> findAll();
|
||||
|
||||
@Select("<script>SELECT ID FROM WORKBASKET "
|
||||
+ "WHERE ID IN( <foreach item='item' collection='workbasketIds' separator=',' >#{item}</foreach> ) "
|
||||
+ "<if test=\"_databaseId == 'db2'\">with UR </if> "
|
||||
+ "</script>")
|
||||
List<String> findExistingWorkbaskets(@Param("workbasketIds") List<String> workbasketIds);
|
||||
|
||||
@Insert("<script>INSERT INTO WORKBASKET (ID, KEY, CREATED, MODIFIED, NAME, DOMAIN, TYPE, DESCRIPTION, OWNER, CUSTOM_1, CUSTOM_2, CUSTOM_3, CUSTOM_4, ORG_LEVEL_1, ORG_LEVEL_2, ORG_LEVEL_3, ORG_LEVEL_4, MARKED_FOR_DELETION) VALUES (#{workbasket.id}, #{workbasket.key}, #{workbasket.created}, #{workbasket.modified}, #{workbasket.name}, #{workbasket.domain}, #{workbasket.type}, #{workbasket.description}, #{workbasket.owner}, #{workbasket.custom1}, #{workbasket.custom2}, #{workbasket.custom3}, #{workbasket.custom4}, #{workbasket.orgLevel1}, #{workbasket.orgLevel2}, #{workbasket.orgLevel3}, #{workbasket.orgLevel4}, #{workbasket.markedForDeletion}) "
|
||||
+ "</script>")
|
||||
@Options(keyProperty = "id", keyColumn = "ID")
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package acceptance.jobs;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import acceptance.AbstractAccTest;
|
||||
import pro.taskana.BaseQuery;
|
||||
import pro.taskana.TaskService;
|
||||
import pro.taskana.WorkbasketService;
|
||||
import pro.taskana.WorkbasketSummary;
|
||||
import pro.taskana.jobs.WorkbasketCleanupJob;
|
||||
import pro.taskana.security.JAASRunner;
|
||||
import pro.taskana.security.WithAccessId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Acceptance test for all "jobs workbasket runner" scenarios.
|
||||
*/
|
||||
@RunWith(JAASRunner.class)
|
||||
public class WorkbasketCleanupJobAccTest extends AbstractAccTest {
|
||||
|
||||
WorkbasketService workbasketService;
|
||||
TaskService taskService;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
workbasketService = taskanaEngine.getWorkbasketService();
|
||||
taskService = taskanaEngine.getTaskService();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
resetDb(true);
|
||||
}
|
||||
|
||||
@WithAccessId(userName = "admin")
|
||||
@Test
|
||||
public void shouldCleanWorkbasketMarkedForDeletion() throws Exception {
|
||||
long totalWorkbasketCount = workbasketService.createWorkbasketQuery().count();
|
||||
assertEquals(25, totalWorkbasketCount);
|
||||
List<WorkbasketSummary> workbaskets = workbasketService.createWorkbasketQuery()
|
||||
.keyIn("GPK_KSC", "sort001")
|
||||
.orderByKey(
|
||||
BaseQuery.SortDirection.ASCENDING)
|
||||
.list();
|
||||
assertEquals(taskService.allTasksCompletedByWorkbasketId(workbaskets.get(1).getId()), true);
|
||||
workbasketService.markWorkbasketForDeletion(workbaskets.get(1).getId());
|
||||
|
||||
WorkbasketCleanupJob job = new WorkbasketCleanupJob(taskanaEngine, null, null);
|
||||
job.run();
|
||||
|
||||
totalWorkbasketCount = workbasketService.createWorkbasketQuery().count();
|
||||
assertEquals(24, totalWorkbasketCount);
|
||||
}
|
||||
|
||||
@WithAccessId(userName = "admin")
|
||||
@Test
|
||||
public void shouldCleanWorkbasketMarkedForDeletionWithCompletedTasks() throws Exception {
|
||||
long totalWorkbasketCount = workbasketService.createWorkbasketQuery().count();
|
||||
assertEquals(25, totalWorkbasketCount);
|
||||
List<WorkbasketSummary> workbaskets = workbasketService.createWorkbasketQuery()
|
||||
.keyIn("GPK_KSC", "sort001")
|
||||
.orderByKey(
|
||||
BaseQuery.SortDirection.ASCENDING)
|
||||
.list();
|
||||
assertEquals(taskService.allTasksCompletedByWorkbasketId(workbaskets.get(0).getId()), false);
|
||||
assertEquals(taskService.allTasksCompletedByWorkbasketId(workbaskets.get(1).getId()), true);
|
||||
workbasketService.markWorkbasketForDeletion(workbaskets.get(0).getId());
|
||||
workbasketService.markWorkbasketForDeletion(workbaskets.get(1).getId());
|
||||
|
||||
WorkbasketCleanupJob job = new WorkbasketCleanupJob(taskanaEngine, null, null);
|
||||
job.run();
|
||||
|
||||
totalWorkbasketCount = workbasketService.createWorkbasketQuery().count();
|
||||
assertEquals(24, totalWorkbasketCount);
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ public class JobScheduler {
|
|||
public void scheduleCleanupJob() {
|
||||
LOGGER.debug("Entry to scheduleCleanupJob.");
|
||||
TaskCleanupJob.initializeSchedule(taskanaEngine);
|
||||
WorkbasketCleanupJob.initializeSchedule(taskanaEngine);
|
||||
LOGGER.debug("Exit from scheduleCleanupJob.");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue