TSK-681 Create workbasket cleanup job

This commit is contained in:
Martin Rojas Miguel Angel 2018-09-13 11:32:37 +02:00 committed by Holger Hagen
parent e62e0a5ac7
commit 279589a5df
15 changed files with 408 additions and 71 deletions

View File

@ -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 * Add your keys to your query. The keys are compared case-insensitively to the keys of workbaskets with the IN
* operator. * operator.
*
* @param key * @param key
* the keys as Strings * the keys as Strings
* @return the query * @return the query
@ -482,4 +481,14 @@ public interface WorkbasketQuery extends BaseQuery<WorkbasketSummary> {
* @return the query * @return the query
*/ */
WorkbasketQuery orgLevel4Like(String... orgLevel4); 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);
} }

View File

@ -6,6 +6,7 @@ import pro.taskana.exceptions.DomainNotFoundException;
import pro.taskana.exceptions.InvalidArgumentException; import pro.taskana.exceptions.InvalidArgumentException;
import pro.taskana.exceptions.InvalidWorkbasketException; import pro.taskana.exceptions.InvalidWorkbasketException;
import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.TaskanaException;
import pro.taskana.exceptions.WorkbasketAlreadyExistException; import pro.taskana.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.exceptions.WorkbasketInUseException; import pro.taskana.exceptions.WorkbasketInUseException;
import pro.taskana.exceptions.WorkbasketNotFoundException; import pro.taskana.exceptions.WorkbasketNotFoundException;
@ -319,6 +320,18 @@ public interface WorkbasketService {
boolean deleteWorkbasket(String workbasketId) boolean deleteWorkbasket(String workbasketId)
throws NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException, InvalidArgumentException; 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. * Returns the distribution sources for a given workbasket.
* *

View File

@ -48,9 +48,9 @@ public class TaskanaEngineConfiguration {
private static final String TASKANA_ROLES_SEPARATOR = "|"; private static final String TASKANA_ROLES_SEPARATOR = "|";
private static final String TASKANA_JOB_BATCHSIZE = "taskana.jobs.batchSize"; 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_RETRIES = "taskana.jobs.maxRetries";
private static final String TASKANA_JOB_TASK_CLEANUP_RUN_EVERY = "taskana.jobs.cleanup.runEvery"; private static final String TASKANA_JOB_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_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_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_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_DOMAINS_PROPERTY = "taskana.domains";
@ -86,9 +86,9 @@ public class TaskanaEngineConfiguration {
private int maxNumberOfJobRetries = 3; private int maxNumberOfJobRetries = 3;
// Properties for the cleanup job // Properties for the cleanup job
private Instant taskCleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z"); private Instant cleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
private Duration taskCleanupJobRunEvery = Duration.parse("P1D"); private Duration cleanupJobRunEvery = Duration.parse("P1D");
private Duration taskCleanupJobMinimumAge = Duration.parse("P14D"); private Duration cleanupJobMinimumAge = Duration.parse("P14D");
private boolean taskCleanupJobAllCompletedSameParentBusiness = true; private boolean taskCleanupJobAllCompletedSameParentBusiness = true;
// List of configured domain names // 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()) { if (taskCleanupJobFirstRunProperty != null && !taskCleanupJobFirstRunProperty.isEmpty()) {
try { try {
taskCleanupJobFirstRun = Instant.parse(taskCleanupJobFirstRunProperty); cleanupJobFirstRun = Instant.parse(taskCleanupJobFirstRunProperty);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("Could not parse taskCleanupJobFirstRunProperty ({}). Using default. Exception: {} ", LOGGER.warn("Could not parse taskCleanupJobFirstRunProperty ({}). Using default. Exception: {} ",
taskCleanupJobFirstRunProperty, e.getMessage()); 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()) { if (taskCleanupJobRunEveryProperty != null && !taskCleanupJobRunEveryProperty.isEmpty()) {
try { try {
taskCleanupJobRunEvery = Duration.parse(taskCleanupJobRunEveryProperty); cleanupJobRunEvery = Duration.parse(taskCleanupJobRunEveryProperty);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("Could not parse taskCleanupJobRunEveryProperty ({}). Using default. Exception: {} ", LOGGER.warn("Could not parse taskCleanupJobRunEveryProperty ({}). Using default. Exception: {} ",
taskCleanupJobRunEveryProperty, e.getMessage()); 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()) { if (taskCleanupJobMinimumAgeProperty != null && !taskCleanupJobMinimumAgeProperty.isEmpty()) {
try { try {
taskCleanupJobMinimumAge = Duration.parse(taskCleanupJobMinimumAgeProperty); cleanupJobMinimumAge = Duration.parse(taskCleanupJobMinimumAgeProperty);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("Could not parse taskCleanupJobMinimumAgeProperty ({}). Using default. Exception: {} ", LOGGER.warn("Could not parse taskCleanupJobMinimumAgeProperty ({}). Using default. Exception: {} ",
taskCleanupJobMinimumAgeProperty, e.getMessage()); 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("Number of retries of failed task updates: {}", maxNumberOfJobRetries);
LOGGER.debug("TaskCleanupJob configuration: first run at {}", taskCleanupJobFirstRun); LOGGER.debug("CleanupJob configuration: first run at {}", cleanupJobFirstRun);
LOGGER.debug("TaskCleanupJob configuration: runs every {}", taskCleanupJobRunEvery); LOGGER.debug("CleanupJob configuration: runs every {}", cleanupJobRunEvery);
LOGGER.debug("TaskCleanupJob configuration: minimum age of tasks to be cleanup up is {}", LOGGER.debug("CleanupJob configuration: minimum age of tasks to be cleanup up is {}",
taskCleanupJobMinimumAge); cleanupJobMinimumAge);
LOGGER.debug("TaskCleanupJob configuration: all completed task with the same parent business property id {}", LOGGER.debug("TaskCleanupJob configuration: all completed task with the same parent business property id {}",
taskCleanupJobAllCompletedSameParentBusiness); taskCleanupJobAllCompletedSameParentBusiness);
} }
@ -399,7 +399,7 @@ public class TaskanaEngineConfiguration {
return this.propertiesFileName; return this.propertiesFileName;
} }
public int getMaxNumberOfTaskUpdatesPerTransaction() { public int getMaxNumberOfUpdatesPerTransaction() {
return jobBatchSize; return jobBatchSize;
} }
@ -475,16 +475,16 @@ public class TaskanaEngineConfiguration {
this.classificationCategoriesByTypeMap = classificationCategoriesByType; this.classificationCategoriesByTypeMap = classificationCategoriesByType;
} }
public Instant getTaskCleanupJobFirstRun() { public Instant getCleanupJobFirstRun() {
return taskCleanupJobFirstRun; return cleanupJobFirstRun;
} }
public Duration getTaskCleanupJobRunEvery() { public Duration getCleanupJobRunEvery() {
return taskCleanupJobRunEvery; return cleanupJobRunEvery;
} }
public Duration getTaskCleanupJobMinimumAge() { public Duration getCleanupJobMinimumAge() {
return taskCleanupJobMinimumAge; return cleanupJobMinimumAge;
} }
public void setTaskCleanupJobAllCompletedSameParentBusiness(boolean taskCleanupJobAllCompletedSameParentBusiness) { public void setTaskCleanupJobAllCompletedSameParentBusiness(boolean taskCleanupJobAllCompletedSameParentBusiness) {

View File

@ -67,6 +67,8 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
private String[] orgLevel3Like; private String[] orgLevel3Like;
private String[] orgLevel4In; private String[] orgLevel4In;
private String[] orgLevel4Like; private String[] orgLevel4Like;
private boolean isDeletionFlagActivated;
private TaskanaEngineImpl taskanaEngine; private TaskanaEngineImpl taskanaEngine;
private List<String> orderBy; private List<String> orderBy;
private List<String> orderColumns; private List<String> orderColumns;
@ -272,6 +274,12 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
return this; return this;
} }
@Override
public WorkbasketQuery deletionFlagEquals(Boolean deletionFlag) {
this.isDeletionFlagActivated = deletionFlag;
return this;
}
@Override @Override
public WorkbasketQuery orderByName(SortDirection sortDirection) { public WorkbasketQuery orderByName(SortDirection sortDirection) {
return addOrderCriteria("NAME", sortDirection); return addOrderCriteria("NAME", sortDirection);
@ -396,7 +404,7 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
this.columnName = columnName; this.columnName = columnName;
handleCallerRolesAndAccessIds(); handleCallerRolesAndAccessIds();
this.orderBy.clear(); this.orderBy.clear();
this.addOrderCriteria(columnName, sortDirection); //this.addOrderCriteria(columnName, sortDirection);
result = taskanaEngine.getSqlSession().selectList(LINK_TO_VALUEMAPPER, this); result = taskanaEngine.getSqlSession().selectList(LINK_TO_VALUEMAPPER, this);
return result; return result;
} finally { } finally {
@ -587,6 +595,10 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
return orgLevel4Like; return orgLevel4Like;
} }
public boolean isDeletionFlagActivated() {
return isDeletionFlagActivated;
}
public String[] getOwnerLike() { public String[] getOwnerLike() {
return ownerLike; return ownerLike;
} }
@ -688,6 +700,8 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
builder.append(Arrays.toString(orgLevel4In)); builder.append(Arrays.toString(orgLevel4In));
builder.append(", orgLevel4Like="); builder.append(", orgLevel4Like=");
builder.append(Arrays.toString(orgLevel4Like)); builder.append(Arrays.toString(orgLevel4Like));
builder.append(", deletionFlag=");
builder.append(isDeletionFlagActivated);
builder.append(", orderBy="); builder.append(", orderBy=");
builder.append(orderBy); builder.append(orderBy);
builder.append(", joinWithAccessList="); builder.append(", joinWithAccessList=");

View File

@ -3,12 +3,13 @@ package pro.taskana.impl;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import pro.taskana.BulkOperationResults;
import pro.taskana.TaskState; import pro.taskana.TaskState;
import pro.taskana.TaskanaEngine; import pro.taskana.TaskanaEngine;
import pro.taskana.TaskanaRole; import pro.taskana.TaskanaRole;
@ -23,6 +24,7 @@ import pro.taskana.exceptions.DomainNotFoundException;
import pro.taskana.exceptions.InvalidArgumentException; import pro.taskana.exceptions.InvalidArgumentException;
import pro.taskana.exceptions.InvalidWorkbasketException; import pro.taskana.exceptions.InvalidWorkbasketException;
import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.TaskanaException;
import pro.taskana.exceptions.WorkbasketAlreadyExistException; import pro.taskana.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.exceptions.WorkbasketInUseException; import pro.taskana.exceptions.WorkbasketInUseException;
import pro.taskana.exceptions.WorkbasketNotFoundException; import pro.taskana.exceptions.WorkbasketNotFoundException;
@ -725,7 +727,6 @@ public class WorkbasketServiceImpl implements WorkbasketService {
throws NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException, InvalidArgumentException { throws NotAuthorizedException, WorkbasketNotFoundException, WorkbasketInUseException, InvalidArgumentException {
LOGGER.debug("entry to deleteWorkbasket(workbasketId = {})", workbasketId); LOGGER.debug("entry to deleteWorkbasket(workbasketId = {})", workbasketId);
taskanaEngine.checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
HashMap response = new HashMap();
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
if (workbasketId == null || workbasketId.isEmpty()) { if (workbasketId == null || workbasketId.isEmpty()) {
@ -779,19 +780,80 @@ public class WorkbasketServiceImpl implements WorkbasketService {
WorkbasketImpl workbasket = workbasketMapper.findById(workbasketId); WorkbasketImpl workbasket = workbasketMapper.findById(workbasketId);
workbasket.setMarkedForDeletion(true); workbasket.setMarkedForDeletion(true);
workbasketMapper.update(workbasket); workbasketMapper.update(workbasket);
distributionTargetMapper.deleteAllDistributionTargetsBySourceId(workbasketId); deleteWorkbasketTablesReferences(workbasketId);
distributionTargetMapper.deleteAllDistributionTargetsByTargetId(workbasketId);
workbasketAccessMapper.deleteAllAccessItemsForWorkbasketId(workbasketId);
} finally { } finally {
taskanaEngine.returnConnection(); taskanaEngine.returnConnection();
LOGGER.debug("exit from markWorkbasketForDeletion(workbasketId = {}).", workbasketId); 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 @Override
public WorkbasketAccessItemQuery createWorkbasketAccessItemQuery() throws NotAuthorizedException { public WorkbasketAccessItemQuery createWorkbasketAccessItemQuery() throws NotAuthorizedException {
taskanaEngine.checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN); taskanaEngine.checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN);
return new WorkbasketAccessItemQueryImpl(this.taskanaEngine); 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;
}
} }

View File

@ -34,6 +34,8 @@ public abstract class AbstractTaskanaJob implements TaskanaJob {
return new TaskRefreshJob(engine, txProvider, job); return new TaskRefreshJob(engine, txProvider, job);
case TASKCLEANUPJOB: case TASKCLEANUPJOB:
return new TaskCleanupJob(engine, txProvider, job); return new TaskCleanupJob(engine, txProvider, job);
case WORKBASKETCLEANUPJOB:
return new WorkbasketCleanupJob(engine, txProvider, job);
default: default:
throw new TaskanaException( throw new TaskanaException(
"No matching job found for " + job.getType() + " of ScheduledJob " + job.getJobId() + "."); "No matching job found for " + job.getType() + " of ScheduledJob " + job.getJobId() + ".");

View File

@ -82,7 +82,7 @@ public class ClassificationChangedJob extends AbstractTaskanaJob {
} }
private void scheduleTaskRefreshJobs(Set<String> affectedTaskIds) { private void scheduleTaskRefreshJobs(Set<String> affectedTaskIds) {
int batchSize = taskanaEngineImpl.getConfiguration().getMaxNumberOfTaskUpdatesPerTransaction(); int batchSize = taskanaEngineImpl.getConfiguration().getMaxNumberOfUpdatesPerTransaction();
List<List<String>> affectedTaskBatches = partition(affectedTaskIds, batchSize); List<List<String>> affectedTaskBatches = partition(affectedTaskIds, batchSize);
LOGGER.debug("Creating {} TaskRefreshJobs out of {} affected tasks with a maximum number of {} tasks each. ", LOGGER.debug("Creating {} TaskRefreshJobs out of {} affected tasks with a maximum number of {} tasks each. ",
affectedTaskBatches.size(), affectedTaskIds.size(), batchSize); affectedTaskBatches.size(), affectedTaskIds.size(), batchSize);

View File

@ -63,11 +63,9 @@ public class JobRunner {
} }
private ScheduledJob lockJobTransactionally(ScheduledJob job) { private ScheduledJob lockJobTransactionally(ScheduledJob job) {
ScheduledJob lockedJob = null; ScheduledJob lockedJob;
if (txProvider != null) { if (txProvider != null) {
lockedJob = (ScheduledJob) txProvider.executeInTransaction(() -> { lockedJob = (ScheduledJob) txProvider.executeInTransaction(() -> lockJob(job));
return lockJob(job);
});
} else { } else {
lockedJob = lockJob(job); lockedJob = lockJob(job);
} }
@ -101,25 +99,9 @@ public class JobRunner {
jobService.deleteJob(scheduledJob); jobService.deleteJob(scheduledJob);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
// transaction was rolled back -> split job into 2 half sized jobs
LOGGER.warn( LOGGER.warn(
"Processing of job " + scheduledJob.getJobId() + " failed. Trying to split it up into two pieces...", "Processing of job " + scheduledJob.getJobId() + " failed. Trying to split it up into two pieces...",
e); 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);
} }
} }

View File

@ -150,6 +150,7 @@ public class ScheduledJob {
public enum Type { public enum Type {
CLASSIFICATIONCHANGEDJOB, CLASSIFICATIONCHANGEDJOB,
UPDATETASKSJOB, UPDATETASKSJOB,
TASKCLEANUPJOB; TASKCLEANUPJOB,
WORKBASKETCLEANUPJOB;
} }
} }

View File

@ -39,10 +39,10 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
public TaskCleanupJob(TaskanaEngine taskanaEngine, TaskanaTransactionProvider<Object> txProvider, public TaskCleanupJob(TaskanaEngine taskanaEngine, TaskanaTransactionProvider<Object> txProvider,
ScheduledJob scheduledJob) { ScheduledJob scheduledJob) {
super(taskanaEngine, txProvider, scheduledJob); super(taskanaEngine, txProvider, scheduledJob);
firstRun = taskanaEngine.getConfiguration().getTaskCleanupJobFirstRun(); firstRun = taskanaEngine.getConfiguration().getCleanupJobFirstRun();
runEvery = taskanaEngine.getConfiguration().getTaskCleanupJobRunEvery(); runEvery = taskanaEngine.getConfiguration().getCleanupJobRunEvery();
minimumAge = taskanaEngine.getConfiguration().getTaskCleanupJobMinimumAge(); minimumAge = taskanaEngine.getConfiguration().getCleanupJobMinimumAge();
batchSize = taskanaEngine.getConfiguration().getMaxNumberOfTaskUpdatesPerTransaction(); batchSize = taskanaEngine.getConfiguration().getMaxNumberOfUpdatesPerTransaction();
allCompletedSameParentBusiness = taskanaEngine.getConfiguration() allCompletedSameParentBusiness = taskanaEngine.getConfiguration()
.isTaskCleanupJobAllCompletedSameParentBusiness(); .isTaskCleanupJobAllCompletedSameParentBusiness();
} }
@ -62,10 +62,11 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
totalNumberOfTasksCompleted += deleteTasksTransactionally(tasksCompletedBefore.subList(0, upperLimit)); totalNumberOfTasksCompleted += deleteTasksTransactionally(tasksCompletedBefore.subList(0, upperLimit));
tasksCompletedBefore.subList(0, upperLimit).clear(); tasksCompletedBefore.subList(0, upperLimit).clear();
} }
scheduleNextCleanupJob();
LOGGER.info("Job ended successfully. {} tasks deleted.", totalNumberOfTasksCompleted); LOGGER.info("Job ended successfully. {} tasks deleted.", totalNumberOfTasksCompleted);
} catch (Exception e) { } catch (Exception e) {
throw new TaskanaException("Error while processing TaskCleanupJob.", 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> numberParentTasksShouldHave = new HashMap<>();
Map<String, Long> countParentTask = new HashMap<>(); Map<String, Long> countParentTask = new HashMap<>();
for (TaskSummary task : taskList) { for (TaskSummary task : taskList) {
numberParentTasksShouldHave.put(task.getParentBusinessProcessId(), numberParentTasksShouldHave.put(task.getParentBusinessProcessId(), taskanaEngineImpl.getTaskService()
taskanaEngineImpl.getTaskService() .createTaskQuery()
.createTaskQuery() .parentBusinessProcessIdIn(task.getParentBusinessProcessId())
.parentBusinessProcessIdIn(task.getParentBusinessProcessId()) .count());
.count());
countParentTask.merge(task.getParentBusinessProcessId(), 1L, Long::sum); countParentTask.merge(task.getParentBusinessProcessId(), 1L, Long::sum);
} }
@ -145,7 +145,7 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
return tasksIdsToBeDeleted.size() - results.getFailedIds().size(); return tasksIdsToBeDeleted.size() - results.getFailedIds().size();
} }
public void scheduleNextCleanupJob() { private void scheduleNextCleanupJob() {
LOGGER.debug("Entry to scheduleNextCleanupJob."); LOGGER.debug("Entry to scheduleNextCleanupJob.");
ScheduledJob job = new ScheduledJob(); ScheduledJob job = new ScheduledJob();
job.setType(ScheduledJob.Type.TASKCLEANUPJOB); job.setType(ScheduledJob.Type.TASKCLEANUPJOB);

View File

@ -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();
}
}

View File

@ -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='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='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='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 = 'joinWithAccessList'> "
+ "<if test = 'checkReadPermission'> " + "<if test = 'checkReadPermission'> "
+ "AND (a.MAX_READ = 1 " + "AND (a.MAX_READ = 1 "

View File

@ -65,9 +65,10 @@ public interface WorkbasketMapper {
@Result(property = "markedForDeletion", column = "MARKED_FOR_DELETION")}) @Result(property = "markedForDeletion", column = "MARKED_FOR_DELETION")})
WorkbasketImpl findByKeyAndDomain(@Param("key") String key, @Param("domain") String domain); 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}) " @Select(
+ "<if test=\"_databaseId == 'db2'\">with UR </if> " "<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}) "
+ "</script>") + "<if test=\"_databaseId == 'db2'\">with UR </if> "
+ "</script>")
@Results(value = { @Results(value = {
@Result(property = "id", column = "ID"), @Result(property = "id", column = "ID"),
@Result(property = "key", column = "KEY"), @Result(property = "key", column = "KEY"),
@ -86,10 +87,11 @@ public interface WorkbasketMapper {
@Result(property = "orgLevel4", column = "ORG_LEVEL_4")}) @Result(property = "orgLevel4", column = "ORG_LEVEL_4")})
List<WorkbasketSummaryImpl> findDistributionTargets(@Param("id") String id); 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 " @Select(
+ " WHERE ID IN (SELECT SOURCE_ID FROM DISTRIBUTION_TARGETS WHERE TARGET_ID = #{id}) " "<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 "
+ "<if test=\"_databaseId == 'db2'\">with UR </if> " + " WHERE ID IN (SELECT SOURCE_ID FROM DISTRIBUTION_TARGETS WHERE TARGET_ID = #{id}) "
+ "</script>") + "<if test=\"_databaseId == 'db2'\">with UR </if> "
+ "</script>")
@Results(value = { @Results(value = {
@Result(property = "id", column = "ID"), @Result(property = "id", column = "ID"),
@Result(property = "key", column = "KEY"), @Result(property = "key", column = "KEY"),
@ -150,6 +152,12 @@ public interface WorkbasketMapper {
@Result(property = "orgLevel4", column = "ORG_LEVEL_4")}) @Result(property = "orgLevel4", column = "ORG_LEVEL_4")})
List<WorkbasketSummaryImpl> findAll(); 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}) " @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>") + "</script>")
@Options(keyProperty = "id", keyColumn = "ID") @Options(keyProperty = "id", keyColumn = "ID")

View File

@ -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);
}
}

View File

@ -38,6 +38,7 @@ public class JobScheduler {
public void scheduleCleanupJob() { public void scheduleCleanupJob() {
LOGGER.debug("Entry to scheduleCleanupJob."); LOGGER.debug("Entry to scheduleCleanupJob.");
TaskCleanupJob.initializeSchedule(taskanaEngine); TaskCleanupJob.initializeSchedule(taskanaEngine);
WorkbasketCleanupJob.initializeSchedule(taskanaEngine);
LOGGER.debug("Exit from scheduleCleanupJob."); LOGGER.debug("Exit from scheduleCleanupJob.");
} }