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

View File

@ -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.
*

View File

@ -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) {

View File

@ -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=");

View File

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

View File

@ -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() + ".");

View File

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

View File

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

View File

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

View File

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

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='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 "

View File

@ -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")

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() {
LOGGER.debug("Entry to scheduleCleanupJob.");
TaskCleanupJob.initializeSchedule(taskanaEngine);
WorkbasketCleanupJob.initializeSchedule(taskanaEngine);
LOGGER.debug("Exit from scheduleCleanupJob.");
}