Coverage Summary for Class: Reader (org.kitodo.production.workflow.model)
Class |
Class, %
|
Method, %
|
Line, %
|
Reader |
100%
(1/1)
|
80%
(12/15)
|
83,3%
(50/60)
|
/*
* (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.workflow.model;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.Query;
import org.camunda.bpm.model.bpmn.instance.EndEvent;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.Gateway;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.StartEvent;
import org.camunda.bpm.model.bpmn.instance.Task;
import org.kitodo.config.ConfigCore;
import org.kitodo.exceptions.WorkflowException;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.workflow.model.beans.TaskInfo;
public class Reader {
private BpmnModelInstance modelInstance;
private Map<Task, TaskInfo> tasks;
/**
* Constructor with diagram name as parameter. It loads modelInstance from file
* for given name.
*
* @param diagramName
* as String
* @throws IOException
* in case if file for given name doesn't exist
*/
public Reader(String diagramName) throws IOException {
String diagramPath = ConfigCore.getKitodoDiagramDirectory() + diagramName + ".bpmn20.xml";
loadProcess(ServiceManager.getFileService().read(Paths.get(diagramPath).toUri()));
}
/**
* Constructor with diagram name as parameter. It loads modelInstance for xml
* with given content.
*
* @param diagramXmlContent
* as InputStream
* @throws IOException
* in case if input stream contains incorrect data
*/
public Reader(InputStream diagramXmlContent) throws IOException {
loadProcess(diagramXmlContent);
}
/**
* Method reads workflow tasks, in case reading fails it throws
* WorkflowException.
*
* @throws WorkflowException
* is thrown when reading of the tasks fail, exception message
* explains what caused problem
*/
public void validateWorkflowTasks() throws WorkflowException {
readWorkflowTasks();
}
/**
* Get tasks.
*
* @return value of tasks
*/
public Map<Task, TaskInfo> getTasks() {
return tasks;
}
/**
* Set tasks as map of BPMN Task objects. Key is BPMN Task and value is
* TaskInfo.
*
* @param tasks
* as map of BPMN Task objects
*/
public void setTasks(Map<Task, TaskInfo> tasks) {
this.tasks = tasks;
}
void readWorkflowTasks() throws WorkflowException {
tasks = new LinkedHashMap<>();
StartEvent startEvent = modelInstance.getModelElementsByType(StartEvent.class).iterator().next();
if (startEvent.getOutgoing().iterator().hasNext()) {
iterateOverNodes(startEvent.getOutgoing().iterator().next().getTarget());
}
}
/**
* Read the workflow from diagram.
*
* @param diagramXmlContent
* as InputStream
*/
private void loadProcess(InputStream diagramXmlContent) throws IOException {
modelInstance = Bpmn.readModelFromStream(diagramXmlContent);
getWorkflowFromProcess();
}
/**
* Get workflow from process inside the given file.
*/
private void getWorkflowFromProcess() throws IOException {
Process process = modelInstance.getModelElementsByType(Process.class).iterator().next();
if (Objects.isNull(process)) {
throw new IOException("It looks that given file or input stream contains invalid BPMN diagram!");
}
}
/**
* Iterate over diagram nodes.
*
* @param node
* for current iteration call
*/
private void iterateOverNodes(FlowNode node) throws WorkflowException {
iterateOverNodes(node, 1);
}
/**
* Iterate over diagram nodes.
*
* @param node
* for current iteration call
* @param ordering
* of task
*/
private void iterateOverNodes(FlowNode node, int ordering) throws WorkflowException {
if (node instanceof Task) {
addTask((Task) node, ordering);
} else if (node instanceof Gateway) {
Query<FlowNode> nextNodes = node.getSucceedingNodes();
if (nextNodes.count() == 1) {
if (node.getPreviousNodes().count() > 1) {
iterateOverNodes(nextNodes.singleResult(), ordering);
} else {
throw new WorkflowException(Helper.getTranslation("workflowExceptionParallelGatewayOneTask"));
}
} else if (nextNodes.count() > 1) {
addParallelTasksBranch(nextNodes.list(), ordering);
} else {
throw new WorkflowException(Helper.getTranslation("workflowExceptionParallelGatewayNoTask"));
}
}
}
/**
* Add all tasks for parallel execution - following given parallel gateway until
* ending parallel gateway.
*
* @param nodes
* nodes of parallel gateway
* @param ordering
* of task
*/
private void addParallelTasksBranch(List<FlowNode> nodes, int ordering) throws WorkflowException {
for (FlowNode node : nodes) {
if (isBranchInvalid(node)) {
throw new WorkflowException(Helper.getTranslation("workflowExceptionParallelBranch",
node.getName()));
}
iterateOverNodes(node, ordering);
}
}
private boolean isBranchInvalid(FlowNode node) {
List<FlowNode> nextNodes = node.getSucceedingNodes().list();
for (FlowNode nextNode : nextNodes) {
if (nextNode instanceof Task) {
return true;
}
}
return false;
}
/**
* Add task to tasks list. If task has 0 following nodes, it makes assumption
* that it is the last task in the workflow.
*
* @param task
* for task
* @param ordering
* for task
*/
private void addTask(Task task, int ordering) throws WorkflowException {
Query<FlowNode> nextNodes = task.getSucceedingNodes();
int nextNodesSize = nextNodes.count();
if (nextNodesSize == 1) {
FlowNode nextNode = nextNodes.singleResult();
if (nextNode instanceof EndEvent) {
addTaskIfThereIsNoLoop(task, new TaskInfo(ordering, true));
} else {
addTaskIfThereIsNoLoop(task, new TaskInfo(ordering, false));
ordering++;
iterateOverNodes(nextNode, ordering);
}
} else if (nextNodesSize == 0) {
addTaskIfThereIsNoLoop(task, new TaskInfo(ordering, true));
} else {
throw new WorkflowException(Helper.getTranslation("workflowExceptionMissingGateway",
task.getName()));
}
}
/**
* If there are more than one incoming node - it can mean that loop has appeared
* (more incoming nodes than outgoing).
*
* @param task
* for verification if there is no loop
* @param taskInfo
* additional information needed for add task
*/
private void addTaskIfThereIsNoLoop(Task task, TaskInfo taskInfo) throws WorkflowException {
Query<FlowNode> previousNodes = task.getPreviousNodes();
if (previousNodes.count() == 1) {
tasks.put(task, taskInfo);
} else {
throw new WorkflowException(
Helper.getTranslation("workflowExceptionLoop", task.getName()));
}
}
/**
* Get modelInstance.
*
* @return value of modelInstance
*/
BpmnModelInstance getModelInstance() {
return modelInstance;
}
}