Coverage Summary for Class: WorkflowForm (org.kitodo.production.forms)
Class |
Class, %
|
Method, %
|
Line, %
|
WorkflowForm |
100%
(1/1)
|
36,1%
(13/36)
|
31,5%
(56/178)
|
/*
* (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.forms;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.inject.Named;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.config.ConfigCore;
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.Workflow;
import org.kitodo.data.database.enums.WorkflowStatus;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.exceptions.WorkflowException;
import org.kitodo.production.enums.ObjectType;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.model.LazyDTOModel;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.data.DataEditorSettingService;
import org.kitodo.production.services.data.TemplateService;
import org.kitodo.production.services.file.FileService;
import org.kitodo.production.services.workflow.WorkflowControllerService;
import org.kitodo.production.workflow.model.Converter;
import org.kitodo.production.workflow.model.Reader;
@Named("WorkflowForm")
@SessionScoped
public class WorkflowForm extends BaseForm {
private static final Logger logger = LogManager.getLogger(WorkflowForm.class);
private Workflow workflow = new Workflow();
private final transient DataEditorSettingService dataEditorSettingService = ServiceManager.getDataEditorSettingService();
private final transient FileService fileService = ServiceManager.getFileService();
private String svgDiagram;
private boolean dataEditorSettingsDefined = false;
private String xmlDiagram;
private WorkflowStatus workflowStatus;
private static final String BPMN_EXTENSION = ".bpmn20.xml";
private static final String SVG_EXTENSION = ".svg";
private static final String SVG_DIAGRAM_URI = "svgDiagramURI";
private static final String XML_DIAGRAM_URI = "xmlDiagramURI";
private final String workflowEditPath = MessageFormat.format(REDIRECT_PATH, "workflowEdit");
private Integer roleId;
private boolean migration;
private static final String MIGRATION_FORM_PATH = MessageFormat.format(REDIRECT_PATH,"system");
/**
* Constructor.
*/
public WorkflowForm() {
super.setLazyDTOModel(new LazyDTOModel(ServiceManager.getWorkflowService()));
}
/**
* Get list of workflow statues for select list.
*
* @return list of SelectItem objects
*/
public WorkflowStatus[] getWorkflowStatuses() {
return WorkflowStatus.values();
}
/**
* Get workflowStatus.
*
* @return value of workflowStatus
*/
public WorkflowStatus getWorkflowStatus() {
return workflowStatus;
}
/**
* Set workflowStatus.
*
* @param workflowStatus as org.kitodo.data.database.enums.WorkflowStatus
*/
public void setWorkflowStatus(WorkflowStatus workflowStatus) {
this.workflowStatus = workflowStatus;
}
/**
* Read XML for file chosen out of the select list.
*/
public void readXMLDiagram() {
URI xmlDiagramURI = new File(
ConfigCore.getKitodoDiagramDirectory() + encodeXMLDiagramName(this.workflow.getTitle())).toURI();
try (InputStream inputStream = fileService.read(xmlDiagramURI);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
StringBuilder sb = new StringBuilder();
String line = bufferedReader.readLine();
while (Objects.nonNull(line)) {
sb.append(line).append("\n");
line = bufferedReader.readLine();
}
xmlDiagram = sb.toString();
} catch (IOException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
/**
* Save workflow and redirect to list view.
*
* @return url to list view
*/
public String saveAndRedirect() {
if (migration && WorkflowStatus.DRAFT.equals(this.workflowStatus)) {
Helper.setErrorMessage(Helper.getTranslation("errorMigrationDraft"));
return this.stayOnCurrentPage;
}
try {
if (saveFiles()) {
this.workflow.setStatus(this.workflowStatus);
saveWorkflow();
if (!this.workflow.getTemplates().isEmpty()) {
updateTemplateTasks();
}
if (migration) {
migration = false;
return MIGRATION_FORM_PATH + "&workflowId=" + workflow.getId();
}
return projectsPage;
} else {
return this.stayOnCurrentPage;
}
} catch (IOException | DAOException | DataException e) {
Helper.setErrorMessage("errorDiagramFile", new Object[] {this.workflow.getTitle() }, logger, e);
return this.stayOnCurrentPage;
} catch (WorkflowException e) {
Helper.setErrorMessage("errorDiagramTask", new Object[] {this.workflow.getTitle(), e.getMessage() }, logger,
e);
return this.stayOnCurrentPage;
}
}
/**
* Update the tasks of the templates associated with the current workflow and delete associated
* editor settings.
*/
public void updateTemplateTasks() throws DAOException, IOException, WorkflowException, DataException {
Converter converter = new Converter(this.workflow.getTitle());
for (Template workflowTemplate : this.workflow.getTemplates()) {
List<Task> templateTasks = new ArrayList<>(workflowTemplate.getTasks());
if (!templateTasks.isEmpty()) {
if (this.dataEditorSettingsDefined) {
for (Task templateTask : templateTasks) {
this.dataEditorSettingService.removeFromDatabaseByTaskId(templateTask.getId());
}
}
workflowTemplate.getTasks().clear();
TemplateService templateService = ServiceManager.getTemplateService();
converter.convertWorkflowToTemplate(workflowTemplate);
templateService.save(workflowTemplate, true);
new WorkflowControllerService().activateNextTasks(workflowTemplate.getTasks());
}
}
}
/**
* Check if the workflow has associated data editor settings.
* @return value of dataEditorSettingsDefined
*/
public boolean hasWorkflowDataEditorSettingsDefined() {
return this.dataEditorSettingsDefined;
}
/**
* Cancel Workflow creation.
* @return redirectPath
*/
public String cancel() {
if (migration) {
try {
ServiceManager.getWorkflowService().remove(workflow);
} catch (DataException e) {
Helper.setErrorMessage(ERROR_DELETING, new Object[] {this.workflow.getTitle(), e.getMessage() }, logger,
e);
return this.stayOnCurrentPage;
}
migration = false;
return MIGRATION_FORM_PATH;
}
return "projects?keepPagination=true&faces-redirect=true";
}
/**
* Archive active workflow.
*/
public void archive() {
this.workflow.setStatus(WorkflowStatus.ARCHIVED);
saveWorkflow();
}
/**
* Remove workflow if no template is assigned to it.
*/
public void delete() {
if (!this.workflow.getTemplates().isEmpty()) {
Helper.setErrorMessage("templateAssignedError");
} else {
try {
ServiceManager.getWorkflowService().remove(this.workflow);
String diagramDirectory = ConfigCore.getKitodoDiagramDirectory();
URI svgDiagramURI = new File(
diagramDirectory + decodeXMLDiagramName(this.workflow.getTitle()) + SVG_EXTENSION).toURI();
URI xmlDiagramURI = new File(diagramDirectory + encodeXMLDiagramName(this.workflow.getTitle()))
.toURI();
fileService.delete(svgDiagramURI);
fileService.delete(xmlDiagramURI);
} catch (DataException | IOException e) {
Helper.setErrorMessage(ERROR_DELETING, new Object[] {ObjectType.WORKFLOW.getTranslationSingular() },
logger, e);
}
}
}
/**
* Save content of the diagram files.
*
* @return true if save, false if not
*/
private boolean saveFiles() throws IOException, WorkflowException {
Map<String, String> requestParameterMap = FacesContext.getCurrentInstance().getExternalContext()
.getRequestParameterMap();
Map<String, URI> diagramsUris = getDiagramUris();
URI svgDiagramURI = diagramsUris.get(SVG_DIAGRAM_URI);
URI xmlDiagramURI = diagramsUris.get(XML_DIAGRAM_URI);
xmlDiagram = requestParameterMap.get("editForm:workflowTabView:xmlDiagram");
if (Objects.nonNull(xmlDiagram)) {
svgDiagram = StringUtils.substringAfter(xmlDiagram, "kitodo-diagram-separator");
xmlDiagram = StringUtils.substringBefore(xmlDiagram, "kitodo-diagram-separator");
Reader reader = new Reader(new ByteArrayInputStream(xmlDiagram.getBytes(StandardCharsets.UTF_8)));
reader.validateWorkflowTasks();
Converter converter = new Converter(new ByteArrayInputStream(xmlDiagram.getBytes(StandardCharsets.UTF_8)));
converter.validateWorkflowTaskList();
saveFile(svgDiagramURI, svgDiagram);
saveFile(xmlDiagramURI, xmlDiagram);
}
return fileService.fileExist(xmlDiagramURI) && fileService.fileExist(svgDiagramURI);
}
private Map<String, URI> getDiagramUris() {
return getDiagramUris(this.workflow.getTitle());
}
private Map<String, URI> getDiagramUris(String fileName) {
String diagramDirectory = ConfigCore.getKitodoDiagramDirectory();
URI svgDiagramURI = new File(diagramDirectory + decodeXMLDiagramName(fileName) + SVG_EXTENSION)
.toURI();
URI xmlDiagramURI = new File(diagramDirectory + encodeXMLDiagramName(fileName)).toURI();
Map<String, URI> diagramUris = new HashMap<>();
diagramUris.put(SVG_DIAGRAM_URI, svgDiagramURI);
diagramUris.put(XML_DIAGRAM_URI, xmlDiagramURI);
return diagramUris;
}
void saveFile(URI fileURI, String fileContent) {
try (OutputStream outputStream = fileService.write(fileURI);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream))) {
bufferedWriter.write(fileContent);
} catch (IOException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
private String decodeXMLDiagramName(String xmlDiagramName) {
if (xmlDiagramName.contains(BPMN_EXTENSION)) {
return xmlDiagramName.replace(BPMN_EXTENSION, "");
}
return xmlDiagramName;
}
private String encodeXMLDiagramName(String xmlDiagramName) {
if (!xmlDiagramName.contains(BPMN_EXTENSION)) {
return xmlDiagramName + BPMN_EXTENSION;
}
return xmlDiagramName;
}
private void saveWorkflow() {
try {
ServiceManager.getWorkflowService().save(this.workflow, true);
} catch (DataException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
/**
* Create new workflow.
*
* @return page
*/
public String newWorkflow() {
this.workflow = new Workflow();
this.workflow.setClient(ServiceManager.getUserService().getSessionClientOfAuthenticatedUser());
return workflowEditPath + "&id=" + (Objects.isNull(this.workflow.getId()) ? 0 : this.workflow.getId());
}
/**
* Duplicate the selected workflow.
*
* @param itemId
* ID of the workflow to duplicate
* @return page address; either redirect to the edit workflow page or return
* 'null' if the workflow could not be retrieved, which will prompt JSF
* to remain on the same page and reuse the bean.
*/
public String duplicate(Integer itemId) {
try {
Workflow baseWorkflow = ServiceManager.getWorkflowService().getById(itemId);
Map<String, URI> diagramsUris = getDiagramUris(baseWorkflow.getTitle());
URI xmlDiagramURI = diagramsUris.get(XML_DIAGRAM_URI);
this.workflow = ServiceManager.getWorkflowService().duplicateWorkflow(baseWorkflow);
setWorkflowStatus(WorkflowStatus.DRAFT);
Map<String, URI> diagramsCopyUris = getDiagramUris();
URI xmlDiagramCopyURI = diagramsCopyUris.get(XML_DIAGRAM_URI);
try (InputStream xmlInputStream = ServiceManager.getFileService().read(xmlDiagramURI)) {
this.xmlDiagram = IOUtils.toString(xmlInputStream, StandardCharsets.UTF_8);
saveFile(xmlDiagramCopyURI, this.xmlDiagram);
} catch (IOException e) {
Helper.setErrorMessage("unableToDuplicateWorkflow", logger, e);
return this.stayOnCurrentPage;
}
return workflowEditPath;
} catch (DAOException e) {
Helper.setErrorMessage(ERROR_DUPLICATE, new Object[] {ObjectType.WORKFLOW.getTranslationSingular() },
logger, e);
return this.stayOnCurrentPage;
}
}
/**
* Set workflow by id.
*
* @param id
* of workflow to set
*/
public void setWorkflowById(int id) {
try {
setWorkflow(ServiceManager.getWorkflowService().getById(id));
} catch (DAOException e) {
Helper.setErrorMessage(ERROR_LOADING_ONE, new Object[] {ObjectType.WORKFLOW.getTranslationSingular(), id },
logger, e);
}
}
/**
* Method being used as viewAction for workflow edit form. If the given
* parameter 'id' is '0', the form for creating a new workflow will be
* displayed.
*
* @param id
* of the workflow to load
*/
public void load(int id) {
try {
if (!Objects.equals(id, 0)) {
Workflow workflow = ServiceManager.getWorkflowService().getById(id);
setWorkflow(workflow);
setWorkflowStatus(workflow.getStatus());
readXMLDiagram();
this.dataEditorSettingsDefined = this.dataEditorSettingService.areDataEditorSettingsDefinedForWorkflow(workflow);
}
setSaveDisabled(false);
} catch (DAOException e) {
Helper.setErrorMessage(ERROR_LOADING_ONE, new Object[] {ObjectType.WORKFLOW.getTranslationSingular(), id },
logger, e);
}
}
/**
* Get role id.
*
* @return value of roleId
*/
public Integer getRoleId() {
return roleId;
}
/**
* Set role idd.
*
* @param roleId
* as Integer.
*/
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
/**
* Get hidden list of roles.
*
* @return hidden list of roles
*/
public List<SelectItem> getRoles() {
List<SelectItem> selectItems = new ArrayList<>();
List<Role> roles = ServiceManager.getRoleService()
.getAllRolesByClientId(ServiceManager.getUserService().getSessionClientId());
for (Role role : roles) {
selectItems.add(new SelectItem(role.getId(), role.getTitle(), null));
}
return selectItems;
}
/**
* Get workflow.
*
* @return value of workflow
*/
public Workflow getWorkflow() {
return workflow;
}
/**
* Set workflow.
*
* @param workflow
* as Workflow
*/
public void setWorkflow(Workflow workflow) {
this.workflow = workflow;
}
/**
* Get content of XML diagram file.
*
* @return content of XML diagram file as String
*/
public String getXmlDiagram() {
return xmlDiagram;
}
/**
* Set content of XML diagram file.
*
* @param xmlDiagram
* content of XML diagram as String
*/
public void setXmlDiagram(String xmlDiagram) {
this.xmlDiagram = xmlDiagram;
}
/**
* Get content of SVG diagram file.
*
* @return content of SVG diagram file as String
*/
String getSvgDiagram() {
return svgDiagram;
}
/**
* Set content of SVG diagram file.
*
* @param svgDiagram
* content of SVG diagram as String
*/
void setSvgDiagram(String svgDiagram) {
this.svgDiagram = svgDiagram;
}
/**
* Get migration.
*
* @return value of migration
*/
public boolean isMigration() {
return migration;
}
/**
* Set migration.
*
* @param migration as boolean
*/
public void setMigration(boolean migration) {
this.migration = migration;
}
/**
* Get language.
*
* @return language of the currently logged in user
*/
public String getLanguage() {
return ServiceManager.getUserService().getCurrentUser().getLanguage();
}
/**
* Set language.
*
* @param language as String
*/
public void setLanguage(String language) {
// We don't need to do anything. The language value is written into a hidden input field for the localization
// of the editor. On saving the workflow form it gets submitted again. Therefore, a setter is expected, and we
// only need it for completeness’s sake. If we find a better way to get the language value into the editor's JS
// we should do so. :)
}
}