diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/enums/TaskEditType.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/enums/TaskEditType.java index 867a2d8be32..0451d30e40d 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/enums/TaskEditType.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/enums/TaskEditType.java @@ -43,7 +43,12 @@ public enum TaskEditType { /** * Automatic = all kinds of automatic steps. */ - AUTOMATIC(4, "automatic"); + AUTOMATIC(4, "automatic"), + + /** + * Queue = all kinds of changes by ActiveMQ. + */ + QUEUE(5, "queue"); private int value; private String title; diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/CommentDAO.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/CommentDAO.java index 9eb6eadf74f..3e8647eeb0d 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/CommentDAO.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/CommentDAO.java @@ -16,6 +16,7 @@ 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.exceptions.DAOException; public class CommentDAO extends BaseDAO { @@ -54,6 +55,18 @@ public List getAllByProcess(Process process) { Collections.singletonMap("processId", process.getId())); } + /** + * Get all comments by task ordered by id ascending. + * + * @param task + * The current task to get the comments for + * @return List of comments + */ + public List getAllByTask(Task task) { + return getByQuery("FROM Comment WHERE currentTask_id = :taskId ORDER BY id ASC", + Collections.singletonMap("taskId", task.getId())); + } + /** * Save list of comments. * diff --git a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java index b300555428b..755ac4b7bf7 100644 --- a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java +++ b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java @@ -610,6 +610,8 @@ public enum ParameterCore implements ParameterInterface { ACTIVE_MQ_FINALIZE_STEP_QUEUE(new Parameter("activeMQ.finalizeStep.queue")), + ACTIVE_MQ_TASK_ACTION_QUEUE(new Parameter("activeMQ.taskAction.queue")), + ACTIVE_MQ_USER(new Parameter("activeMQ.user")), ACTIVE_MQ_RESULTS_TOPIC(new Parameter("activeMQ.results.topic")), diff --git a/Kitodo/src/main/java/org/kitodo/exceptions/ProcessorException.java b/Kitodo/src/main/java/org/kitodo/exceptions/ProcessorException.java new file mode 100644 index 00000000000..3e5c464e00f --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/exceptions/ProcessorException.java @@ -0,0 +1,36 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.exceptions; + +public class ProcessorException extends Exception { + + /** + * Constructor with given exception. + * + * @param exception + * as Exception + */ + public ProcessorException(Exception exception) { + super(exception); + } + + /** + * Constructor with given message. + * + * @param message + * the exception message + */ + public ProcessorException(String message) { + super(message); + } + +} diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java index 6d92306dbab..05e8ebf9c20 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java @@ -27,6 +27,7 @@ import org.kitodo.data.database.beans.Process; import org.kitodo.data.database.beans.Task; import org.kitodo.data.database.enums.CommentType; +import org.kitodo.data.database.enums.TaskEditType; import org.kitodo.data.database.exceptions.DAOException; import org.kitodo.data.elasticsearch.exceptions.CustomResponseException; import org.kitodo.data.exceptions.DataException; @@ -159,7 +160,7 @@ public String addCommentToAllBatchProcesses() { */ private void reportProblem(Comment comment) { try { - this.workflowControllerService.reportProblem(comment); + this.workflowControllerService.reportProblem(comment, TaskEditType.MANUAL_SINGLE); } catch (DataException e) { Helper.setErrorMessage("reportingProblem", logger, e); } @@ -225,11 +226,11 @@ public int getSizeOfPreviousStepsForProblemReporting() { */ public String solveProblem(Comment comment) { try { - this.workflowControllerService.solveProblem(comment); + this.workflowControllerService.solveProblem(comment, TaskEditType.MANUAL_SINGLE); } catch (DataException | DAOException | IOException e) { Helper.setErrorMessage("SolveProblem", logger, e); } - refreshProcess(this.currentTask.getProcess()); + refreshProcess(comment.getCurrentTask().getProcess()); return MessageFormat.format(REDIRECT_PATH, "tasks"); } @@ -239,8 +240,8 @@ public String solveProblem(Comment comment) { public String solveProblemForAllBatchProcesses(Comment comment) { for (Task task : batchHelper.getSteps()) { for (Comment processComment : ServiceManager.getCommentService().getAllCommentsByProcess(task.getProcess())) { - if (!processComment.isCorrected() - && processComment.getCorrectionTask().getTitle().equals(comment.getCorrectionTask().getTitle())) { + if (!processComment.isCorrected() && verifyCorrectionTasks(comment.getCorrectionTask(), + processComment.getCorrectionTask())) { solveProblem(processComment); } } @@ -248,6 +249,26 @@ public String solveProblemForAllBatchProcesses(Comment comment) { return MessageFormat.format(REDIRECT_PATH, "tasks"); } + /** + * Verify whether both correction tasks are null or share identical titles. + * + * @param commentCorrectionTask + * The comment correction task + * @param processCommentCorrectionTask + * The process comment correction task + * @return True if they are null or have equal titles + */ + private static boolean verifyCorrectionTasks(Task commentCorrectionTask, Task processCommentCorrectionTask) { + if (Objects.isNull(commentCorrectionTask) && Objects.isNull(processCommentCorrectionTask)) { + return true; + } else if (Objects.isNull(commentCorrectionTask) && Objects.nonNull( + processCommentCorrectionTask) || Objects.nonNull(commentCorrectionTask) && Objects.isNull( + processCommentCorrectionTask)) { + return false; + } + return processCommentCorrectionTask.getTitle().equals(commentCorrectionTask.getTitle()); + } + /** * refresh the process in the session. * diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java index 5f88d41aec6..73a2a316b61 100644 --- a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java +++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java @@ -57,7 +57,7 @@ public class ActiveMQDirector implements Runnable, ServletContextListener { private static Collection services; static { - services = Arrays.asList(new FinalizeStepProcessor()); + services = Arrays.asList(new FinalizeStepProcessor(), new TaskActionProcessor()); } private static Connection connection = null; diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQProcessor.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQProcessor.java index 44f9879699e..9a62c509ac3 100644 --- a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQProcessor.java +++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQProcessor.java @@ -27,6 +27,7 @@ import org.kitodo.data.database.beans.Client; import org.kitodo.data.database.beans.User; import org.kitodo.data.database.exceptions.DAOException; +import org.kitodo.exceptions.ProcessorException; import org.kitodo.production.enums.ReportLevel; import org.kitodo.production.helper.Helper; import org.kitodo.production.security.SecurityUserDetails; @@ -63,7 +64,7 @@ public abstract class ActiveMQProcessor implements MessageListener { * an object providing access to the fields of the received map * message */ - protected abstract void process(MapMessageObjectReader ticket) throws DAOException, JMSException; + protected abstract void process(MapMessageObjectReader ticket) throws ProcessorException, JMSException; /** * Instantiating the class ActiveMQProcessor always requires to pass the diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/FinalizeStepProcessor.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/FinalizeStepProcessor.java index e70bdf68f38..eba784164cf 100644 --- a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/FinalizeStepProcessor.java +++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/FinalizeStepProcessor.java @@ -23,6 +23,7 @@ import org.kitodo.data.database.beans.Property; import org.kitodo.data.database.enums.CommentType; import org.kitodo.data.database.exceptions.DAOException; +import org.kitodo.exceptions.ProcessorException; import org.kitodo.production.forms.CurrentTaskForm; import org.kitodo.production.services.ServiceManager; @@ -44,32 +45,36 @@ public FinalizeStepProcessor() { } /** - * This is the main routine processing incoming tickets. It gets an - * CurrentTaskForm object, sets it to the appropriate step which is - * retrieved from the database, appends the message − if any − to the wiki - * field, and executes the form’s the step close function. + * This is the main routine processing incoming tickets. It gets an CurrentTaskForm object, sets it to the + * appropriate step which is retrieved from the database, appends the message − if any − to the wiki field, and + * executes the form’s the step close function. * * @param ticket - * the incoming message + * the incoming message */ @Override - protected void process(MapMessageObjectReader ticket) throws DAOException, JMSException { + protected void process(MapMessageObjectReader ticket) throws ProcessorException, JMSException { CurrentTaskForm dialog = new CurrentTaskForm(); - Integer stepID = ticket.getMandatoryInteger("id"); - dialog.setCurrentTask(ServiceManager.getTaskService().getById(stepID)); - if (ticket.hasField("properties")) { - updateProperties(dialog, ticket.getMapOfStringToString("properties")); - } - if (ticket.hasField("message")) { - Comment comment = new Comment(); - comment.setProcess(dialog.getCurrentTask().getProcess()); - comment.setAuthor(ServiceManager.getUserService().getCurrentUser()); - comment.setMessage(ticket.getString("message")); - comment.setType(CommentType.INFO); - comment.setCreationDate(new Date()); - ServiceManager.getCommentService().saveToDatabase(comment); + try { + Integer stepID = ticket.getMandatoryInteger("id"); + dialog.setCurrentTask(ServiceManager.getTaskService().getById(stepID)); + + if (ticket.hasField("properties")) { + updateProperties(dialog, ticket.getMapOfStringToString("properties")); + } + if (ticket.hasField("message")) { + Comment comment = new Comment(); + comment.setProcess(dialog.getCurrentTask().getProcess()); + comment.setMessage(ticket.getString("message")); + comment.setAuthor(ServiceManager.getUserService().getCurrentUser()); + comment.setType(CommentType.INFO); + comment.setCreationDate(new Date()); + ServiceManager.getCommentService().saveToDatabase(comment); + } + dialog.closeTaskByUser(); + } catch (DAOException e) { + throw new ProcessorException(e); } - dialog.closeTaskByUser(); } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/TaskAction.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/TaskAction.java new file mode 100644 index 00000000000..b58fd37df74 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/TaskAction.java @@ -0,0 +1,39 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.interfaces.activemq; + +public enum TaskAction { + /** + * Adds a comment to the task. + */ + COMMENT, + + /** + * Add an error comment when task status is INWORK and set the task status to LOCKED if the correction id is set. + */ + ERROR_OPEN, + + /** + * Set task status of LOCKED (if correction id is set) or INWORK (if correction id is not set) task to OPEN. + */ + ERROR_CLOSE, + + /** + * Set task status of open task to INWORK. + */ + PROCESS, + + /** + * Close a task. + */ + CLOSE +} diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/TaskActionProcessor.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/TaskActionProcessor.java new file mode 100644 index 00000000000..4d5ea5d7537 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/TaskActionProcessor.java @@ -0,0 +1,208 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.interfaces.activemq; + +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.jms.JMSException; + +import org.kitodo.config.ConfigCore; +import org.kitodo.config.enums.ParameterCore; +import org.kitodo.data.database.beans.Comment; +import org.kitodo.data.database.beans.Task; +import org.kitodo.data.database.beans.User; +import org.kitodo.data.database.enums.CommentType; +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.elasticsearch.exceptions.CustomResponseException; +import org.kitodo.data.exceptions.DataException; +import org.kitodo.exceptions.ProcessorException; +import org.kitodo.production.services.ServiceManager; +import org.kitodo.production.services.data.TaskService; +import org.kitodo.production.services.workflow.WorkflowControllerService; + +/** + * This is a web service interface to modify task status. + */ +public class TaskActionProcessor extends ActiveMQProcessor { + + public static final String KEY_CORRECTION_TASK_ID = "correctionTaskId"; + public static final String KEY_MESSAGE = "message"; + public static final String KEY_TASK_ACTION = "action"; + public static final String KEY_TASK_ID = "id"; + private final TaskService taskService = ServiceManager.getTaskService(); + private final WorkflowControllerService workflowControllerService; + + /** + * The default constructor looks up the queue name to use in kitodo_config.properties. If that is not configured and + * “null” is passed to the super constructor, this will prevent ActiveMQDirector.registerListeners() from starting + * this service. + */ + public TaskActionProcessor() { + super(ConfigCore.getOptionalString(ParameterCore.ACTIVE_MQ_TASK_ACTION_QUEUE).orElse(null)); + workflowControllerService = new WorkflowControllerService(); + } + + /** + * This is the main routine processing incoming messages. It gets the task id and the task action for processing. + * Every action has its own behavior so please read the comment on the action for more information. + * + * @param mapMessageObjectReader + * the incoming message + */ + @Override + protected void process(MapMessageObjectReader mapMessageObjectReader) throws ProcessorException, JMSException { + Integer taskId = mapMessageObjectReader.getMandatoryInteger(KEY_TASK_ID); + String taskActionField = mapMessageObjectReader.getMandatoryString(KEY_TASK_ACTION); + TaskAction taskAction; + try { + taskAction = TaskAction.valueOf(mapMessageObjectReader.getMandatoryString(KEY_TASK_ACTION)); + } catch (IllegalArgumentException e) { + throw new ProcessorException("Unknown task action " + taskActionField); + } + + try { + Task currentTask = taskService.getById(taskId); + if (Objects.isNull(currentTask)) { + throw new ProcessorException("Task with id " + taskId + " not found."); + } + processAction(mapMessageObjectReader, taskAction, currentTask); + } catch (DataException | DAOException | IOException e) { + throw new ProcessorException(e); + } + } + + private void processAction(MapMessageObjectReader mapMessageObjectReader, TaskAction taskAction, Task currentTask) + throws JMSException, ProcessorException, DataException, DAOException, IOException { + Comment comment = null; + if (mapMessageObjectReader.hasField(KEY_MESSAGE)) { + comment = buildComment(currentTask, mapMessageObjectReader.getMandatoryString(KEY_MESSAGE)); + } + + User currentUser = ServiceManager.getUserService().getCurrentUser(); + switch (taskAction) { + case PROCESS: + if (!TaskStatus.OPEN.equals(currentTask.getProcessingStatus())) { + throw new ProcessorException("Status of task is not OPEN."); + } + actionProcess(currentTask, currentUser); + break; + case ERROR_OPEN: + if (!TaskStatus.INWORK.equals(currentTask.getProcessingStatus())) { + throw new ProcessorException("Status of task is not INWORK."); + } + if (!mapMessageObjectReader.hasField(KEY_MESSAGE)) { + throw new ProcessorException("Message field of task action ERROR_OPEN is required."); + } + actionErrorOpen(mapMessageObjectReader, comment); + break; + case ERROR_CLOSE: + if ((!mapMessageObjectReader.hasField(KEY_CORRECTION_TASK_ID) && !TaskStatus.INWORK.equals( + currentTask.getProcessingStatus())) || (mapMessageObjectReader.hasField( + KEY_CORRECTION_TASK_ID) && !TaskStatus.LOCKED.equals(currentTask.getProcessingStatus()))) { + throw new ProcessorException( + "Status of task is not INWORK if there is a no corrected task ID or LOCKED if there is a corrected task ID."); + } + actionErrorClose(mapMessageObjectReader, currentTask, currentUser); + break; + case CLOSE: + workflowControllerService.closeTaskByUser(currentTask); + break; + default: + if (!mapMessageObjectReader.hasField(KEY_MESSAGE)) { + throw new ProcessorException("Message field of task action COMMENT is required."); + } + } + + if (Objects.nonNull(comment)) { + ServiceManager.getCommentService().saveToDatabase(comment); + } + } + + private static Comment buildComment(Task currentTask, String message) { + Comment comment = new Comment(); + comment.setProcess(currentTask.getProcess()); + comment.setAuthor(ServiceManager.getUserService().getCurrentUser()); + comment.setMessage(message); + comment.setCreationDate(new Date()); + comment.setType(CommentType.INFO); + comment.setCurrentTask(currentTask); + return comment; + } + + private void actionErrorOpen(MapMessageObjectReader mapMessageObjectReader, Comment comment) + throws ProcessorException, JMSException, DAOException, DataException { + if (mapMessageObjectReader.hasField(KEY_CORRECTION_TASK_ID)) { + Integer correctionTaskId = mapMessageObjectReader.getMandatoryInteger(KEY_CORRECTION_TASK_ID); + Task correctionTask = taskService.getById(correctionTaskId); + if (Objects.isNull(correctionTask)) { + throw new ProcessorException("Correction task with id " + correctionTaskId + " not found."); + } + comment.setCorrectionTask(correctionTask); + } + comment.setType(CommentType.ERROR); + workflowControllerService.reportProblem(comment, TaskEditType.QUEUE); + } + + private void actionProcess(Task currentTask, User currentUser) throws DataException { + currentTask.setProcessingStatus(TaskStatus.INWORK); + currentTask.setEditType(TaskEditType.QUEUE); + currentTask.setProcessingTime(new Date()); + taskService.replaceProcessingUser(currentTask, currentUser); + currentTask.setProcessingBegin(new Date()); + taskService.save(currentTask); + } + + private void actionErrorClose(MapMessageObjectReader mapMessageObjectReader, Task currentTask, User currentUser) + throws JMSException, DataException, DAOException, IOException { + currentTask.setProcessingStatus(TaskStatus.OPEN); + currentTask.setEditType(TaskEditType.QUEUE); + currentTask.setProcessingBegin(null); + currentTask.setProcessingTime(null); + taskService.replaceProcessingUser(currentTask, currentUser); + taskService.save(currentTask); + + if (mapMessageObjectReader.hasField(KEY_CORRECTION_TASK_ID)) { + markErrorCommentAsCorrected(currentTask, + mapMessageObjectReader.getMandatoryInteger(KEY_CORRECTION_TASK_ID)); + } else { + markErrorCommentAsCorrected(currentTask); + } + } + + private void markErrorCommentAsCorrected(Task currentTask) throws DAOException, DataException, IOException { + markErrorCommentAsCorrected(currentTask, null); + } + + private void markErrorCommentAsCorrected(Task currentTask, Integer correctionTaskId) + throws DAOException, DataException, IOException { + List comments = ServiceManager.getCommentService().getAllCommentsByTask(currentTask); + Optional optionalComment; + optionalComment = comments.stream().filter(currentTaskComment -> CommentType.ERROR.equals( + currentTaskComment.getType()) && !currentTaskComment.isCorrected() && isEqualCorrectionTask( + correctionTaskId, currentTaskComment.getCorrectionTask())).findFirst(); + if (optionalComment.isPresent()) { + workflowControllerService.solveProblem(optionalComment.get(), TaskEditType.QUEUE); + } + } + + private static boolean isEqualCorrectionTask(Integer correctionTaskId, Task correctionTask) { + return (Objects.isNull(correctionTaskId) && Objects.isNull(correctionTask)) || (Objects.nonNull( + correctionTaskId) && correctionTaskId.equals(correctionTask.getId())); + } + +} diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java index 182bf566b09..63b1470591b 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java @@ -17,6 +17,7 @@ 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.exceptions.DAOException; import org.kitodo.data.database.persistence.CommentDAO; import org.kitodo.production.services.data.base.SearchDatabaseService; @@ -71,6 +72,17 @@ public List getAllCommentsByProcess(Process process) { return dao.getAllByProcess(process); } + /** + * Get all comments by task ordered by id ascending. + * + * @param task + * The current task to get the comments for + * @return List of comments + */ + public List getAllCommentsByTask(Task task) { + return dao.getAllByTask(task); + } + /** * Save list of comments to database. * diff --git a/Kitodo/src/main/java/org/kitodo/production/services/workflow/WorkflowControllerService.java b/Kitodo/src/main/java/org/kitodo/production/services/workflow/WorkflowControllerService.java index 67bb3726fcf..032a0611810 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/workflow/WorkflowControllerService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/workflow/WorkflowControllerService.java @@ -348,28 +348,33 @@ public void unassignTaskFromUser(Task task) throws DataException { /** * Unified method for report problem . * - * @param comment as Comment object + * @param comment + * as Comment object + * @param taskEditType + * of task change */ - public void reportProblem(Comment comment) throws DataException { + public void reportProblem(Comment comment, TaskEditType taskEditType) 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.setProcessingStatus( + Objects.nonNull(comment.getCorrectionTask()) ? TaskStatus.LOCKED : TaskStatus.INWORK); + currentTask.setEditType(taskEditType); 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); + if (Objects.nonNull(comment.getCorrectionTask())) { + Task correctionTask = comment.getCorrectionTask(); + correctionTask.setProcessingStatus(TaskStatus.OPEN); + correctionTask.setProcessingEnd(null); + correctionTask.setCorrection(true); + taskService.save(correctionTask); + lockTasksBetweenCurrentAndCorrectionTask(currentTask, correctionTask); + } updateProcessSortHelperStatus(currentTask.getProcess()); } @@ -377,12 +382,23 @@ public void reportProblem(Comment comment) throws DataException { * Unified method for solve problem. * * @param comment - * as Comment object + * as Comment object */ - public void solveProblem(Comment comment) throws DataException, DAOException, IOException { - closeTaskByUser(comment.getCorrectionTask()); + public void solveProblem(Comment comment, TaskEditType taskEditType) + throws DataException, DAOException, IOException { + if (Objects.nonNull(comment.getCorrectionTask())) { + closeTaskByUser(comment.getCorrectionTask()); + comment.setCorrectionTask(ServiceManager.getTaskService().getById(comment.getCorrectionTask().getId())); + } else { + Task currentTask = comment.getCurrentTask(); + currentTask.setProcessingStatus(TaskStatus.OPEN); + currentTask.setEditType(taskEditType); + currentTask.setProcessingTime(new Date()); + currentTask.setProcessingBegin(null); + taskService.replaceProcessingUser(currentTask, getCurrentUser()); + taskService.save(currentTask); + } 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 { diff --git a/Kitodo/src/main/resources/kitodo_config.properties b/Kitodo/src/main/resources/kitodo_config.properties index 5afc19d397f..4f00df84098 100644 --- a/Kitodo/src/main/resources/kitodo_config.properties +++ b/Kitodo/src/main/resources/kitodo_config.properties @@ -521,7 +521,6 @@ useLocalDirectory=true ldap_useTLS=false - # ----------------------------------- # Authority control configuration # ----------------------------------- @@ -623,6 +622,8 @@ activeMQ.user=testAdmin # You can provide a queue from which messages are read to finalize steps #activeMQ.finalizeStep.queue=KitodoProduction.FinalizeStep.Queue +# You can provide a queue from which messages are read to process task actions +#activeMQ.taskAction.queue=KitodoProduction.TaskAction.Queue # ----------------------------------- # Elasticsearch properties diff --git a/Kitodo/src/test/java/org/kitodo/production/interfaces/activemq/TaskActionProcessorIT.java b/Kitodo/src/test/java/org/kitodo/production/interfaces/activemq/TaskActionProcessorIT.java new file mode 100644 index 00000000000..c31762420bb --- /dev/null +++ b/Kitodo/src/test/java/org/kitodo/production/interfaces/activemq/TaskActionProcessorIT.java @@ -0,0 +1,288 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.interfaces.activemq; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Objects; + +import javax.jms.JMSException; + +import org.apache.commons.lang3.StringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.kitodo.MockDatabase; +import org.kitodo.SecurityTestUtils; +import org.kitodo.data.database.beans.Comment; +import org.kitodo.data.database.beans.Task; +import org.kitodo.data.database.enums.TaskStatus; +import org.kitodo.exceptions.ProcessorException; +import org.kitodo.production.services.ServiceManager; +import org.kitodo.production.services.data.TaskService; + +public class TaskActionProcessorIT { + + private static final TaskService taskService = ServiceManager.getTaskService(); + + /** + * Prepare the data for every test. + * + * @throws Exception + * if something goes wrong + */ + @Before + public void prepare() throws Exception { + MockDatabase.startNode(); + MockDatabase.insertProcessesForWorkflowFull(); + SecurityTestUtils.addUserDataToSecurityContext(ServiceManager.getUserService().getById(1), 1); + } + + /** + * Clean the data after every test. + * + * @throws Exception + * if something goes wrong + */ + @After + public void clean() throws Exception { + MockDatabase.stopNode(); + MockDatabase.cleanDatabase(); + SecurityTestUtils.cleanSecurityContext(); + } + + @Test(expected = ProcessorException.class) + public void testTaskNotFound() throws Exception { + processAction(Integer.MIN_VALUE, TaskAction.COMMENT.name(), StringUtils.EMPTY, null); + } + + @Test(expected = ProcessorException.class) + public void testUnsupportedAction() throws Exception { + processAction(9, "UNSUPPORTED", StringUtils.EMPTY, null); + } + + /** + * Test the task action PROCESS. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionProcess() throws Exception { + Task task = taskService.getById(9); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + task.getProcessingStatus()); + processAction(task, TaskAction.PROCESS); + assertEquals("Task '" + task.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + taskService.getById(task.getId()).getProcessingStatus()); + } + + /** + * Test the error case without task status OPEN for task action PROCESS. + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = ProcessorException.class) + public void testActionProcessWithoutTaskStatusOpen() throws Exception { + Task task = taskService.getById(8); + assertEquals("Task '" + task.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + task.getProcessingStatus()); + processAction(task, TaskAction.PROCESS); + } + + /** + * Test the task action CLOSE. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionClose() throws Exception { + Task task = taskService.getById(9); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + task.getProcessingStatus()); + processAction(task, TaskAction.CLOSE); + assertEquals("Task '" + task.getTitle() + "' status should be DONE!", TaskStatus.DONE, + taskService.getById(task.getId()).getProcessingStatus()); + } + + /** + * Test the task action ERROR_OPEN. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionErrorOpen() throws Exception { + Task inWorkTask = taskService.getById(8); + assertEquals("Task '" + inWorkTask.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + inWorkTask.getProcessingStatus()); + processAction(inWorkTask, TaskAction.ERROR_OPEN); + assertEquals("Task '" + inWorkTask.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + taskService.getById(inWorkTask.getId()).getProcessingStatus()); + } + + /** + * Test the task action ERROR_OPEN with a correction task. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionErrorOpenWithCorrectionTask() throws Exception { + Task task = taskService.getById(8); + Task correctionTask = taskService.getById(6); + assertEquals("Task '" + task.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + task.getProcessingStatus()); + processAction(task, TaskAction.ERROR_OPEN, correctionTask.getId(), 1); + assertEquals("Task '" + task.getTitle() + "' status should be LOCKED!", TaskStatus.LOCKED, + taskService.getById(task.getId()).getProcessingStatus()); + assertEquals("Correction task '" + correctionTask.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + taskService.getById(correctionTask.getId()).getProcessingStatus()); + } + + /** + * Test the error case with wrong task status for task action ERROR_OPEN. + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = ProcessorException.class) + public void testActionErrorOpenWithoutTaskStatusInWork() throws Exception { + Task task = taskService.getById(9); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + task.getProcessingStatus()); + processAction(task, TaskAction.ERROR_OPEN); + } + + /** + * Test the error case without message for task action ERROR_OPEN. + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = ProcessorException.class) + public void testActionErrorOpenWithoutMessage() throws Exception { + Task task = taskService.getById(10); + processAction(task.getId(), TaskAction.ERROR_OPEN.name(), StringUtils.EMPTY, null); + } + + /** + * Test the task action ERROR_CLOSE. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionErrorClose() throws Exception { + Task task = taskService.getById(8); + assertEquals("Task '" + task.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + task.getProcessingStatus()); + processAction(task, TaskAction.ERROR_CLOSE); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + taskService.getById(task.getId()).getProcessingStatus()); + } + + /** + * Test the task action ERROR_CLOSE with a correction task. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionErrorCloseWithCorrectionTask() throws Exception { + Task task = taskService.getById(8); + Task correctionTask = taskService.getById(6); + assertEquals("Task '" + task.getTitle() + "' status should be INWORK!", TaskStatus.INWORK, + task.getProcessingStatus()); + + processAction(task, TaskAction.ERROR_OPEN, correctionTask.getId(), 1); + assertEquals("Task '" + task.getTitle() + "' status should be LOCKED!", TaskStatus.LOCKED, + taskService.getById(task.getId()).getProcessingStatus()); + assertEquals("Correction task '" + correctionTask.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + taskService.getById(correctionTask.getId()).getProcessingStatus()); + + processAction(task, TaskAction.ERROR_CLOSE, correctionTask.getId(), 2); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + taskService.getById(task.getId()).getProcessingStatus()); + assertEquals("Correction task '" + correctionTask.getTitle() + "' status should be DONE!", TaskStatus.DONE, + taskService.getById(correctionTask.getId()).getProcessingStatus()); + } + + /** + * Test the error case without task status LOCKED for task action ERROR_CLOSE. + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = ProcessorException.class) + public void testActionErrorCloseWithoutTaskStatusInWork() throws Exception { + Task task = taskService.getById(9); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + task.getProcessingStatus()); + processAction(task, TaskAction.ERROR_CLOSE); + } + + /** + * Test the task action COMMENT. + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testActionComment() throws Exception { + Task task = taskService.getById(9); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + task.getProcessingStatus()); + processAction(task, TaskAction.COMMENT, null, 1); + processAction(task, TaskAction.COMMENT, null, 2); + assertEquals("Task '" + task.getTitle() + "' status should be OPEN!", TaskStatus.OPEN, + taskService.getById(task.getId()).getProcessingStatus()); + } + + private static void processAction(Task task, TaskAction taskAction) throws JMSException, ProcessorException { + processAction(task, taskAction, null, 1); + } + + private static void processAction(Task task, TaskAction taskAction, Integer correctionTaskId, int commentCount) + throws JMSException, ProcessorException { + String message = "Process action " + taskAction.name(); + processAction(task.getId(), taskAction.name(), message, correctionTaskId); + List comments = ServiceManager.getCommentService().getAllCommentsByTask(task); + assertEquals("Comment should be created!", commentCount, comments.size()); + assertEquals("Comment message should be '" + message + "'!", message, + comments.get(commentCount - 1).getMessage()); + } + + private static void processAction(Integer taskId, String action, String message, Integer correctionTaskId) + throws JMSException, ProcessorException { + MapMessageObjectReader mapMessageObjectReader = mock(MapMessageObjectReader.class); + when(mapMessageObjectReader.getMandatoryInteger(TaskActionProcessor.KEY_TASK_ID)).thenReturn(taskId); + when(mapMessageObjectReader.getMandatoryString(TaskActionProcessor.KEY_TASK_ACTION)).thenReturn(action); + if (StringUtils.isNotEmpty(message)) { + when(mapMessageObjectReader.hasField(TaskActionProcessor.KEY_MESSAGE)).thenReturn(Boolean.TRUE); + when(mapMessageObjectReader.getMandatoryString(TaskActionProcessor.KEY_MESSAGE)).thenReturn(message); + } + if (Objects.nonNull(correctionTaskId)) { + when(mapMessageObjectReader.hasField(TaskActionProcessor.KEY_CORRECTION_TASK_ID)).thenReturn(Boolean.TRUE); + when(mapMessageObjectReader.getMandatoryInteger(TaskActionProcessor.KEY_CORRECTION_TASK_ID)).thenReturn( + correctionTaskId); + } + TaskActionProcessor taskActionProcessor = spy(TaskActionProcessor.class); + taskActionProcessor.process(mapMessageObjectReader); + } +} diff --git a/Kitodo/src/test/java/org/kitodo/production/services/workflow/WorkflowControllerServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/workflow/WorkflowControllerServiceIT.java index e1c21fe0f38..18839d9a4d0 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/workflow/WorkflowControllerServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/workflow/WorkflowControllerServiceIT.java @@ -38,6 +38,7 @@ import org.kitodo.data.database.beans.Task; import org.kitodo.data.database.beans.WorkflowCondition; import org.kitodo.data.database.enums.CommentType; +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.exceptions.DataException; @@ -423,7 +424,7 @@ public void shouldReportProblem() throws Exception { ServiceManager.getCommentService().saveToDatabase(problem); - workflowService.reportProblem(problem); + workflowService.reportProblem(problem, TaskEditType.MANUAL_SINGLE); assertEquals( "Report of problem was incorrect - task '" + correctionTask.getTitle() + "' is not set up to open!", @@ -459,8 +460,8 @@ public void shouldSolveProblem() throws Exception { ServiceManager.getCommentService().saveToDatabase(correctionComment); - workflowService.reportProblem(correctionComment); - workflowService.solveProblem(correctionComment); + workflowService.reportProblem(correctionComment, TaskEditType.MANUAL_SINGLE); + workflowService.solveProblem(correctionComment, TaskEditType.MANUAL_SINGLE); Process process = ServiceManager.getProcessService().getById(currentTask.getProcess().getId()); for (Task task : process.getTasks()) {