Coverage Summary for Class: WorkflowControllerService (org.kitodo.production.services.workflow)
Class |
Class, %
|
Method, %
|
Line, %
|
WorkflowControllerService |
100%
(1/1)
|
85%
(34/40)
|
70,6%
(230/326)
|
/*
* (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
*
* This file is part of the Kitodo project.
*
* It is licensed under GNU General Public License version 3 or later.
*
* For the full copyright and license information, please read the
* GPL3-License.txt file that was distributed with this source code.
*/
package org.kitodo.production.services.workflow;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.api.command.CommandResult;
import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface;
import org.kitodo.api.dataformat.Workpiece;
import org.kitodo.api.validation.State;
import org.kitodo.api.validation.ValidationResult;
import org.kitodo.config.ConfigCore;
import org.kitodo.config.enums.ParameterCore;
import org.kitodo.data.database.beans.Comment;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Task;
import org.kitodo.data.database.beans.User;
import org.kitodo.data.database.beans.WorkflowCondition;
import org.kitodo.data.database.enums.TaskEditType;
import org.kitodo.data.database.enums.TaskStatus;
import org.kitodo.data.database.enums.WorkflowConditionType;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.elasticsearch.index.converter.ProcessConverter;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.helper.VariableReplacer;
import org.kitodo.production.helper.WebDav;
import org.kitodo.production.helper.metadata.ImageHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyPrefsHelper;
import org.kitodo.production.helper.tasks.TaskManager;
import org.kitodo.production.metadata.MetadataLock;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.data.TaskService;
import org.kitodo.production.thread.TaskScriptThread;
public class WorkflowControllerService {
private List<Task> automaticTasks = new ArrayList<>();
private boolean flagWait = false;
private final ReentrantLock flagWaitLock = new ReentrantLock();
private final WebDav webDav = new WebDav();
private static final Logger logger = LogManager.getLogger(WorkflowControllerService.class);
private final TaskService taskService = ServiceManager.getTaskService();
/**
* Set Task status up.
*
* @param task
* to change status up
*/
public void setTaskStatusUp(Task task) throws DataException, IOException, DAOException {
setTaskStatusUp(Collections.singletonList(task));
}
/**
* Set Task status up.
*
* @param tasks
* to change status up
*/
public void setTaskStatusUp(List<Task> tasks) throws DataException, IOException, DAOException {
for (Task task : tasks) {
if (task.getProcessingStatus() != TaskStatus.DONE) {
setProcessingStatusUp(task);
task.setEditType(TaskEditType.ADMIN);
if (task.getProcessingStatus() == TaskStatus.DONE) {
close(task);
} else {
task.setProcessingTime(new Date());
taskService.replaceProcessingUser(task, getCurrentUser());
ServiceManager.getTaskService().save(task);
ServiceManager.getProcessService().save(task.getProcess());
}
}
}
}
/**
* Change Task status down.
*
* @param task
* to change status down
*/
public void setTaskStatusDown(Task task) {
setTaskStatusDown(Collections.singletonList(task));
}
/**
* Change Task status down.
*
* @param tasks
* to change status down
*/
public void setTaskStatusDown(List<Task> tasks) {
for (Task task : tasks) {
task.setEditType(TaskEditType.ADMIN);
task.setProcessingTime(new Date());
taskService.replaceProcessingUser(task, getCurrentUser());
setProcessingStatusDown(task);
if (task.getProcessingStatus() == TaskStatus.LOCKED) {
List<Task> previousTasks = getPreviousTasks(task);
for (Task previousTask : previousTasks) {
setProcessingStatusDown(previousTask);
}
}
}
}
/**
* Change Task status up for list of tasks assigned to given Process.
*
* @param process
* object
*/
public void setTasksStatusUp(Process process) throws DataException, IOException, DAOException {
List<Task> currentTask = ServiceManager.getProcessService().getCurrentTasks(process);
if (currentTask.isEmpty()) {
activateNextTasks(process.getTasks());
return;
}
setTaskStatusUp(currentTask);
}
/**
* Change Task status down for list of tasks assigned to given Process.
*
* @param process
* object
*/
public void setTasksStatusDown(Process process) {
List<Task> currentTask = ServiceManager.getProcessService().getCurrentTasks(process);
if (currentTask.isEmpty()) {
currentTask = getLastClosedTask(process);
}
setTaskStatusDown(currentTask);
}
private List<Task> getLastClosedTask(Process process) {
List<Task> lastOpenTasks = new ArrayList<>();
int ordering = 0;
for (Task task : process.getTasks()) {
if (TaskStatus.DONE.equals(task.getProcessingStatus())) {
if (task.getOrdering() > ordering) {
lastOpenTasks.clear();
}
lastOpenTasks.add(task);
}
}
return lastOpenTasks;
}
private boolean validateMetadata(Task task) throws IOException, DAOException {
URI metadataFileUri = ServiceManager.getProcessService().getMetadataFileUri(task.getProcess());
Workpiece workpiece = ServiceManager.getMetsService().loadWorkpiece(metadataFileUri);
RulesetManagementInterface ruleset = ServiceManager.getRulesetManagementService().getRulesetManagement();
ruleset.load(new File(Paths.get(
ConfigCore.getParameter(ParameterCore.DIR_RULESETS),
task.getProcess().getRuleset().getFile()).toString()));
ValidationResult validationResult = ServiceManager.getMetadataValidationService().validate(workpiece, ruleset);
boolean strictValidation = ConfigCore.getBooleanParameter(ParameterCore.VALIDATION_FAIL_ON_WARNING);
State state = validationResult.getState();
if (State.ERROR.equals(state) || (strictValidation && !State.SUCCESS.equals(state))) {
Helper.setErrorMessage(Helper.getTranslation("dataEditor.validation.state.error"));
for (String message : validationResult.getResultMessages()) {
Helper.setErrorMessage(message);
}
}
if (strictValidation) {
return State.SUCCESS.equals(state);
} else {
return !State.ERROR.equals(state);
}
}
/**
* Close method task called by user action.
*
* @param task
* object
*/
public void closeTaskByUser(Task task) throws DataException, IOException, DAOException {
// if the result of the task is to be verified first, then if necessary,
// cancel the completion
if (task.isTypeCloseVerify()) {
// metadata validation
if (task.isTypeMetadata()
&& ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.USE_META_DATA_VALIDATION)
&& !validateMetadata(task)) {
throw new DataException("Error on metadata validation!");
}
// image validation
if (task.isTypeImagesWrite()) {
ImageHelper mih = new ImageHelper();
URI imageFolder = ServiceManager.getProcessService().getImagesOriginDirectory(false, task.getProcess());
if (!mih.checkIfImagesValid(task.getProcess().getTitle(), imageFolder)) {
throw new DataException("Error on image validation!");
}
}
}
// unlock the process
MetadataLock.setFree(task.getProcess().getId());
if (task.isTypeImagesRead() || task.isTypeImagesWrite()) {
this.webDav.uploadFromHome(task.getProcess());
}
task.setEditType(TaskEditType.MANUAL_SINGLE);
close(task);
}
/**
* Close task.
*
* @param task
* as Task object
*/
public void close(Task task) throws DataException, IOException, DAOException {
task.setProcessingStatus(TaskStatus.DONE);
task.setCorrection(false);
task.setProcessingTime(new Date());
User user = null;
if (!task.isTypeAutomatic()) {
user = getCurrentUser();
}
taskService.replaceProcessingUser(task, user);
task.setProcessingEnd(new Date());
taskService.save(task);
automaticTasks = new ArrayList<>();
activateTasksForClosedTask(task);
}
/**
* Checks if all children of a process are closed.
* @param process the process to check
* @return true if all children are closed
*/
public static boolean allChildrenClosed(Process process) {
if (!process.getChildren().isEmpty()) {
boolean allChildrenClosed = true;
for (Process child : process.getChildren()) {
allChildrenClosed &= "100000000".equals(child.getSortHelperStatus())
|| "100000000000".equals(child.getSortHelperStatus());
}
return allChildrenClosed;
}
return false;
}
/**
* Taken from CurrentTaskForm.
*
* @param task
* object
*/
public void assignTaskToUser(Task task) {
this.flagWaitLock.lock();
try {
if (!this.flagWait) {
this.flagWait = true;
task.setProcessingStatus(TaskStatus.INWORK);
task.setEditType(TaskEditType.MANUAL_SINGLE);
task.setProcessingTime(new Date());
taskService.replaceProcessingUser(task, getCurrentUser());
if (Objects.isNull(task.getProcessingBegin())) {
task.setProcessingBegin(new Date());
}
Process process = task.getProcess();
List<Task> concurrentTasks = getConcurrentTasksForClose(process.getTasks(), task);
for (Task concurrentTask : concurrentTasks) {
concurrentTask.setProcessingStatus(TaskStatus.LOCKED);
taskService.save(concurrentTask);
}
updateProcessSortHelperStatus(process);
// if it is an image task, then download the images into the
// user home directory
if (task.isTypeImagesRead() || task.isTypeImagesWrite()) {
downloadToHome(task);
}
} else {
Helper.setErrorMessage("stepInWorkError");
}
this.flagWait = false;
} catch (DataException e) {
Helper.setErrorMessage("stepSaveError", logger, e);
} finally {
this.flagWaitLock.unlock();
}
}
/**
* Unassing user from task.
*
* @param task
* object
*/
public void unassignTaskFromUser(Task task) throws DataException {
if (task.isTypeImagesRead() || task.isTypeImagesWrite()) {
this.webDav.uploadFromHome(task.getProcess());
}
task.setProcessingStatus(TaskStatus.OPEN);
taskService.replaceProcessingUser(task, null);
// if we have a correction task here then never remove startdate
if (task.isCorrection()) {
task.setProcessingBegin(null);
}
task.setEditType(TaskEditType.MANUAL_SINGLE);
task.setProcessingTime(new Date());
taskService.save(task);
// unlock the process
MetadataLock.setFree(task.getProcess().getId());
updateProcessSortHelperStatus(task.getProcess());
}
/**
* Unified method for report problem .
*
* @param comment as Comment object
*/
public void reportProblem(Comment comment) throws DataException {
Task currentTask = comment.getCurrentTask();
if (currentTask.isTypeImagesRead() || currentTask.isTypeImagesWrite()) {
this.webDav.uploadFromHome(getCurrentUser(), comment.getProcess());
}
Date date = new Date();
currentTask.setProcessingStatus(TaskStatus.LOCKED);
currentTask.setEditType(TaskEditType.MANUAL_SINGLE);
currentTask.setProcessingTime(date);
taskService.replaceProcessingUser(currentTask, getCurrentUser());
currentTask.setProcessingBegin(null);
taskService.save(currentTask);
Task correctionTask = comment.getCorrectionTask();
correctionTask.setProcessingStatus(TaskStatus.OPEN);
correctionTask.setProcessingEnd(null);
correctionTask.setCorrection(true);
taskService.save(correctionTask);
lockTasksBetweenCurrentAndCorrectionTask(currentTask, correctionTask);
updateProcessSortHelperStatus(currentTask.getProcess());
}
/**
* Unified method for solve problem.
*
* @param comment
* as Comment object
*/
public void solveProblem(Comment comment) throws DataException, DAOException, IOException {
closeTaskByUser(comment.getCorrectionTask());
comment.setCurrentTask(ServiceManager.getTaskService().getById(comment.getCurrentTask().getId()));
comment.setCorrectionTask(ServiceManager.getTaskService().getById(comment.getCorrectionTask().getId()));
comment.setCorrected(Boolean.TRUE);
comment.setCorrectionDate(new Date());
try {
ServiceManager.getCommentService().saveToDatabase(comment);
} catch (DAOException e) {
Helper.setErrorMessage("errorSaving", new Object[] {"comment"}, logger, e);
}
}
/**
* Set processing status up. This method adds double check of task status.
*
* @param task
* object
*/
private void setProcessingStatusUp(Task task) {
if (task.getProcessingStatus() != TaskStatus.DONE) {
TaskStatus newTaskStatus = TaskStatus.getStatusFromValue(task.getProcessingStatus().getValue() + 1);
task.setProcessingStatus(newTaskStatus);
}
}
/**
* Set processing status down. This method adds double check of task status.
*
* @param task
* object
*/
private void setProcessingStatusDown(Task task) {
if (task.getProcessingStatus() != TaskStatus.LOCKED) {
TaskStatus newTaskStatus = TaskStatus.getStatusFromValue(task.getProcessingStatus().getValue() - 1);
task.setProcessingStatus(newTaskStatus);
}
}
private void activateTasksForClosedTask(Task closedTask) throws DataException, IOException, DAOException {
Process process = closedTask.getProcess();
// check if there are tasks that take place in parallel but are not yet
// completed
List<Task> tasks = process.getTasks();
List<Task> concurrentTasksForOpen = getConcurrentTasksForOpen(tasks, closedTask);
if (concurrentTasksForOpen.isEmpty() && !isAnotherTaskInWorkWhichBlocksOtherTasks(tasks, closedTask)) {
if (!closedTask.isLast()) {
activateNextTasks(getAllHigherTasks(tasks, closedTask));
}
} else {
activateConcurrentTasks(concurrentTasksForOpen);
}
process = ServiceManager.getProcessService().getById(process.getId());
URI imagesOrigDirectory = ServiceManager.getProcessService().getImagesOriginDirectory(true, process);
Integer numberOfFiles = ServiceManager.getFileService().getNumberOfFiles(imagesOrigDirectory);
if (!process.getSortHelperImages().equals(numberOfFiles)) {
process.setSortHelperImages(numberOfFiles);
}
ServiceManager.getProcessService().save(process);
process = ServiceManager.getProcessService().getById(process.getId());
for (Task automaticTask : automaticTasks) {
automaticTask.setProcessingBegin(new Date());
TaskScriptThread thread = new TaskScriptThread(automaticTask);
TaskManager.addTask(thread);
}
closeParent(process);
}
private void closeParent(Process process) throws DataException {
if (Objects.nonNull(process.getParent()) && allChildrenClosed(process.getParent())) {
process.getParent().setSortHelperStatus("100000000");
ServiceManager.getProcessService().save(process.getParent());
closeParent(process.getParent());
}
}
private void lockTasksBetweenCurrentAndCorrectionTask(Task currentTask, Task correctionTask) throws DataException {
List<Task> allTasksInBetween = taskService.getAllTasksInBetween(correctionTask.getOrdering(),
currentTask.getOrdering(), currentTask.getProcess().getId());
for (Task taskInBetween : allTasksInBetween) {
taskInBetween.setProcessingStatus(TaskStatus.LOCKED);
taskInBetween.setCorrection(true);
taskInBetween.setProcessingEnd(null);
taskService.save(taskInBetween);
}
}
private List<Task> getAllHigherTasks(List<Task> tasks, Task task) {
List<Task> allHigherTasks = new ArrayList<>();
for (Task tempTask : tasks) {
if (tempTask.getOrdering() > task.getOrdering()) {
allHigherTasks.add(tempTask);
}
}
allHigherTasks.sort(Comparator.comparing(Task::getOrdering));
return allHigherTasks;
}
private List<Task> getConcurrentTasksForClose(List<Task> tasks, Task task) {
List<Task> allConcurrentTasks = new ArrayList<>();
for (Task tempTask : tasks) {
if (tempTask.getOrdering().equals(task.getOrdering()) && tempTask.getProcessingStatus().getValue() < 2
&& !tempTask.getId().equals(task.getId()) && !tempTask.isConcurrent()) {
allConcurrentTasks.add(tempTask);
}
}
return allConcurrentTasks;
}
private List<Task> getConcurrentTasksForOpen(List<Task> tasks, Task task) {
boolean blocksOtherTasks = isAnotherTaskInWorkWhichBlocksOtherTasks(tasks, task);
List<Task> allConcurrentTasks = new ArrayList<>();
for (Task tempTask : tasks) {
if (tempTask.getOrdering().equals(task.getOrdering()) && tempTask.getProcessingStatus().getValue() < 3
&& !tempTask.getId().equals(task.getId())) {
if (blocksOtherTasks) {
if (tempTask.isConcurrent()) {
allConcurrentTasks.add(tempTask);
}
} else {
allConcurrentTasks.add(tempTask);
}
}
}
return allConcurrentTasks;
}
private boolean isAnotherTaskInWorkWhichBlocksOtherTasks(List<Task> tasks, Task task) {
for (Task tempTask : tasks) {
if (tempTask.getOrdering().equals(task.getOrdering()) && tempTask.getProcessingStatus() == TaskStatus.INWORK
&& !tempTask.getId().equals(task.getId()) && !tempTask.isConcurrent()) {
return true;
}
}
return false;
}
/**
* Activate the concurrent tasks.
*/
private void activateConcurrentTasks(List<Task> concurrentTasks) throws DataException, IOException, DAOException {
for (Task concurrentTask : concurrentTasks) {
if (concurrentTask.getProcessingStatus().equals(TaskStatus.LOCKED)) {
activateTask(concurrentTask);
}
}
}
/**
* If no open parallel tasks are available, activate the next tasks.
*/
public void activateNextTasks(List<Task> allHigherTasks) throws DataException, IOException, DAOException {
List<Task> nextTasks = getNextTasks(allHigherTasks);
for (Task nextTask : nextTasks) {
activateTask(nextTask);
}
}
private List<Task> getNextTasks(List<Task> allHigherTasks) {
int ordering = 0;
boolean matched = false;
List<Task> nextTasks = new ArrayList<>();
for (Task higherTask : allHigherTasks) {
if (ordering < higherTask.getOrdering() && !matched) {
ordering = higherTask.getOrdering();
}
if (ordering == higherTask.getOrdering() && higherTask.getProcessingStatus().getValue() < 2) {
nextTasks.add(higherTask);
matched = true;
}
}
return nextTasks;
}
private List<Task> getPreviousTasks(Task higherTask) {
List<Task> tasks = higherTask.getProcess().getTasks();
boolean isConcurrentOpenTask = false;
List<Task> previousTasks = new ArrayList<>();
List<Task> concurrentTasks = getConcurrentTasksForClose(tasks, higherTask);
for (Task concurrentTask : concurrentTasks) {
if (concurrentTask.getProcessingStatus().equals(TaskStatus.LOCKED)) {
isConcurrentOpenTask = true;
break;
}
}
if (!isConcurrentOpenTask) {
boolean matched = false;
int ordering = higherTask.getOrdering() - 1;
for (Task task : tasks) {
if (task.getOrdering() > ordering && task.getOrdering() < higherTask.getOrdering() && !matched) {
ordering = task.getOrdering();
}
if (ordering == task.getOrdering()) {
previousTasks.add(task);
matched = true;
}
}
}
return previousTasks;
}
/**
* If no open parallel tasks are available, activate the next tasks.
*/
private void activateTask(Task task) throws DataException, IOException, DAOException {
if ((!task.isCorrection() || task.isRepeatOnCorrection())
&& isWorkflowConditionFulfilled(task.getProcess(), task.getWorkflowCondition())) {
// activate the task if it is not fully automatic
task.setProcessingStatus(TaskStatus.OPEN);
task.setProcessingTime(new Date());
task.setEditType(TaskEditType.AUTOMATIC);
processAutomaticTask(task);
taskService.save(task);
} else {
// close task as it is not going to be executed
task.setProcessingStatus(TaskStatus.DONE);
task.setProcessingTime(new Date());
task.setProcessingEnd(new Date());
task.setEditType(TaskEditType.AUTOMATIC);
task.setCorrection(false);
taskService.save(task);
activateTasksForClosedTask(task);
}
}
private boolean isWorkflowConditionFulfilled(Process process, WorkflowCondition workflowCondition)
throws IOException {
if (Objects.isNull(workflowCondition) || workflowCondition.getType().equals(WorkflowConditionType.NONE)) {
return true;
} else {
if (workflowCondition.getType().equals(WorkflowConditionType.SCRIPT)) {
return runScriptCondition(workflowCondition.getValue(), process);
}
if (workflowCondition.getType().equals(WorkflowConditionType.XPATH)) {
return runXPathCondition(process, workflowCondition.getValue());
}
return true;
}
}
private boolean runScriptCondition(String script, Process process) throws IOException {
LegacyPrefsHelper legacyPrefsHelper = ServiceManager.getRulesetService().getPreferences(process.getRuleset());
LegacyMetsModsDigitalDocumentHelper legacyMetsModsDigitalDocumentHelper = ServiceManager.getProcessService()
.readMetadataFile(ServiceManager.getFileService().getMetadataFilePath(process), legacyPrefsHelper)
.getDigitalDocument();
VariableReplacer replacer = new VariableReplacer(legacyMetsModsDigitalDocumentHelper.getWorkpiece(),
process, null);
script = replacer.replace(script);
CommandResult commandResult = ServiceManager.getCommandService().runCommand(script);
return commandResult.isSuccessful();
}
private boolean runXPathCondition(Process process, String xpath) throws IOException {
return ServiceManager.getProcessService().getNodeListFromMetadataFile(process, xpath).getLength() > 0;
}
private void processAutomaticTask(Task task) {
// if it is an automatic task with script
if (task.isTypeAutomatic()) {
task.setProcessingStatus(TaskStatus.INWORK);
automaticTasks.add(task);
}
}
/**
* Update process sort helper status.
*
* @param process
* object
*/
public static void updateProcessSortHelperStatus(Process process) {
if (!process.getTasks().isEmpty()) {
String value = ProcessConverter.getCombinedProgressAsString(process, false);
process.setSortHelperStatus(value);
}
}
/**
* Download to user home directory.
*
* @param task
* object
*/
private void downloadToHome(Task task) {
task.setProcessingTime(new Date());
if (ServiceManager.getSecurityAccessService().isAuthenticated()) {
taskService.replaceProcessingUser(task, getCurrentUser());
this.webDav.downloadToHome(task.getProcess(), !task.isTypeImagesWrite());
}
}
private User getCurrentUser() {
return ServiceManager.getUserService().getCurrentUser();
}
/**
* Set up processing status for given list of processes.
*/
public void setTaskStatusUpForProcesses(List<Process> processes) {
for (Process processForStatus : processes) {
try {
setTasksStatusUp(processForStatus);
} catch (DataException | IOException | DAOException e) {
Helper.setErrorMessage("errorChangeTaskStatus",
new Object[] {Helper.getTranslation("up"), processForStatus.getId() }, logger, e);
}
}
}
/**
* Set down processing status for given list of processes.
*/
public void setTaskStatusDownForProcesses(List<Process> processes) {
for (Process processForStatus : processes) {
try {
setTasksStatusDown(processForStatus);
ServiceManager.getProcessService().save(processForStatus, true);
updateProcessSortHelperStatus(processForStatus);
} catch (DataException e) {
Helper.setErrorMessage("errorChangeTaskStatus",
new Object[] {Helper.getTranslation("down"), processForStatus.getId() }, logger, e);
}
}
}
}