Coverage Summary for Class: TaskService (org.kitodo.production.services.data)
Class |
Class, %
|
Method, %
|
Line, %
|
TaskService |
100%
(1/1)
|
39,7%
(25/63)
|
35,3%
(100/283)
|
/*
* (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.data;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.kitodo.api.command.CommandResult;
import org.kitodo.data.database.beans.Folder;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Project;
import org.kitodo.data.database.beans.Role;
import org.kitodo.data.database.beans.Task;
import org.kitodo.data.database.beans.Template;
import org.kitodo.data.database.beans.User;
import org.kitodo.data.database.enums.IndexAction;
import org.kitodo.data.database.enums.TaskEditType;
import org.kitodo.data.database.enums.TaskStatus;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.database.persistence.BaseDAO;
import org.kitodo.data.database.persistence.TaskDAO;
import org.kitodo.data.elasticsearch.exceptions.CustomResponseException;
import org.kitodo.data.elasticsearch.index.Indexer;
import org.kitodo.data.elasticsearch.index.type.TaskType;
import org.kitodo.data.elasticsearch.index.type.enums.TaskTypeField;
import org.kitodo.data.elasticsearch.search.Searcher;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.exceptions.InvalidImagesException;
import org.kitodo.exceptions.MediaNotFoundException;
import org.kitodo.export.ExportDms;
import org.kitodo.production.dto.ProjectDTO;
import org.kitodo.production.dto.TaskDTO;
import org.kitodo.production.dto.UserDTO;
import org.kitodo.production.enums.GenerationMode;
import org.kitodo.production.enums.ObjectType;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.helper.SearchResultGeneration;
import org.kitodo.production.helper.VariableReplacer;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyPrefsHelper;
import org.kitodo.production.helper.tasks.EmptyTask;
import org.kitodo.production.model.Subfolder;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.command.CommandService;
import org.kitodo.production.services.command.KitodoScriptService;
import org.kitodo.production.services.data.base.ProjectSearchService;
import org.kitodo.production.services.file.SubfolderFactoryService;
import org.kitodo.production.services.image.ImageGenerator;
import org.kitodo.production.services.workflow.WorkflowControllerService;
import org.primefaces.model.SortOrder;
/**
* The class provides a service for tasks. The service can be used to perform
* functions on the task because the task itself is a database bean and
* therefore may not include functionality.
*/
public class TaskService extends ProjectSearchService<Task, TaskDTO, TaskDAO> {
private static final Logger logger = LogManager.getLogger(TaskService.class);
private static volatile TaskService instance = null;
/**
* Constructor with Searcher and Indexer assigning.
*/
private TaskService() {
super(new TaskDAO(), new TaskType(), new Indexer<>(Task.class), new Searcher(Task.class),
TaskTypeField.CLIENT_ID.getKey(), TaskTypeField.RELATED_PROJECT_IDS.getKey());
}
/**
* Return singleton variable of type TaskService.
*
* @return unique instance of TaskService
*/
public static TaskService getInstance() {
TaskService localReference = instance;
if (Objects.isNull(localReference)) {
synchronized (TaskService.class) {
localReference = instance;
if (Objects.isNull(localReference)) {
localReference = new TaskService();
instance = localReference;
}
}
}
return localReference;
}
/**
* Creates and returns a query to retrieve tasks for which the currently
* logged in user is eligible.
*
* @return query to retrieve tasks for which the user eligible.
*/
private BoolQueryBuilder createUserTaskQuery(String filter, boolean onlyOwnTasks, boolean hideCorrectionTasks,
boolean showAutomaticTasks, List<TaskStatus> taskStatusRestrictions) {
User user = ServiceManager.getUserService().getAuthenticatedUser();
BoolQueryBuilder query = new BoolQueryBuilder();
query.must(getQueryForTemplate(0));
if (Objects.isNull(filter)) {
filter = "";
}
SearchResultGeneration searchResultGeneration = new SearchResultGeneration(filter, true, true);
query.must(searchResultGeneration.getQueryForFilter(ObjectType.TASK));
query.must(getQueryForProcessingStatuses(taskStatusRestrictions.stream()
.map(TaskStatus::getValue).collect(Collectors.toSet())));
if (onlyOwnTasks) {
query.must(getQueryForProcessingUser(user.getId()));
} else {
BoolQueryBuilder subQuery = new BoolQueryBuilder();
subQuery.should(getQueryForProcessingUser(user.getId()));
for (Role role : user.getRoles()) {
subQuery.should(createSimpleQuery(TaskTypeField.ROLES + ".id", role.getId(), true));
}
query.must(subQuery);
}
if (hideCorrectionTasks) {
query.must(createSimpleQuery(TaskTypeField.CORRECTION.getKey(), false, true));
}
if (!showAutomaticTasks) {
query.must(getQueryForTypeAutomatic(false));
}
return query;
}
@Override
public Long countDatabaseRows() throws DAOException {
return countDatabaseRows("SELECT COUNT(*) FROM Task WHERE " + BaseDAO.getDateFilter("processingBegin"));
}
@Override
public Long countNotIndexedDatabaseRows() throws DAOException {
return countDatabaseRows("SELECT COUNT(*) FROM Task WHERE " + BaseDAO.getDateFilter("processingBegin")
+ " AND (indexAction = 'INDEX' OR indexAction IS NULL)");
}
@Override
public Long countResults(Map filters) throws DataException {
return countResults(new HashMap<String, String>(filters), false, false, false, null);
}
public Long countResults(HashMap<String, String> filters, boolean onlyOwnTasks, boolean hideCorrectionTasks,
boolean showAutomaticTasks, List<TaskStatus> taskStatus)
throws DataException {
return countDocuments(createUserTaskQuery(ServiceManager.getFilterService().parseFilterString(filters),
onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus));
}
@Override
public List<Task> getAllNotIndexed() {
return getByQuery("FROM Task WHERE " + BaseDAO.getDateFilter("processingBegin")
+ " AND (indexAction = 'INDEX' OR indexAction IS NULL)");
}
@Override
public List<Task> getAllForSelectedClient() {
throw new UnsupportedOperationException();
}
@Override
public List<TaskDTO> loadData(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters)
throws DataException {
return loadData(first, pageSize, sortField, sortOrder, filters, false, false, false,
Arrays.asList(TaskStatus.OPEN, TaskStatus.INWORK));
}
/**
* Load tasks with given parameters.
* @param first index of first task to load
* @param pageSize number of tasks to load
* @param sortField name of field by which tasks are sorted
* @param sortOrder SortOrder by which tasks are sorted - either ascending or descending
* @param filters filter map
* @param onlyOwnTasks boolean controlling whether to load only tasks assigned to current user or not
* @param hideCorrectionTasks boolean controlling whether to load correction tasks or not
* @param showAutomaticTasks boolean controlling whether to load automatic tasks or not
* @param taskStatus list of TaskStatus by which tasks are filtered
* @return List of loaded tasks
* @throws DataException if tasks cannot be loaded from search index
*/
public List<TaskDTO> loadData(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters,
boolean onlyOwnTasks, boolean hideCorrectionTasks, boolean showAutomaticTasks,
List<TaskStatus> taskStatus)
throws DataException {
if ("process.creationDate".equals(sortField)) {
sortField = "processForTask.creationDate";
}
String filter = ServiceManager.getFilterService().parseFilterString(filters);
return findByQuery(createUserTaskQuery(filter, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks,
taskStatus), getSortBuilder(sortField, sortOrder), first, pageSize, false);
}
/**
* Method saves or removes dependencies with process, users and user's
* groups related to modified task.
*
* @param task
* object
*/
@Override
protected void manageDependenciesForIndex(Task task) throws CustomResponseException, DataException, IOException {
if (Objects.nonNull(task.getProcess())) {
manageProcessDependenciesForIndex(task);
} else if (Objects.nonNull(task.getTemplate())) {
manageTemplateDependenciesForIndex(task);
}
}
private void manageProcessDependenciesForIndex(Task task)
throws CustomResponseException, DataException, IOException {
Process process = task.getProcess();
if (task.getIndexAction() == IndexAction.DELETE) {
process.getTasks().remove(task);
ServiceManager.getProcessService().saveToIndex(process, false);
} else {
ServiceManager.getProcessService().saveToIndex(process, false);
}
}
private void manageTemplateDependenciesForIndex(Task task)
throws CustomResponseException, DataException, IOException {
Template template = task.getTemplate();
if (task.getIndexAction().equals(IndexAction.DELETE)) {
template.getTasks().remove(task);
ServiceManager.getTemplateService().saveToIndex(template, false);
} else {
ServiceManager.getTemplateService().saveToIndex(template, false);
}
}
/**
* Replace processing user for given task. Handles add/remove from list of
* processing tasks.
*
* @param task
* for which user will be assigned as processing user
* @param user
* which will process given task
*/
public void replaceProcessingUser(Task task, User user) {
User currentProcessingUser = task.getProcessingUser();
if (Objects.isNull(user) && Objects.isNull(currentProcessingUser)) {
logger.info("do nothing - there is neither a new nor an old user");
} else if (Objects.isNull(user)) {
currentProcessingUser.getProcessingTasks().remove(task);
task.setProcessingUser(null);
} else if (Objects.isNull(currentProcessingUser)) {
user.getProcessingTasks().add(task);
task.setProcessingUser(user);
} else if (Objects.equals(currentProcessingUser.getId(), user.getId())) {
logger.info("do nothing - both are the same");
} else {
currentProcessingUser.getProcessingTasks().remove(task);
user.getProcessingTasks().add(task);
task.setProcessingUser(user);
}
}
/**
* Find the distinct task titles.
*
* @return a list of titles
*/
public List<String> findTaskTitlesDistinct() throws DataException, DAOException {
return findDistinctValues(QueryBuilders.matchAllQuery(), "title.keyword", true, countDatabaseRows());
}
@Override
public TaskDTO convertJSONObjectToDTO(Map<String, Object> jsonObject, boolean related) throws DataException {
TaskDTO taskDTO = new TaskDTO();
taskDTO.setId(getIdFromJSONObject(jsonObject));
taskDTO.setTitle(TaskTypeField.TITLE.getStringValue(jsonObject));
taskDTO.setLocalizedTitle(getLocalizedTitle(taskDTO.getTitle()));
taskDTO.setOrdering(TaskTypeField.ORDERING.getIntValue(jsonObject));
int taskStatus = TaskTypeField.PROCESSING_STATUS.getIntValue(jsonObject);
taskDTO.setProcessingStatus(TaskStatus.getStatusFromValue(taskStatus));
taskDTO.setProcessingStatusTitle(Helper.getTranslation(taskDTO.getProcessingStatus().getTitle()));
int editType = TaskTypeField.EDIT_TYPE.getIntValue(jsonObject);
taskDTO.setEditType(TaskEditType.getTypeFromValue(editType));
taskDTO.setEditTypeTitle(Helper.getTranslation(taskDTO.getEditType().getTitle()));
taskDTO.setProcessingTime(TaskTypeField.PROCESSING_TIME.getStringValue(jsonObject));
taskDTO.setProcessingBegin(TaskTypeField.PROCESSING_BEGIN.getStringValue(jsonObject));
taskDTO.setProcessingEnd(TaskTypeField.PROCESSING_END.getStringValue(jsonObject));
taskDTO.setCorrection(TaskTypeField.CORRECTION.getBooleanValue(jsonObject));
taskDTO.setTypeAutomatic(TaskTypeField.TYPE_AUTOMATIC.getBooleanValue(jsonObject));
taskDTO.setTypeMetadata(TaskTypeField.TYPE_METADATA.getBooleanValue(jsonObject));
taskDTO.setTypeImagesWrite(TaskTypeField.TYPE_IMAGES_WRITE.getBooleanValue(jsonObject));
taskDTO.setTypeImagesRead(TaskTypeField.TYPE_IMAGES_READ.getBooleanValue(jsonObject));
taskDTO.setBatchStep(TaskTypeField.BATCH_STEP.getBooleanValue(jsonObject));
taskDTO.setRoleIds(convertJSONValuesToList(TaskTypeField.ROLES.getJsonArray(jsonObject)));
taskDTO.setRolesSize(TaskTypeField.ROLES.getSizeOfProperty(jsonObject));
taskDTO.setCorrectionCommentStatus(TaskTypeField.CORRECTION_COMMENT_STATUS.getIntValue(jsonObject));
convertTaskProjectFromJsonObjectToDTO(jsonObject, taskDTO);
/*
* We read the list of the process but not the list of templates, because only process tasks
* are displayed in the task list and reading the template list would result in
* never-ending loops as the list of templates reads the list of tasks.
*/
int process = TaskTypeField.PROCESS_ID.getIntValue(jsonObject);
if (process > 0 && !related) {
taskDTO.setProcess(ServiceManager.getProcessService().findById(process, true));
taskDTO.setBatchAvailable(ServiceManager.getProcessService()
.isProcessAssignedToOnlyOneBatch(taskDTO.getProcess().getBatches()));
}
int processingUser = TaskTypeField.PROCESSING_USER_ID.getIntValue(jsonObject);
if (processingUser > 0) {
UserDTO userDTO = new UserDTO();
userDTO.setId(processingUser);
userDTO.setLogin(TaskTypeField.PROCESSING_USER_LOGIN.getStringValue(jsonObject));
userDTO.setName(TaskTypeField.PROCESSING_USER_NAME.getStringValue(jsonObject));
userDTO.setSurname(TaskTypeField.PROCESSING_USER_SURNAME.getStringValue(jsonObject));
userDTO.setFullName(TaskTypeField.PROCESSING_USER_FULLNAME.getStringValue(jsonObject));
taskDTO.setProcessingUser(userDTO);
}
return taskDTO;
}
/**
* Parses and adds properties related to the project of a task to the taskDTO.
*
* @param jsonObject the jsonObject retrieved from the ElasticSearch index for a task
* @param taskDTO the taskDTO
*/
private void convertTaskProjectFromJsonObjectToDTO(Map<String, Object> jsonObject, TaskDTO taskDTO) throws DataException {
ProjectDTO projectDTO = new ProjectDTO();
projectDTO.setId(TaskTypeField.PROJECT_ID.getIntValue(jsonObject));
projectDTO.setTitle(TaskTypeField.PROJECT_TITLE.getStringValue(jsonObject));
taskDTO.setProject(projectDTO);
}
private List<Integer> convertJSONValuesToList(List<Map<String, Object>> jsonObject) {
return jsonObject.stream()
.flatMap(map -> map.values().stream())
.filter(o -> StringUtils.isNumeric(o.toString()))
.map(o -> (Integer) o)
.collect(Collectors.toList());
}
/**
* Convert date of processing begin to formatted String.
*
* @param task
* object
* @return formatted date string
*/
public String getProcessingBeginAsFormattedString(Task task) {
return Helper.getDateAsFormattedString(task.getProcessingBegin());
}
/**
* Convert date of processing end to formatted String.
*
* @param task
* object
* @return formatted date string
*/
public String getProcessingEndAsFormattedString(Task task) {
return Helper.getDateAsFormattedString(task.getProcessingEnd());
}
/**
* Convert date of processing day to formatted String.
*
* @param task
* object
* @return formatted date string
*/
public String getProcessingTimeAsFormattedString(Task task) {
return Helper.getDateAsFormattedString(task.getProcessingTime());
}
/**
* Get localized (translated) title of task.
*
* @param title
* as String
* @return localized title
*/
public String getLocalizedTitle(String title) {
return Helper.getTranslation(title);
}
/**
* Get project(s). If the task belongs to a template, the projects are in
* the template. If the task belongs to a process, the project is in the
* process.
*
* @return value of project(s)
*/
public static List<Project> getProjects(Task task) {
Process process = task.getProcess();
Template template = task.getTemplate();
if (Objects.nonNull(process)) {
return Collections.singletonList(process.getProject());
} else if (Objects.nonNull(template)) {
return template.getProjects();
} else {
return Collections.emptyList();
}
}
/**
* Get roles list size.
*
* @param task
* object
* @return size of roles assigned to task
*/
public int getRolesSize(Task task) {
return task.getRoles().size();
}
/**
* Get title with user.
*
* @return des Schritttitels sowie (sofern vorhanden) den Benutzer mit
* vollständigem Namen
*/
public String getTitleWithUserName(Task task) {
String titleWithUserName = task.getTitle();
User user = task.getProcessingUser();
if (Objects.nonNull(user) && Objects.nonNull(user.getId())) {
titleWithUserName += " (" + ServiceManager.getUserService().getFullName(user) + ")";
}
return titleWithUserName;
}
/**
* Get script path.
*
* @param task
* object
* @return script path as String
*/
public String getScriptPath(Task task) {
if (Objects.nonNull(task.getScriptPath()) && !task.getScriptPath().isEmpty()) {
return task.getScriptPath();
}
return "";
}
/**
* Execute script for task.
*
* @param task
* object
* @param script
* String
* @param automatic
* boolean
* @return int
*/
public boolean executeScript(Task task, String script, boolean automatic) throws DataException {
if (Objects.isNull(script) || script.isEmpty()) {
return false;
}
script = script.replace("{", "(").replace("}", ")");
LegacyMetsModsDigitalDocumentHelper dd = null;
Process po = task.getProcess();
LegacyPrefsHelper prefs = ServiceManager.getRulesetService().getPreferences(po.getRuleset());
try {
dd = ServiceManager.getProcessService()
.readMetadataFile(ServiceManager.getFileService().getMetadataFilePath(po), prefs)
.getDigitalDocument();
} catch (IOException e2) {
logger.error(e2);
}
VariableReplacer replacer = new VariableReplacer(dd.getWorkpiece(), po, task);
script = replacer.replace(script);
boolean executedSuccessful = false;
try {
if (script.startsWith("action:")) {
logger.info("Calling KitodoScript interpreter: {}", script);
KitodoScriptService kitodoScriptService = ServiceManager.getKitodoScriptService();
kitodoScriptService.execute(Arrays.asList(task.getProcess()), script);
executedSuccessful = true;
} else {
logger.info("Calling the shell: {}", script);
CommandService commandService = ServiceManager.getCommandService();
CommandResult commandResult = commandService.runCommand(script);
executedSuccessful = commandResult.isSuccessful();
}
finishOrReturnAutomaticTask(task, automatic, executedSuccessful);
} catch (IOException | DAOException | InvalidImagesException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
} catch (MediaNotFoundException e) {
Helper.setWarnMessage(e.getMessage());
}
return executedSuccessful;
}
/**
* Execute all scripts for step.
*
* @param task
* StepObject
* @param automatic
* boolean
*/
public void executeScript(Task task, boolean automatic) throws DataException {
String script = task.getScriptPath();
boolean scriptFinishedSuccessful = true;
logger.debug("starting script {}", script);
if (Objects.nonNull(script) && !script.trim().isEmpty()) {
scriptFinishedSuccessful = executeScript(task, script, automatic);
}
if (!scriptFinishedSuccessful) {
abortTask(task);
}
}
/**
* Make the necessary changes when performing an automatic task.
*
* @param task
* ongoing task
* @param automatic
* if it is an automatic task
* @param successful
* if the processing was successful
* @throws DataException
* if the task cannot be saved
* @throws IOException
* if the task cannot be closed
*/
private void finishOrReturnAutomaticTask(Task task, boolean automatic, boolean successful)
throws DataException, IOException, DAOException {
if (automatic) {
task.setEditType(TaskEditType.AUTOMATIC);
if (successful) {
task.setProcessingStatus(TaskStatus.DONE);
new WorkflowControllerService().close(task);
} else {
task.setProcessingStatus(TaskStatus.OPEN);
save(task);
}
}
}
private void abortTask(Task task) throws DataException {
task.setProcessingStatus(TaskStatus.OPEN);
task.setEditType(TaskEditType.AUTOMATIC);
save(task);
}
/**
* Performs creating images when this happens automatically in a task.
*
* @param executingThread
* Executing thread (displayed in the taskmanager)
* @param task
* Task that generates images
* @param automatic
* Whether it is an automatic task
* @throws DataException
* if the task cannot be saved
*/
public void generateImages(EmptyTask executingThread, Task task, boolean automatic) throws DataException {
try {
Process process = task.getProcess();
Subfolder sourceFolder = new Subfolder(process, process.getProject().getGeneratorSource());
List<Subfolder> foldersToGenerate = SubfolderFactoryService.createAll(process, task.getContentFolders());
ImageGenerator generator = new ImageGenerator(sourceFolder, GenerationMode.ALL, foldersToGenerate);
generator.setSupervisor(executingThread);
generator.run();
finishOrReturnAutomaticTask(task, automatic, Objects.isNull(executingThread.getException()));
} catch (IOException | DAOException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
/**
* Execute DMS export.
*
* @param task
* as Task object
*/
public void executeDmsExport(Task task) throws DataException, IOException, DAOException {
new ExportDms(task).startExport(task);
}
/**
* Get current tasks with exact title for batch with exact id.
*
* @param title
* of task as String
* @param batchId
* id of batch as Integer
* @return list of Task objects
*/
public List<Task> getCurrentTasksOfBatch(String title, Integer batchId) {
return dao.getCurrentTasksOfBatch(title, batchId);
}
/**
* Get all tasks between two given ordering of tasks for given process id.
*
* @param orderingMax
* as Integer
* @param orderingMin
* as Integer
* @param processId
* id of process for which tasks are searched as Integer
* @return list of Task objects
*/
public List<Task> getAllTasksInBetween(Integer orderingMax, Integer orderingMin, Integer processId) {
return dao.getAllTasksInBetween(orderingMax, orderingMin, processId);
}
/**
* Get next tasks for problem solution for given process id.
*
* @param ordering
* of Task for which it searches next ones as Integer
* @param processId
* id of process for which tasks are searched as Integer
* @return list of Task objects
*/
public List<Task> getNextTasksForProblemSolution(Integer ordering, Integer processId) {
return dao.getNextTasksForProblemSolution(ordering, processId);
}
/**
* Get previous tasks for problem solution for given process id.
*
* @param ordering
* of Task for which it searches previous ones as Integer
* @param processId
* id of process for which tasks are searched as Integer
* @return list of Task objects
*/
public List<Task> getPreviousTasksForProblemReporting(Integer ordering, Integer processId) {
return dao.getPreviousTasksForProblemReporting(ordering, processId);
}
/**
* Find tasks by id of process.
*
* @param id
* of process
* @return list of JSON objects with tasks for specific process id
*/
List<Map<String, Object>> findByProcessId(Integer id) throws DataException {
return findDocuments(getQueryForProcess(id));
}
/**
* Find tasks by id of template.
*
* @param id
* of template
* @return list of JSON objects with tasks for specific template id
*/
List<Map<String, Object>> findByTemplateId(Integer id) throws DataException {
return findDocuments(getQueryForTemplate(id));
}
/**
* Get query for automatic type of task.
*
* @param typeAutomatic
* automatic type of task as boolean
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForTypeAutomatic(boolean typeAutomatic) {
return createSimpleQuery(TaskTypeField.TYPE_AUTOMATIC.getKey(), typeAutomatic, true);
}
/**
* Get query for process.
*
* @param processId
* process id as int
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForProcess(int processId) {
return createSimpleQuery(TaskTypeField.PROCESS_ID.getKey(), processId, true);
}
/**
* Get query for processing user.
*
* @param processingUserId
* processing user id as int
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForProcessingUser(int processingUserId) {
return createSimpleQuery(TaskTypeField.PROCESSING_USER_ID.getKey(), processingUserId, true);
}
/**
* Get query for processing status.
*
* @param processingStatus
* processing status as int
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForProcessingStatus(int processingStatus) {
return createSimpleQuery(TaskTypeField.PROCESSING_STATUS.getKey(), processingStatus, true);
}
/**
* Get query for processing statuses.
*
* @param processingStatus
* set of processing statuses as Integer
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForProcessingStatuses(Set<Integer> processingStatus) {
return createSetQuery(TaskTypeField.PROCESSING_STATUS.getKey(), processingStatus, true);
}
/**
* Get query for template.
*
* @param templateId
* template id as int
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForTemplate(int templateId) {
return createSimpleQuery(TaskTypeField.TEMPLATE_ID.getKey(), templateId, true);
}
/**
* The function determines, from projects, the folders whose contents can be
* generated automatically.
*
* <p>
* This feature is needed once by the task in the template to determine
* which folders show buttons in the interface to turn content creation on
* or off. In addition, the function of the task in the process is required
* to determine if there is at least one folder to be created in the task,
* because then action links for generating are displayed, and not
* otherwise.
*
* <p>
* To create content automatically, a folder must be defined as the template
* folder in the project. The templates serve to create the contents in the
* other folders to be created. Under no circumstances should the contents
* of the template folder be automatically generated, even if, for example,
* after a reconfiguration, this is still set as otherwise they would
* overwrite themselves. Also, contents cannot be created in folders where
* nothing is configured. The folders that are left over can be created.
*
* @param projects
* an object stream of projects that may have folders defined
* whose contents can be auto-generated
* @return an object stream of generable folders
*/
public static Stream<Folder> generatableFoldersFromProjects(Stream<Project> projects) {
Stream<Project> projectsWithSourceFolder = skipProjectsWithoutSourceFolder(projects);
Stream<Folder> allowedFolders = dropOwnSourceFolders(projectsWithSourceFolder);
return removeFoldersThatCannotBeGenerated(allowedFolders);
}
/**
* Only lets projects pass where a source folder is selected.
*
* @param projects
* the unpurified stream of projects
* @return a stream only of projects that define a source to generate images
*/
private static Stream<Project> skipProjectsWithoutSourceFolder(Stream<Project> projects) {
return projects.filter(project -> Objects.nonNull(project.getGeneratorSource()));
}
/**
* Drops all folders to generate if they are their own source folder.
*
* @param projects
* projects whose folders allowed to be generated are to be
* determined
* @return a stream of folders that are allowed to be generated
*/
private static Stream<Folder> dropOwnSourceFolders(Stream<Project> projects) {
Stream<Pair<Folder, Folder>> withSources = projects.flatMap(
project -> project.getFolders().stream().map(folder -> Pair.of(folder, project.getGeneratorSource())));
Stream<Pair<Folder, Folder>> filteredWithSources = withSources.filter(
destinationAndSource -> !destinationAndSource.getLeft().equals(destinationAndSource.getRight()));
return filteredWithSources.map(Pair::getLeft);
}
/**
* Removes all folders to generate which do not have anything to generate
* configured.
*
* @param folders
* a stream of folders
* @return a stream only of those folders where an image generation module
* has been selected
*/
private static Stream<Folder> removeFoldersThatCannotBeGenerated(Stream<Folder> folders) {
return folders.filter(folder -> folder.getDerivative().isPresent() || folder.getDpi().isPresent()
|| folder.getImageScale().isPresent() || folder.getImageSize().isPresent());
}
/**
* Get the duration of a task in days.
* @param task the task to get the duration for
* @return the duration in days
*/
public long getDurationInDays(Task task) {
Date end = task.getProcessingEnd();
if (Objects.isNull(end)) {
end = new Date();
}
Date begin = task.getProcessingBegin();
if (Objects.isNull(begin)) {
begin = task.getProcessingTime();
if (Objects.isNull(begin)) {
begin = new Date();
}
}
long differenceTime = end.getTime() - begin.getTime();
return differenceTime / (1000 * 60 * 60 * 24);
}
/**
* Compute and return list of tasks that are open or in work in the given Process 'process' and are concurrent but
* not equal not the to given Task 'task', if 'task' is not null.
* @param process Process whose tasks are returned
* @param task task to filter from returned list, if not null
* @return List of concurrent open or inwork tasks
*/
public static List<Task> getConcurrentTasksOpenOrInWork(Process process, Task task) {
List<Task> tasks = process.getTasks().stream()
.filter(t -> TaskStatus.INWORK.equals(t.getProcessingStatus())
|| TaskStatus.OPEN.equals(t.getProcessingStatus()))
.collect(Collectors.toList());
if (Objects.nonNull(task) && task.isConcurrent()) {
return tasks.stream()
.filter(t -> !t.getId().equals(task.getId()))
.collect(Collectors.toList());
} else {
return tasks;
}
}
/**
* Filter and return current list of tasks for those in work by other users.
* @param tasks list of tasks
* @return list of tasks in work by other users
*/
public static List<Task> getTasksInWorkByOtherUsers(List<Task> tasks) {
int authenticatedUserId = ServiceManager.getUserService().getAuthenticatedUser().getId();
return tasks.stream()
.filter(t -> Objects.nonNull(t.getProcessingUser())
&& authenticatedUserId != t.getProcessingUser().getId())
.collect(Collectors.toList());
}
/**
* Compute and return list of tasks that are eligible as 'currentTask' for a new correction comment.
*
* @return list of current task options for new correction comment
*/
public static List<Task> getCurrentTaskOptions(Process process) {
int authenticatedUserId = ServiceManager.getUserService().getAuthenticatedUser().getId();
// NOTE: checking for 'INWORK' tasks that do not have a 'processingUser' shouldn't be necessary, but the current
// version of Kitodo.Production allows setting tasks to 'INWORK' without explicitly assigning a user to it via a
// process' task list (e.g. administrative action)
return process.getTasks().stream()
.filter(t -> (TaskStatus.INWORK.equals(t.getProcessingStatus())
&& ((Objects.nonNull(t.getProcessingUser()) && authenticatedUserId == t.getProcessingUser().getId())
|| Objects.isNull(t.getProcessingUser())))
|| TaskStatus.OPEN.equals(t.getProcessingStatus()))
.collect(Collectors.toList());
}
/**
* Create and return a tooltip for the correction message switch using the give Process and Task.
* @param process Process for which tooltip is created
* @param task Task for which tooltip is created
* @return tooltip for correction message switch
*/
public static String getCorrectionMessageSwitchTooltip(Process process, Task task) {
if (isCorrectionWorkflow(process)) {
return Helper.getTranslation("dataEditor.comment.correctionWorkflowAlreadyActive");
} else if (Objects.nonNull(task) && task.getOrdering() == 1) {
return Helper.getTranslation("dataEditor.comment.firstTaskInWorkflow");
} else {
List<Task> concurrentTasks = TaskService.getConcurrentTasksOpenOrInWork(process, task);
if (concurrentTasks.isEmpty()) {
Helper.setErrorMessage("Invalid process state: no 'inwork' or 'open' task found!");
return "";
} else if (concurrentTasks.get(0).getOrdering() == 1) {
return Helper.getTranslation("dataEditor.comment.firstTaskInWorkflow");
} else {
List<Task> tasksInWorkByOtherUsers = TaskService.getTasksInWorkByOtherUsers(concurrentTasks);
if (tasksInWorkByOtherUsers.isEmpty()) {
return "";
} else {
return MessageFormat.format(Helper.getTranslation("dataEditor.comment.parallelTaskInWorkText"),
tasksInWorkByOtherUsers.get(0).getTitle(),
tasksInWorkByOtherUsers.get(0).getProcessingUser().getFullName());
}
}
}
}
/**
* Check and return whether 'correction' flag is set to true for any task of the current process,
* e.g. if process is currently in a correction workflow.
*
* @return whether process is in correction workflow or not
*/
public static boolean isCorrectionWorkflow(Process process) {
return process.getTasks().stream().anyMatch(Task::isCorrection);
}
/**
* Get the id of the template task corresponding to the given task.
* The corresponding template task was the blueprint when creating the given task.
* @param task task to find the corresponding template task for
* @return id of the template task or -1 if no matching task could be found
*/
public static int getCorrespondingTemplateTaskId(Task task) {
List<Task> templateTasks = task.getProcess().getTemplate().getTasks().stream()
.filter(t -> t.getOrdering().equals(task.getOrdering()))
.filter(t -> t.getTitle().equals(task.getTitle()))
.collect(Collectors.toList());
if (templateTasks.size() == 1) {
return templateTasks.get(0).getId();
}
return -1;
}
}