org.kitodo.services.data.ProcessService.java Source code

Java tutorial

Introduction

Here is the source code for org.kitodo.services.data.ProcessService.java

Source

/*
 * (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.services.data;

import com.sun.research.ws.wadl.HTTPMethods;

import de.sub.goobi.config.ConfigCore;
import de.sub.goobi.config.ConfigProjects;
import de.sub.goobi.helper.Helper;
import de.sub.goobi.helper.VariableReplacer;
import de.sub.goobi.helper.exceptions.InvalidImagesException;
import de.sub.goobi.metadaten.MetadatenHelper;
import de.sub.goobi.metadaten.MetadatenSperrung;
import de.sub.goobi.metadaten.MetadatenVerifizierung;
import de.sub.goobi.metadaten.copier.CopierData;
import de.sub.goobi.metadaten.copier.DataCopier;
import de.sub.goobi.persistence.apache.FolderInformation;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.goobi.production.cli.helper.WikiFieldHelper;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.json.simple.JSONObject;
import org.kitodo.api.docket.DocketData;
import org.kitodo.api.docket.DocketInterface;
import org.kitodo.api.filemanagement.ProcessSubType;
import org.kitodo.api.filemanagement.filters.FileNameBeginsAndEndsWithFilter;
import org.kitodo.api.filemanagement.filters.FileNameEndsAndDoesNotBeginWithFilter;
import org.kitodo.data.database.beans.Batch;
import org.kitodo.data.database.beans.Batch.Type;
import org.kitodo.data.database.beans.Docket;
import org.kitodo.data.database.beans.History;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Project;
import org.kitodo.data.database.beans.ProjectFileGroup;
import org.kitodo.data.database.beans.Property;
import org.kitodo.data.database.beans.Ruleset;
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.beans.Workpiece;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.database.helper.MetadataHelper;
import org.kitodo.data.database.helper.enums.IndexAction;
import org.kitodo.data.database.helper.enums.MetadataFormat;
import org.kitodo.data.database.helper.enums.TaskStatus;
import org.kitodo.data.database.persistence.ProcessDAO;
import org.kitodo.data.elasticsearch.exceptions.CustomResponseException;
import org.kitodo.data.elasticsearch.index.Indexer;
import org.kitodo.data.elasticsearch.index.type.ProcessType;
import org.kitodo.data.elasticsearch.search.Searcher;
import org.kitodo.data.elasticsearch.search.enums.SearchCondition;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.dto.BatchDTO;
import org.kitodo.dto.ProcessDTO;
import org.kitodo.dto.ProjectDTO;
import org.kitodo.dto.PropertyDTO;
import org.kitodo.dto.TaskDTO;
import org.kitodo.dto.UserDTO;
import org.kitodo.serviceloader.KitodoServiceLoader;
import org.kitodo.services.ServiceManager;
import org.kitodo.services.data.base.TitleSearchService;
import org.kitodo.services.file.FileService;

import ugh.dl.ContentFile;
import ugh.dl.DigitalDocument;
import ugh.dl.DocStruct;
import ugh.dl.Fileformat;
import ugh.dl.Prefs;
import ugh.dl.VirtualFileGroup;
import ugh.exceptions.PreferencesException;
import ugh.exceptions.ReadException;
import ugh.exceptions.WriteException;
import ugh.fileformats.excel.RDFFile;
import ugh.fileformats.mets.MetsMods;
import ugh.fileformats.mets.MetsModsImportExport;
import ugh.fileformats.mets.XStream;

public class ProcessService extends TitleSearchService<Process, ProcessDTO> {

    private ProcessDAO processDAO = new ProcessDAO();
    private ProcessType processType = new ProcessType();
    private final MetadatenSperrung msp = new MetadatenSperrung();
    private final ServiceManager serviceManager = new ServiceManager();
    private final FileService fileService = serviceManager.getFileService();
    private static final Logger logger = LogManager.getLogger(ProcessService.class);
    private static final String TEMPORARY_FILENAME_PREFIX = "temporary_";

    private static String DIRECTORY_PREFIX = "orig";
    private static String DIRECTORY_SUFFIX = "images";

    /**
     * Constructor with Searcher and Indexer assigning.
     */
    public ProcessService() {
        super(new Searcher(Process.class));
        this.indexer = new Indexer<>(Process.class);
    }

    @Override
    public List<ProcessDTO> findAll(String sort, Integer offset, Integer size) throws DataException {
        return convertJSONObjectsToDTOs(findAllDocuments(sort, offset, size), false);
    }

    @Override
    public Process getById(Integer id) throws DAOException {
        return processDAO.find(id);
    }

    @Override
    public List<Process> getAll() {
        return processDAO.findAll();
    }

    /**
     * Method saves process object to database.
     *
     * @param process
     *            object
     */
    @Override
    public void saveToDatabase(Process process) throws DAOException {
        processDAO.save(process, getProgress(process, null));
    }

    /**
     * Method saves process document to the index of Elastic Search.
     *
     * @param process
     *            object
     */
    @Override
    @SuppressWarnings("unchecked")
    public void saveToIndex(Process process) throws CustomResponseException, IOException {
        indexer.setMethod(HTTPMethods.PUT);
        if (process != null) {
            indexer.performSingleRequest(process, processType);
        }
    }

    /**
     * Method saves or removes batches, tasks and project related to modified
     * process.
     *
     * @param process
     *            object
     */
    @Override
    protected void manageDependenciesForIndex(Process process)
            throws CustomResponseException, DAOException, DataException, IOException {
        manageBatchesDependenciesForIndex(process);
        manageProjectDependenciesForIndex(process);
        manageTaskDependenciesForIndex(process);
        manageTemplatesDependenciesForIndex(process);
        manageWorkpiecesDependenciesForIndex(process);
    }

    /**
     * Check if IndexAction flag is delete. If true remove process from list of
     * processes and re-save batch, if false only re-save batch object.
     *
     * @param process
     *            object
     */
    private void manageBatchesDependenciesForIndex(Process process) throws CustomResponseException, IOException {
        if (process.getIndexAction() == IndexAction.DELETE) {
            for (Batch batch : process.getBatches()) {
                batch.getProcesses().remove(process);
                serviceManager.getBatchService().saveToIndex(batch);
            }
        } else {
            for (Batch batch : process.getBatches()) {
                serviceManager.getBatchService().saveToIndex(batch);
            }
        }
    }

    /**
     * Add process to project, if project is assigned to process.
     *
     * @param process
     *            object
     */
    private void manageProjectDependenciesForIndex(Process process) throws CustomResponseException, IOException {
        if (process.getProject() != null) {
            serviceManager.getProjectService().saveToIndex(process.getProject());
        }
    }

    /**
     * Check IndexAction flag in for process object. If DELETE remove all tasks
     * from index, if other call saveOrRemoveTaskInIndex() method.
     *
     * @param process
     *            object
     */
    private void manageTaskDependenciesForIndex(Process process)
            throws CustomResponseException, DAOException, IOException, DataException {
        if (process.getIndexAction() == IndexAction.DELETE) {
            for (Task task : process.getTasks()) {
                serviceManager.getTaskService().removeFromIndex(task);
            }
        } else {
            saveOrRemoveTasksInIndex(process);
        }
    }

    /**
     * Compare index and database, according to comparisons results save or
     * remove tasks.
     *
     * @param process
     *            object
     */
    private void saveOrRemoveTasksInIndex(Process process)
            throws CustomResponseException, DAOException, IOException, DataException {
        List<Integer> database = new ArrayList<>();
        List<Integer> index = new ArrayList<>();

        for (Task task : process.getTasks()) {
            database.add(task.getId());
            serviceManager.getTaskService().saveToIndex(task);
        }

        List<JSONObject> searchResults = serviceManager.getTaskService().findByProcessId(process.getId());
        for (JSONObject object : searchResults) {
            Integer id = getIdFromJSONObject(object);
            index.add(id);
        }

        List<Integer> missingInIndex = findMissingValues(database, index);
        List<Integer> notNeededInIndex = findMissingValues(index, database);

        if (missingInIndex.size() > 0) {
            for (Integer missing : missingInIndex) {
                serviceManager.getTaskService().saveToIndex(serviceManager.getTaskService().getById(missing));
            }
        }

        if (notNeededInIndex.size() > 0) {
            for (Integer notNeeded : notNeededInIndex) {
                serviceManager.getTaskService().removeFromIndex(notNeeded);
            }
        }
    }

    /**
     * Remove template if process is removed, add template if process is marked
     * as template.
     *
     * @param process
     *            object
     */
    private void manageTemplatesDependenciesForIndex(Process process) throws CustomResponseException, IOException {
        if (process.getIndexAction() == IndexAction.DELETE) {
            for (Template template : process.getTemplates()) {
                serviceManager.getTemplateService().removeFromIndex(template);
            }
        } else {
            for (Template template : process.getTemplates()) {
                serviceManager.getTemplateService().saveToIndex(template);
                saveDependantProperties(template.getProperties());
            }
        }
    }

    /**
     * Remove workpiece if process is removed, add workpiece if process is
     * marked as workpiece.
     *
     * @param process
     *            object
     */
    private void manageWorkpiecesDependenciesForIndex(Process process) throws CustomResponseException, IOException {
        if (process.getIndexAction() == IndexAction.DELETE) {
            for (Workpiece workpiece : process.getWorkpieces()) {
                serviceManager.getWorkpieceService().removeFromIndex(workpiece);
            }
        } else {
            for (Workpiece workpiece : process.getWorkpieces()) {
                serviceManager.getWorkpieceService().saveToIndex(workpiece);
                saveDependantProperties(workpiece.getProperties());
            }
        }
    }

    /**
     * Compare two list and return difference between them.
     *
     * @param firstList
     *            list from which records can be remove
     * @param secondList
     *            records stored here will be removed from firstList
     * @return difference between two lists
     */
    private List<Integer> findMissingValues(List<Integer> firstList, List<Integer> secondList) {
        List<Integer> newList = new ArrayList<>(firstList);
        newList.removeAll(secondList);
        return newList;
    }

    /**
     * Save to index dependant properties.
     *
     * @param properties
     *            List
     */
    private void saveDependantProperties(List<Property> properties) throws CustomResponseException, IOException {
        for (Property property : properties) {
            serviceManager.getPropertyService().saveToIndex(property);
        }
    }

    /**
     * Save list of processes to database.
     *
     * @param list
     *            of processes
     */
    public void saveList(List<Process> list) throws DAOException {
        processDAO.saveList(list);
    }

    /**
     * Method removes process object from database.
     *
     * @param process
     *            object
     */
    @Override
    public void removeFromDatabase(Process process) throws DAOException {
        processDAO.remove(process);
    }

    /**
     * Method removes process object from database.
     *
     * @param id
     *            of process object
     */
    @Override
    public void removeFromDatabase(Integer id) throws DAOException {
        processDAO.remove(id);
    }

    /**
     * Method removes process object from index of Elastic Search.
     *
     * @param process
     *            object
     */
    @Override
    @SuppressWarnings("unchecked")
    public void removeFromIndex(Process process) throws CustomResponseException, IOException {
        indexer.setMethod(HTTPMethods.DELETE);
        if (process != null) {
            indexer.performSingleRequest(process, processType);
        }
    }

    @Override
    public List<Process> getByQuery(String query) throws DAOException {
        return processDAO.search(query);
    }

    @Override
    public Long countDatabaseRows() throws DAOException {
        return processDAO.count("FROM Process");
    }

    @Override
    public Long countDatabaseRows(String query) throws DAOException {
        return processDAO.count(query);
    }

    public void refresh(Process process) {
        processDAO.refresh(process);
    }

    /**
     * Find processes by output name.
     *
     * @param outputName
     *            as String
     * @return list of JSON objects
     */
    public List<JSONObject> findByOutputName(String outputName) throws DataException {
        QueryBuilder query = createSimpleQuery("outputName", outputName, true, Operator.AND);
        return searcher.findDocuments(query.toString());
    }

    /**
     * Find processes for exact creation date.
     *
     * @param creationDate
     *            of the searched processes as Date
     * @param searchCondition
     *            as SearchCondition - bigger, smaller and so on
     * @return list of JSON objects
     */
    public List<JSONObject> findByCreationDate(Date creationDate, SearchCondition searchCondition)
            throws DataException {
        QueryBuilder query = createSimpleCompareDateQuery("creationDate", creationDate, searchCondition);
        return searcher.findDocuments(query.toString());
    }

    /**
     * Find processes by wiki field.
     *
     * @param wikiField
     *            as String
     * @return list of JSON objects
     */
    public List<JSONObject> findByWikiField(String wikiField) throws DataException {
        QueryBuilder query = createSimpleQuery("wikiField", wikiField, true, Operator.AND);
        return searcher.findDocuments(query.toString());
    }

    /**
     * Find processes by id of project.
     *
     * @param id
     *            of project
     * @return list of ProcessDTO objects with processes for specific process id
     */
    public List<ProcessDTO> findByProjectId(Integer id, boolean related) throws DataException {
        return convertJSONObjectsToDTOs(searcher.findDocuments(getQueryProjectId(id).toString()), related);
    }

    /**
     * Count all SortHelperImages fields for project id. It is used for statistical
     * purpose.
     * 
     * @param projectId
     *            as Integer
     * @return amount of SortHelperImages fields for project id as Long
     */
    public Long findCountForSortHelperImages(Integer projectId) throws DataException {
        return findCountAggregation(getQueryProjectId(projectId).toString(), "sortHelperImages");
    }

    /**
     * Sum all values in SortHelperImages fields for project id. It is used for
     * statistical purpose.
     * 
     * @param projectId
     *            as Integer
     * @return sum of all values in SortHelperImages fields for project id as Double
     */
    public Double findSumForSortHelperImages(Integer projectId) throws DataException {
        return findSumAggregation(getQueryProjectId(projectId).toString(), "sortHelperImages");
    }

    private QueryBuilder getQueryProjectId(Integer id) {
        return createSimpleQuery("project", id, true);
    }

    /**
     * Find processes by docket.
     *
     * @param docket
     *            of project
     * @return list of JSON objects with processes for specific docket
     */
    public List<JSONObject> findByDocket(Docket docket) throws DataException {
        QueryBuilder query = createSimpleQuery("docket_id", docket.getId(), true);
        return searcher.findDocuments(query.toString());
    }

    /**
     * Find processes by ruleset.
     *
     * @param ruleset
     *            of project
     * @return list of JSON objects with processes for specific ruleset
     */
    public List<JSONObject> findByRuleset(Ruleset ruleset) throws DataException {
        QueryBuilder query = createSimpleQuery("ruleset", ruleset.getId(), true);
        return searcher.findDocuments(query.toString());
    }

    /**
     * Find processes by title of project.
     *
     * @param title
     *            of process
     * @return list of JSON objects with processes for specific process id
     */
    List<JSONObject> findByProjectTitle(String title) throws DataException {
        Set<Integer> projectIds = new HashSet<>();

        List<JSONObject> projects = serviceManager.getProjectService().findByTitle(title, true);
        for (JSONObject project : projects) {
            projectIds.add(getIdFromJSONObject(project));
        }
        return searcher.findDocuments(createSetQuery("project", projectIds, true).toString());
    }

    /**
     * Find processes by id of batch.
     *
     * @param id
     *            of process
     * @return list of JSON objects with processes for specific batch id
     */
    List<JSONObject> findByBatchId(Integer id) throws DataException {
        QueryBuilder query = createSimpleQuery("batches.id", id, true);
        return searcher.findDocuments(query.toString());
    }

    /**
     * Find processes by title of batch.
     *
     * @param title
     *            of batch
     * @return list of JSON objects with processes for specific batch title
     */
    List<JSONObject> findByBatchTitle(String title) throws DataException {
        Set<Integer> batchIds = new HashSet<>();

        List<JSONObject> batches = serviceManager.getBatchService().findByTitle(title, true);
        for (JSONObject batch : batches) {
            batchIds.add(getIdFromJSONObject(batch));
        }
        return searcher.findDocuments(createSetQuery("batches.id", batchIds, true).toString());
    }

    /**
     * Find processes by property.
     *
     * @param title
     *            of property
     * @param value
     *            of property
     * @param contains
     *            true or false
     * @return list of JSON objects with processes for specific property
     */
    List<JSONObject> findByProperty(String title, String value, boolean contains) throws DataException {
        Set<Integer> propertyIds = new HashSet<>();

        List<JSONObject> properties;
        if (value == null) {
            properties = serviceManager.getPropertyService().findByTitle(title, "process", contains);
        } else if (title == null) {
            properties = serviceManager.getPropertyService().findByValue(value, "process", contains);
        } else {
            properties = serviceManager.getPropertyService().findByTitleAndValue(title, value, "process", contains);
        }

        for (JSONObject property : properties) {
            propertyIds.add(getIdFromJSONObject(property));
        }
        return searcher.findDocuments(createSetQuery("properties.id", propertyIds, true).toString());
    }

    private List<JSONObject> findBySortHelperStatusProjectArchivedAndTemplate(boolean closed, boolean archived,
            boolean template, String sort) throws DataException {
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(getQuerySortHelperStatus(closed));
        query.must(getQueryProjectArchived(archived));
        query.must(getQueryTemplate(template));
        return searcher.findDocuments(query.toString(), sort);
    }

    private List<JSONObject> findBySortHelperStatusAndProjectArchived(boolean closed, boolean archived, String sort)
            throws DataException {
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(getQuerySortHelperStatus(closed));
        query.must(getQueryProjectArchived(archived));
        return searcher.findDocuments(query.toString(), sort);
    }

    private List<JSONObject> findBySortHelperStatusAndTemplate(boolean closed, boolean template, String sort)
            throws DataException {
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(getQuerySortHelperStatus(closed));
        query.must(getQueryTemplate(template));
        return searcher.findDocuments(query.toString(), sort);
    }

    private List<JSONObject> findByArchivedAndTemplate(boolean archived, boolean template, String sort)
            throws DataException {
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(getQueryProjectArchived(archived));
        query.must(getQueryTemplate(template));
        return searcher.findDocuments(query.toString(), sort);
    }

    private List<JSONObject> findByArchived(boolean archived, String sort) throws DataException {
        return searcher.findDocuments(getQueryProjectArchived(archived).toString(), sort);
    }

    private List<JSONObject> findBySortHelperStatus(boolean closed, String sort) throws DataException {
        return searcher.findDocuments(getQuerySortHelperStatus(closed).toString(), sort);
    }

    List<JSONObject> findByTemplate(boolean template, String sort) throws DataException {
        return searcher.findDocuments(getQueryTemplate(template).toString(), sort);
    }

    List<ProcessDTO> findNotTemplateByProjectIds(Set<Integer> projectIds, boolean related) throws DataException {
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(createSetQuery("project", projectIds, true));
        query.must(getQueryTemplate(false));
        return convertJSONObjectsToDTOs(searcher.findDocuments(query.toString()), related);
    }

    /**
     * Get query for template.
     * 
     * @param template
     *            true or false
     * @return query as QueryBuilder
     */
    public QueryBuilder getQueryTemplate(boolean template) {
        return createSimpleQuery("template", template, true);
    }

    /**
     * Get query for sort helper status.
     *
     * @param closed
     *            true or false
     * @return query as QueryBuilder
     */
    public QueryBuilder getQuerySortHelperStatus(boolean closed) {
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.should(createSimpleQuery("sortHelperStatus", "100000000", closed));
        query.should(createSimpleQuery("sortHelperStatus", "100000000000", closed));
        return query;
    }

    /**
     * Get query for archived projects.
     *
     * @param archived
     *            true or false
     * @return query as QueryBuilder
     */
    public QueryBuilder getQueryProjectArchived(boolean archived) throws DataException {
        List<ProjectDTO> projects = serviceManager.getProjectService().findByArchived(archived, true);
        return createSetQuery("project", serviceManager.getFilterService().collectIds(projects), true);
    }

    /**
     * Sort results by creation date.
     * 
     * @param sortOrder
     *            ASC or DESC as SortOrder
     * @return sort
     */
    public String sortByCreationDate(SortOrder sortOrder) {
        return SortBuilders.fieldSort("creationDate").order(sortOrder).toString();
    }

    /**
     * Convert DTO to bean object.
     * 
     * @param processDTO
     *            DTO object
     * @return bean object
     */
    public Process convertDtoToBean(ProcessDTO processDTO) throws DAOException {
        return serviceManager.getProcessService().getById(processDTO.getId());
    }

    /**
     * Convert list of DTOs to list of beans.
     * 
     * @param dtos
     *            list of DTO objects
     * @return list of beans
     */
    public ArrayList<Process> convertDtosToBeans(List<ProcessDTO> dtos) throws DAOException {
        ArrayList<Process> processes = new ArrayList<>();
        for (ProcessDTO processDTO : dtos) {
            processes.add(convertDtoToBean(processDTO));
        }
        return processes;
    }

    /**
     * Method adds all object found in database to Elastic Search index.
     */
    @SuppressWarnings("unchecked")
    public void addAllObjectsToIndex() throws CustomResponseException, InterruptedException, IOException {
        indexer.setMethod(HTTPMethods.PUT);
        indexer.performMultipleRequests(getAll(), processType);
    }

    @Override
    public ProcessDTO convertJSONObjectToDTO(JSONObject jsonObject, boolean related) throws DataException {
        ProcessDTO processDTO = new ProcessDTO();
        processDTO.setId(getIdFromJSONObject(jsonObject));
        processDTO.setTitle(getStringPropertyForDTO(jsonObject, "title"));
        processDTO.setPropertiesSize(getSizeOfRelatedPropertyForDTO(jsonObject, "properties"));
        if (!related) {
            processDTO = convertRelatedJSONObjects(jsonObject, processDTO);
        }
        return processDTO;
    }

    private ProcessDTO convertRelatedJSONObjects(JSONObject jsonObject, ProcessDTO processDTO)
            throws DataException {
        Integer project = getIntegerPropertyForDTO(jsonObject, "project");
        processDTO.setProject(serviceManager.getProjectService().findById(project));
        processDTO
                .setBatches(convertRelatedJSONObjectToDTO(jsonObject, "batches", serviceManager.getBatchService()));
        processDTO.setBatchID(getBatchID(processDTO));
        processDTO.setProperties(
                convertRelatedJSONObjectToDTO(jsonObject, "properties", serviceManager.getPropertyService()));
        processDTO.setSortedCorrectionSolutionMessages(getSortedCorrectionSolutionMessages(processDTO));
        processDTO.setTasks(convertRelatedJSONObjectToDTO(jsonObject, "tasks", serviceManager.getTaskService()));
        processDTO.setImageFolderInUse(isImageFolderInUse(processDTO));
        processDTO.setProgressClosed(getProgressClosed(null, processDTO));
        processDTO.setProgressInProcessing(getProgressInProcessing(null, processDTO));
        processDTO.setProgressOpen(getProgressOpen(null, processDTO));
        processDTO.setProgressLocked(getProgressLocked(null, processDTO));
        processDTO.setBlockedUsers(getBlockedUser(processDTO));
        processDTO.setContainsUnreachableSteps(getContainsUnreachableSteps(processDTO));
        return processDTO;
    }

    /**
     * Get title without white spaces.
     *
     * @param title
     *            of process
     * @return title with '__' instead of ' '
     */
    public String getNormalizedTitle(String title) {
        return title.replace(" ", "__");
    }

    /**
     * Returns the batches of the desired type for a process.
     *
     * @param type
     *            of batches to return
     * @return all batches of the desired type
     */
    public List<Batch> getBatchesByType(Process process, Type type) {
        List<Batch> batches = getBatchesInitialized(process);
        if (type != null) {
            List<Batch> result = new ArrayList<>(batches);
            Iterator<Batch> indicator = result.iterator();
            while (indicator.hasNext()) {
                if (!type.equals(indicator.next().getType())) {
                    indicator.remove();
                }
            }
            return result;
        }
        return batches;
    }

    /**
     * The function getBatchesInitialized() returns the batches for a process
     * and takes care that the object is initialized from Hibernate already and
     * will not be bothered if the Hibernate session ends. TODO: check if it is
     * necessary!!
     *
     * @return the batches field of the process which is loaded
     */
    public List<Batch> getBatchesInitialized(Process process) {
        if (process.getId() != null) {
            Hibernate.initialize(process.getBatches());
        }
        return process.getBatches();
    }

    /**
     * The function getHistoryInitialized() returns the history events for a
     * process and takes care that the object is initialized from Hibernate
     * already and will not be bothered if the Hibernate session ends. TODO:
     * check if it is necessary!!
     *
     * @return the history field of the process which is loaded
     */
    public List<History> getHistoryInitialized(Process process) {
        try {
            @SuppressWarnings("unused")
            Session s = Helper.getHibernateSession();
            Hibernate.initialize(process.getHistory());
        } catch (HibernateException e) {
            logger.debug("Hibernate exception: ", e);
        }
        if (process.getHistory() == null) {
            process.setHistory(new ArrayList<>());
        }
        return process.getHistory();
    }

    /**
     * The function getPropertiesInitialized() returns the descriptive fields
     * (properties?) for a process and takes care that the object is
     * initialized from Hibernate already and will not be bothered if the
     * Hibernate session ends. TODO: check if it is necessary!! <- e.g.
     * BeanHelper uses it
     *
     * @return the properties field of the process which is loaded
     */
    public List<Property> getPropertiesInitialized(Process process) {
        try {
            Hibernate.initialize(process.getProperties());
        } catch (HibernateException e) {
            logger.debug("Hibernate exception: ", e);
        }
        return process.getProperties();
    }

    /**
     * Get blocked user.
     *
     * @return blocked metadata (user)
     */
    public UserDTO getBlockedUser(ProcessDTO process) {
        UserDTO result = null;
        if (MetadatenSperrung.isLocked(process.getId())) {
            String userID = this.msp.getLockBenutzer(process.getId());
            try {
                result = serviceManager.getUserService().findById(Integer.valueOf(userID));
            } catch (Exception e) {
                Helper.setFehlerMeldung(Helper.getTranslation("userNotFound"), e);
            }
        }
        return result;
    }

    public long getBlockedMinutes(Process process) {
        return this.msp.getLockSekunden(process.getId()) / 60;
    }

    public long getBlockedSeconds(Process process) {
        return this.msp.getLockSekunden(process.getId()) % 60;
    }

    /**
     * Get directory for tig images.
     *
     * @param useFallBack
     *            add description
     * @param process
     *            object
     * @return tif directory
     */
    public URI getImagesTifDirectory(boolean useFallBack, Process process) throws IOException {
        URI dir = fileService.getProcessSubTypeURI(process, ProcessSubType.IMAGE, null);
        DIRECTORY_SUFFIX = ConfigCore.getParameter("DIRECTORY_SUFFIX", "tif");
        DIRECTORY_PREFIX = ConfigCore.getParameter("DIRECTORY_PREFIX", "orig");
        /* nur die _tif-Ordner anzeigen, die nicht mir orig_ anfangen */
        FilenameFilter filterDirectory = new FileNameEndsAndDoesNotBeginWithFilter(DIRECTORY_PREFIX + "_",
                "_" + DIRECTORY_SUFFIX);
        URI tifOrdner = null;
        ArrayList<URI> directories = fileService.getSubUris(filterDirectory, dir);
        for (URI directory : directories) {
            tifOrdner = directory;
        }

        if (tifOrdner == null && useFallBack) {
            String suffix = ConfigCore.getParameter("MetsEditorDefaultSuffix", "");
            if (!suffix.equals("")) {
                ArrayList<URI> folderList = fileService.getSubUrisForProcess(null, process, ProcessSubType.IMAGE,
                        "");
                for (URI folder : folderList) {
                    if (folder.toString().endsWith(suffix)) {
                        tifOrdner = folder;
                        break;
                    }
                }
            }
        }

        if (!(tifOrdner == null) && useFallBack) {
            String suffix = ConfigCore.getParameter("MetsEditorDefaultSuffix", "");
            if (!suffix.equals("")) {
                URI tif = tifOrdner;
                ArrayList<URI> files = fileService.getSubUris(tif);
                if (files == null || files.size() == 0) {
                    ArrayList<URI> folderList = fileService.getSubUris(dir);
                    for (URI folder : folderList) {
                        if (folder.toString().endsWith(suffix) && !folder.getPath().startsWith(DIRECTORY_PREFIX)) {
                            tifOrdner = folder;
                            break;
                        }
                    }
                }
            }
        }

        URI result = fileService.getProcessSubTypeURI(process, ProcessSubType.IMAGE, null);

        if (tifOrdner == null) {
            tifOrdner = URI
                    .create(result.toString() + getNormalizedTitle(process.getTitle()) + "_" + DIRECTORY_SUFFIX);
        }

        if (!ConfigCore.getBooleanParameter("useOrigFolder", true)
                && ConfigCore.getBooleanParameter("createOrigFolderIfNotExists", false)) {
            fileService.createMetaDirectory(result, tifOrdner.toString());
        }

        return tifOrdner;
    }

    /**
     * Check if Tif directory exists.
     *
     * @return true if the Tif-Image-Directory exists, false if not
     */
    public Boolean checkIfTifDirectoryExists(Process process) {
        URI testMe;
        try {
            testMe = getImagesTifDirectory(true, process);
            return fileService.getSubUris(testMe) != null && fileService.fileExist(testMe)
                    && fileService.getSubUris(testMe).size() > 0;
        } catch (IOException e) {
            logger.error(e);
            return false;
        }
    }

    /**
     * Get images origin directory.
     *
     * @param useFallBack
     *            as boolean
     * @param process
     *            object
     * @return path
     */
    public URI getImagesOrigDirectory(boolean useFallBack, Process process) throws IOException {
        if (ConfigCore.getBooleanParameter("useOrigFolder", true)) {
            URI dir = fileService.getProcessSubTypeURI(process, ProcessSubType.IMAGE, null);
            DIRECTORY_SUFFIX = ConfigCore.getParameter("DIRECTORY_SUFFIX", "tif");
            DIRECTORY_PREFIX = ConfigCore.getParameter("DIRECTORY_PREFIX", "orig");
            /* nur die _tif-Ordner anzeigen, die mit orig_ anfangen */
            FilenameFilter filterDirectory = new FileNameBeginsAndEndsWithFilter(DIRECTORY_PREFIX + "_",
                    "_" + DIRECTORY_SUFFIX);
            URI origOrdner = null;
            ArrayList<URI> verzeichnisse = fileService.getSubUris(filterDirectory, dir);
            for (URI aVerzeichnisse : verzeichnisse) {
                origOrdner = aVerzeichnisse;
            }

            if (origOrdner == null && useFallBack) {
                String suffix = ConfigCore.getParameter("MetsEditorDefaultSuffix", "");
                if (!suffix.equals("")) {
                    ArrayList<URI> folderList = fileService.getSubUris(dir);
                    for (URI folder : folderList) {
                        if (folder.toString().endsWith(suffix)) {
                            origOrdner = folder;
                            break;
                        }
                    }
                }
            }

            if (!(origOrdner == null) && useFallBack) {
                String suffix = ConfigCore.getParameter("MetsEditorDefaultSuffix", "");
                if (!suffix.equals("")) {
                    URI tif = origOrdner;
                    ArrayList<URI> files = fileService.getSubUris(tif);
                    if (files == null || files.size() == 0) {
                        ArrayList<URI> folderList = fileService.getSubUris(dir);
                        for (URI folder : folderList) {
                            if (folder.toString().endsWith(suffix)) {
                                origOrdner = folder;
                                break;
                            }
                        }
                    }
                }
            }

            URI result = fileService.getProcessSubTypeURI(process, ProcessSubType.IMAGE, null);

            if (origOrdner == null) {
                origOrdner = URI.create(result.toString() + DIRECTORY_PREFIX + "_"
                        + getNormalizedTitle(process.getTitle()) + "_" + DIRECTORY_SUFFIX);
            }

            if (ConfigCore.getBooleanParameter("createOrigFolderIfNotExists", false)
                    && process.getSortHelperStatus().equals("100000000")) {
                fileService.createMetaDirectory(result, origOrdner.toString());
            }

            return origOrdner;
        } else {
            return getImagesTifDirectory(useFallBack, process);
        }
    }

    /**
     * Get process data directory.
     *
     * @param process
     *            object
     * @return path
     */
    public URI getProcessDataDirectory(Process process) {
        if (process.getProcessBaseUri() == null) {
            process.setProcessBaseUri(fileService.getProcessBaseUriForExistingProcess(process));
            try {
                save(process);
            } catch (DataException e) {
                logger.error(e);
                return URI.create("");
            }
        }
        return process.getProcessBaseUri();
    }

    /**
     * The function getBatchID returns the batches the process is associated
     * with as readable text as read-only property "batchID".
     *
     * @return the batches the process is in
     */
    public String getBatchID(ProcessDTO process) {
        if (process.getBatches() == null || process.getBatches().size() == 0) {
            return null;
        }
        StringBuilder result = new StringBuilder();
        BatchService batchService = new BatchService();
        for (BatchDTO batch : process.getBatches()) {
            if (result.length() > 0) {
                result.append(", ");
            }
            result.append(batchService.getLabel(batch));
        }
        return result.toString();
    }

    /**
     * Get size of tasks' list.
     *
     * @param process
     *            object
     * @return size
     */
    public int getTasksSize(Process process) {
        if (process.getTasks() == null) {
            return 0;
        } else {
            return process.getTasks().size();
        }
    }

    /**
     * Get size of histories' list.
     *
     * @param process
     *            object
     * @return size
     */
    public int getHistorySize(Process process) {
        if (process.getHistory() == null) {
            return 0;
        } else {
            return process.getHistory().size();
        }
    }

    /**
     * Get size of properties' list.
     *
     * @param process
     *            object
     * @return size
     */
    public int getPropertiesSize(Process process) {
        if (process.getProperties() == null) {
            return 0;
        } else {
            return process.getProperties().size();
        }
    }

    /**
     * Get size of workpieces' list.
     *
     * @param process
     *            object
     * @return size
     */
    public int getWorkpiecesSize(Process process) {
        if (process.getWorkpieces() == null) {
            return 0;
        } else {
            return process.getWorkpieces().size();
        }
    }

    /**
     * Get size of templates' list.
     *
     * @param process
     *            object
     * @return size
     */
    public int getTemplatesSize(Process process) {
        if (process.getTemplates() == null) {
            return 0;
        } else {
            return process.getTemplates().size();
        }
    }

    /**
     * Get current task.
     *
     * @param process
     *            object
     * @return current task
     */
    public Task getCurrentTask(Process process) {
        for (Task task : process.getTasks()) {
            if (task.getProcessingStatusEnum() == TaskStatus.OPEN
                    || task.getProcessingStatusEnum() == TaskStatus.INWORK) {
                return task;
            }
        }
        return null;
    }

    public String getCreationDateAsString(Process process) {
        return Helper.getDateAsFormattedString(process.getCreationDate());
    }

    /**
     * Get full progress for process.
     *
     * @param process
     *            bean object
     * @param processDTO
     *            DTO object
     * @return string
     */
    public String getProgress(Process process, ProcessDTO processDTO) {
        HashMap<String, Integer> tasks = getCalculationForProgress(process, processDTO);

        double closed = (tasks.get("closed") * 100) / (double) (tasks.get("closed") + tasks.get("inProcessing")
                + tasks.get("open") + tasks.get("locked"));
        double inProcessing = (tasks.get("inProcessing") * 100) / (double) (tasks.get("closed")
                + tasks.get("inProcessing") + tasks.get("open") + tasks.get("locked"));
        double open = (tasks.get("open") * 100) / (double) (tasks.get("closed") + tasks.get("inProcessing")
                + tasks.get("open") + tasks.get("locked"));
        double locked = (tasks.get("locked") * 100) / (double) (tasks.get("closed") + tasks.get("inProcessing")
                + tasks.get("open") + tasks.get("locked"));

        DecimalFormat decimalFormat = new DecimalFormat("#000");
        return decimalFormat.format(closed) + decimalFormat.format(inProcessing) + decimalFormat.format(open)
                + decimalFormat.format(locked);
    }

    /**
     * Get progress for closed tasks.
     *
     * @param process
     *            Process bean object
     * @param processDTO
     *            ProcessDTO object
     * @return progress for closed steps
     */
    public int getProgressClosed(Process process, ProcessDTO processDTO) {
        HashMap<String, Integer> tasks = getCalculationForProgress(process, processDTO);

        return (tasks.get("closed") * 100)
                / (tasks.get("closed") + tasks.get("inProcessing") + tasks.get("open") + tasks.get("locked"));
    }

    /**
     * Get progress for processed tasks.
     *
     * @param process
     *            Process bean object
     * @param processDTO
     *            ProcessDTO object
     * @return progress for processed tasks
     */
    public int getProgressInProcessing(Process process, ProcessDTO processDTO) {
        HashMap<String, Integer> tasks = getCalculationForProgress(process, processDTO);

        return (tasks.get("inProcessing") * 100)
                / (tasks.get("closed") + tasks.get("inProcessing") + tasks.get("open") + tasks.get("locked"));
    }

    /**
     * Get progress for open tasks.
     *
     * @param process
     *            Process bean object
     * @param processDTO
     *            ProcessDTO object
     * @return return progress for open tasks
     */
    public int getProgressOpen(Process process, ProcessDTO processDTO) {
        HashMap<String, Integer> tasks = getCalculationForProgress(process, processDTO);
        return (tasks.get("open") * 100)
                / (tasks.get("closed") + tasks.get("inProcessing") + tasks.get("open") + tasks.get("locked"));
    }

    /**
     * Get progress for open tasks.
     *
     * @param process
     *            Process bean object
     * @param processDTO
     *            ProcessDTO object
     * @return return progress for open tasks
     */
    public int getProgressLocked(Process process, ProcessDTO processDTO) {
        HashMap<String, Integer> tasks = getCalculationForProgress(process, processDTO);
        return (tasks.get("locked") * 100)
                / (tasks.get("closed") + tasks.get("inProcessing") + tasks.get("open") + tasks.get("locked"));
    }

    private HashMap<String, Integer> getCalculationForProgress(Process process, ProcessDTO processDTO) {
        if (process == null) {
            return calculationForProgress(processDTO);
        } else {
            return calculationForProgress(process);
        }
    }

    private HashMap<String, Integer> calculationForProgress(Process process) {
        HashMap<String, Integer> results = new HashMap<>();
        int open = 0;
        int inProcessing = 0;
        int closed = 0;
        int locked = 0;
        Hibernate.initialize(process.getTasks());
        for (Task task : process.getTasks()) {
            if (task.getProcessingStatusEnum() == TaskStatus.DONE) {
                closed++;
            } else if (task.getProcessingStatusEnum() == TaskStatus.OPEN) {
                open++;
            } else if (task.getProcessingStatusEnum() == TaskStatus.LOCKED) {
                locked++;
            } else {
                inProcessing++;
            }
        }

        results.put("closed", closed);
        results.put("inProcessing", inProcessing);
        results.put("open", open);
        results.put("locked", locked);

        if ((open + inProcessing + closed + locked) == 0) {
            results.put("locked", 1);
        }

        return results;
    }

    private HashMap<String, Integer> calculationForProgress(ProcessDTO process) {
        HashMap<String, Integer> results = new HashMap<>();
        int open = 0;
        int inProcessing = 0;
        int closed = 0;
        int locked = 0;
        for (TaskDTO task : process.getTasks()) {
            if (task.getProcessingStatus() == TaskStatus.DONE) {
                closed++;
            } else if (task.getProcessingStatus() == TaskStatus.OPEN) {
                open++;
            } else if (task.getProcessingStatus() == TaskStatus.LOCKED) {
                locked++;
            } else {
                inProcessing++;
            }
        }

        results.put("closed", closed);
        results.put("inProcessing", inProcessing);
        results.put("open", open);
        results.put("locked", locked);

        if ((open + inProcessing + closed + locked) == 0) {
            results.put("locked", 1);
        }

        return results;
    }

    /**
     * Get full text file path.
     *
     * @param process
     *            object
     * @return path as a String to the full text file
     */
    public String getFulltextFilePath(Process process) {
        return getProcessDataDirectory(process) + "/fulltext.xml";
    }

    /**
     * Read metadata file.
     *
     * @param process
     *            object
     * @return filer format
     */
    public Fileformat readMetadataFile(Process process) throws ReadException, IOException, PreferencesException {
        URI metadataFileUri = serviceManager.getFileService().getMetadataFilePath(process);
        if (!checkForMetadataFile(process)) {
            throw new IOException(Helper.getTranslation("metadataFileNotFound") + " " + metadataFileUri);
        }
        Hibernate.initialize(process.getRuleset());
        /* prfen, welches Format die Metadaten haben (Mets, xstream oder rdf */
        String type = MetadatenHelper.getMetaFileType(metadataFileUri);
        if (logger.isDebugEnabled()) {
            logger.debug("current meta.xml file type for id " + process.getId() + ": " + type);
        }

        Fileformat ff = determineFileFormat(type, process);
        try {
            ff.read(serviceManager.getFileService().getFile(metadataFileUri).toString());
        } catch (ReadException e) {
            if (e.getMessage().startsWith("Parse error at line -1")) {
                Helper.setFehlerMeldung("metadataCorrupt");
            } else {
                throw e;
            }
        }
        return ff;
    }

    private Fileformat determineFileFormat(String type, Process process) throws PreferencesException {
        Fileformat fileFormat = null;
        RulesetService rulesetService = new RulesetService();

        switch (type) {
        case "metsmods":
            fileFormat = new MetsModsImportExport(rulesetService.getPreferences(process.getRuleset()));
            break;
        case "mets":
            fileFormat = new MetsMods(rulesetService.getPreferences(process.getRuleset()));
            break;
        case "xstream":
            fileFormat = new XStream(rulesetService.getPreferences(process.getRuleset()));
            break;
        default:
            fileFormat = new RDFFile(rulesetService.getPreferences(process.getRuleset()));
            break;
        }
        return fileFormat;
    }

    private boolean checkForMetadataFile(Process process) {
        return fileService.fileExist(fileService.getMetadataFilePath(process));
    }

    /**
     * Read metadata as template file.
     *
     * @param process
     *            object
     * @return file format
     */
    public Fileformat readMetadataAsTemplateFile(Process process)
            throws ReadException, IOException, PreferencesException {
        RulesetService rulesetService = new RulesetService();
        Hibernate.initialize(process.getRuleset());
        if (new File(fileService.getProcessSubTypeURI(process, ProcessSubType.TEMPLATE, null)).exists()) {
            Fileformat ff = null;
            String type = MetadatenHelper
                    .getMetaFileType(fileService.getProcessSubTypeURI(process, ProcessSubType.TEMPLATE, null));
            if (logger.isDebugEnabled()) {
                logger.debug("current template.xml file type: " + type);
            }
            ff = determineFileFormat(type, process);
            ff.read(fileService.getProcessSubTypeURI(process, ProcessSubType.TEMPLATE, null).toString());
            return ff;
        } else {
            throw new IOException("File does not exist: "
                    + fileService.getProcessSubTypeURI(process, ProcessSubType.TEMPLATE, null));
        }
    }

    /**
     * Check whether the operation contains tasks that are not assigned to a
     * user or user group.
     * 
     * @param process
     *            bean object
     * @return true or false
     */
    public boolean getContainsUnreachableSteps(Process process) {
        TaskService taskService = serviceManager.getTaskService();
        if (process.getTasks().size() == 0) {
            return true;
        }
        for (Task task : process.getTasks()) {
            if (taskService.getUserGroupsSize(task) == 0 && taskService.getUsersSize(task) == 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check whether the operation contains tasks that are not assigned to a
     * user or user group.
     * 
     * @param process
     *            DTO object
     * @return true or false
     */
    public boolean getContainsUnreachableSteps(ProcessDTO process) {
        if (process.getTasks().size() == 0) {
            return true;
        }
        for (TaskDTO task : process.getTasks()) {
            if (task.getUserGroupsSize() == 0 && task.getUsersSize() == 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if there is one task in edit mode, where the user has the rights to
     * write to image folder.
     * 
     * @param process
     *            bean object
     * @return true or false
     */
    public boolean isImageFolderInUse(Process process) {
        for (Task task : process.getTasks()) {
            if (task.getProcessingStatusEnum() == TaskStatus.INWORK && task.isTypeImagesWrite()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if there is one task in edit mode, where the user has the rights to
     * write to image folder.
     * 
     * @param process
     *            DTO object
     * @return true or false
     */
    public boolean isImageFolderInUse(ProcessDTO process) {
        for (TaskDTO task : process.getTasks()) {
            if (task.getProcessingStatus() == TaskStatus.INWORK && task.isTypeImagesWrite()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get user of task in edit mode with rights to write to image folder.
     */
    public User getImageFolderInUseUser(Process process) {
        for (Task task : process.getTasks()) {
            if (task.getProcessingStatusEnum() == TaskStatus.INWORK && task.isTypeImagesWrite()) {
                return task.getProcessingUser();
            }
        }
        return null;
    }

    /**
     * Download docket.
     *
     * @param process
     *            object
     */
    public void downloadDocket(Process process) throws IOException {

        if (logger.isDebugEnabled()) {
            logger.debug("generate docket for process " + process.getId());
        }
        URI rootPath = Paths.get(ConfigCore.getParameter("xsltFolder")).toUri();
        URI xsltFile;
        if (process.getDocket() != null) {
            xsltFile = serviceManager.getFileService().createResource(rootPath, process.getDocket().getFile());
            if (!fileService.fileExist(xsltFile)) {
                Helper.setFehlerMeldung("docketMissing");
            }
        } else {
            xsltFile = serviceManager.getFileService().createResource(rootPath, "docket.xsl");
        }
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (!facesContext.getResponseComplete()) {

            // write run note to servlet output stream
            DocketInterface module = initialiseDocketModule();

            File file = module.generateDocket(getDocketData(process), xsltFile);
            writeToOutputStream(facesContext, file, process.getTitle() + ".pdf");
        }
    }

    /**
     * Writes a multipage docket for a list of processes to an outpustream.
     *
     * @param processes
     *            The list of processes
     * @throws IOException
     *             when xslt file could not be loaded, or write to output
     *             failed.
     */
    public void downloadDocket(List<Process> processes) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("generate docket for processes " + processes);
        }
        URI rootPath = Paths.get(ConfigCore.getParameter("xsltFolder")).toUri();
        URI xsltFile = serviceManager.getFileService().createResource(rootPath, "docket_multipage.xsl");
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (!facesContext.getResponseComplete()) {

            DocketInterface module = initialiseDocketModule();
            File file = module.generateMultipleDockets(serviceManager.getProcessService().getDocketData(processes),
                    xsltFile);

            writeToOutputStream(facesContext, file, "batch_docket.pdf");
        }
    }

    private void writeToOutputStream(FacesContext facesContext, File file, String fileName) throws IOException {
        HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
        ServletContext servletContext = (ServletContext) facesContext.getExternalContext().getContext();
        String contentType = servletContext.getMimeType(fileName);
        response.setContentType(contentType);
        response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");

        ServletOutputStream outputStream = response.getOutputStream();
        FileInputStream fileInputStream = new FileInputStream(file);
        byte[] bytes = IOUtils.toByteArray(fileInputStream);
        fileInputStream.close();
        outputStream.write(bytes);
        outputStream.flush();
        facesContext.responseComplete();
    }

    private DocketInterface initialiseDocketModule() {
        KitodoServiceLoader<DocketInterface> loader = new KitodoServiceLoader<>(DocketInterface.class,
                ConfigCore.getParameter("moduleFolder"));
        return loader.loadModule();
    }

    /**
     * Get first open task for the process.
     *
     * @param process
     *            object
     * @return first open task
     */
    public Task getFirstOpenStep(Process process) {
        for (Task step : process.getTasks()) {
            if (step.getProcessingStatusEnum().equals(TaskStatus.OPEN)
                    || step.getProcessingStatusEnum().equals(TaskStatus.INWORK)) {
                return step;
            }
        }
        return null;
    }

    /**
     * Get method from name.
     *
     * @param methodName
     *            string
     * @param process
     *            object
     * @return method from name
     */
    public URI getMethodFromName(String methodName, Process process) {
        java.lang.reflect.Method method;
        try {
            method = this.getClass().getMethod(methodName);
            Object o = method.invoke(this);
            return (URI) o;
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            logger.debug("exception: " + e);
        }
        try {
            URI folder = this.getImagesTifDirectory(false, process);
            String folderName = fileService.getFileName(folder);
            folderName = folderName.substring(0, folderName.lastIndexOf("_"));
            folderName = folderName + "_" + methodName;
            folder = fileService.renameFile(folder, folderName);
            if (fileService.fileExist(folder)) {
                return folder;
            }
        } catch (IOException ex) {
            logger.debug("exception: " + ex);
        }
        return null;
    }

    /*
     * public List<String> getPossibleDigitalCollections(Process process) throws
     * JDOMException, IOException { return
     * DigitalCollections.possibleDigitalCollectionsForProcess(process); }
     */

    /**
     * The addMessageToWikiField() method is a helper method which composes the
     * new wiki field using a StringBuilder. The message is encoded using HTML
     * entities to prevent certain characters from playing merry havoc when the
     * message box shall be rendered in a browser later.
     *
     * @param message
     *            the message to append
     */
    public Process addToWikiField(String message, Process process) {
        StringBuilder composer = new StringBuilder();
        if (process.getWikiField() != null && process.getWikiField().length() > 0) {
            composer.append(process.getWikiField());
            composer.append("\r\n");
        }
        composer.append("<p>");
        composer.append(StringEscapeUtils.escapeHtml(message));
        composer.append("</p>");
        process.setWikiField(composer.toString());

        return process;
    }

    /**
     * The method addToWikiField() adds a message with a given level to the wiki
     * field of the process. Four level strings will be recognized and result in
     * different colors:
     *
     * <dl>
     * <dt><code>debug</code></dt>
     * <dd>gray</dd>
     * <dt><code>error</code></dt>
     * <dd>red</dd>
     * <dt><code>user</code></dt>
     * <dd>green</dd>
     * <dt><code>warn</code></dt>
     * <dd>orange</dd>
     * <dt><i>any other value</i></dt>
     * <dd>blue</dd>
     * <dt>
     *
     * @param level
     *            message colour, one of: "debug", "error", "info", "user" or
     *            "warn"; any other value defaults to "info"
     * @param message
     *            text
     */
    public String addToWikiField(String level, String message, Process process) {
        return WikiFieldHelper.getWikiMessage(process, process.getWikiField(), level, message);
    }

    /**
     * The method addToWikiField() adds a message signed by the given user to
     * the wiki field of the process.
     *
     * @param user
     *            to sign the message with
     * @param message
     *            to print
     */
    public void addToWikiField(User user, String message, Process process) {
        String text = message + " (" + user.getSurname() + ")";
        // addToWikiField("user", process, text);
    }

    /**
     * The method createProcessDirs() starts creation of directories configured
     * by parameter processDirs within kitodo_config.properties
     */
    public void createProcessDirs(Process process) throws IOException {

        String[] processDirs = ConfigCore.getStringArrayParameter("processDirs");

        for (String processDir : processDirs) {
            fileService.createMetaDirectory(this.getProcessDataDirectory(process),
                    processDir.replace("(processtitle)", process.getTitle()));
        }

    }

    /**
     * The function getDigitalDocument() returns the digital act of this
     * process.
     *
     * @return the digital act of this process
     * @throws PreferencesException
     *             if the no node corresponding to the file format is available
     *             in the rule set configured
     * @throws ReadException
     *             if the meta data file cannot be read
     * @throws IOException
     *             if creating the process directory or reading the meta data
     *             file fails
     */
    public DigitalDocument getDigitalDocument(Process process)
            throws PreferencesException, ReadException, IOException {
        return readMetadataFile(process).getDigitalDocument();
    }

    /**
     * Filter for correction / solution messages.
     *
     * @param lpe
     *            List of process properties
     * @return List of filtered correction / solution messages
     */
    protected List<PropertyDTO> filterForCorrectionSolutionMessages(List<PropertyDTO> lpe) {
        ArrayList<PropertyDTO> filteredList = new ArrayList<>();
        List<String> listOfTranslations = new ArrayList<>();
        String propertyTitle;

        listOfTranslations.add("Korrektur notwendig");
        listOfTranslations.add("Korrektur durchgefuehrt");
        listOfTranslations.add(Helper.getTranslation("Korrektur notwendig"));
        listOfTranslations.add(Helper.getTranslation("Korrektur durchgefuehrt"));

        if ((lpe == null) || (lpe.size() == 0)) {
            return filteredList;
        }

        // filtering for correction and solution messages
        for (PropertyDTO property : lpe) {
            propertyTitle = property.getTitle();
            if (listOfTranslations.contains(propertyTitle)) {
                filteredList.add(property);
            }
        }
        return filteredList;
    }

    /**
     * Filter and sort after creation date list of process properties for
     * correction and solution messages.
     *
     * @return list of ProcessProperty objects
     */
    public List<PropertyDTO> getSortedCorrectionSolutionMessages(ProcessDTO process) {
        List<PropertyDTO> filteredList;
        List<PropertyDTO> properties = process.getProperties();

        if (properties.isEmpty()) {
            return new ArrayList<>();
        }

        filteredList = filterForCorrectionSolutionMessages(properties);

        if (filteredList.size() > 1) {
            Collections.sort(filteredList, (PropertyDTO firstProperty, PropertyDTO secondProperty) -> firstProperty
                    .getCreationDate().compareTo(secondProperty.getCreationDate()));
        }

        return new ArrayList<>(filteredList);
    }

    public Long getNumberOfProcessesWithTitle(String title) throws DataException {
        return count(createSimpleQuery("title", title, true, Operator.AND).toString());
    }

    /**
     * Reads the metadata File.
     *
     * @param metadataFile
     *            The given metadataFile.
     * @param prefs
     *            The Preferences
     * @return The fileFormat.
     */
    public Fileformat readMetadataFile(URI metadataFile, Prefs prefs)
            throws IOException, PreferencesException, ReadException {
        /* prfen, welches Format die Metadaten haben (Mets, xstream oder rdf */
        String type = MetadataHelper.getMetaFileType(metadataFile);
        Fileformat ff;
        switch (type) {
        case "metsmods":
            ff = new MetsModsImportExport(prefs);
            break;
        case "mets":
            ff = new MetsMods(prefs);
            break;
        case "xstream":
            ff = new XStream(prefs);
            break;
        default:
            ff = new RDFFile(prefs);
            break;
        }
        ff.read(metadataFile.getPath());

        return ff;
    }

    /**
     * DMS-Export an eine gewnschte Stelle.
     *
     * @param process
     *            object
     */

    public boolean startDmsExport(Process process, boolean exportWithImages, boolean exportFullText)
            throws IOException, PreferencesException, org.apache.commons.configuration.ConfigurationException,
            WriteException {
        Prefs preferences = serviceManager.getRulesetService().getPreferences(process.getRuleset());

        Project project = process.getProject();

        ConfigProjects configProjects = new ConfigProjects(project.getTitle());
        String atsPpnBand = process.getTitle();

        /*
         * Dokument einlesen
         */
        Fileformat gdzfile;
        Fileformat newfile;
        FolderInformation fi = new FolderInformation(process.getId(), process.getTitle());
        try {
            URI metadataPath = fi.getMetadataFilePath();
            gdzfile = readMetadataFile(metadataPath, preferences);
            switch (MetadataFormat.findFileFormatsHelperByName(project.getFileFormatDmsExport())) {
            case METS:
                newfile = new MetsModsImportExport(preferences);
                break;
            case METS_AND_RDF:
            default:
                newfile = new RDFFile(preferences);
                break;
            }

            newfile.setDigitalDocument(gdzfile.getDigitalDocument());
            gdzfile = newfile;

        } catch (Exception e) {
            Helper.setFehlerMeldung(Helper.getTranslation("exportError") + process.getTitle(), e);
            logger.error("Export abgebrochen, xml-LeseFehler", e);
            return false;
        }

        String rules = ConfigCore.getParameter("copyData.onExport");
        if (rules != null && !rules.equals("- keine Konfiguration gefunden -")) {
            try {
                new DataCopier(rules).process(new CopierData(newfile, process));
            } catch (ConfigurationException e) {
                Helper.setFehlerMeldung("dataCopier.syntaxError", e.getMessage());
                return false;
            } catch (RuntimeException e) {
                Helper.setFehlerMeldung("dataCopier.runtimeException", e.getMessage());
                return false;
            }
        }

        trimAllMetadata(gdzfile.getDigitalDocument().getLogicalDocStruct());

        /*
         * Metadaten validieren
         */

        if (ConfigCore.getBooleanParameter("useMetadatenvalidierung")) {
            MetadatenVerifizierung mv = new MetadatenVerifizierung();
            if (!mv.validate(gdzfile, preferences, process)) {
                return false;

            }
        }

        /*
         * Speicherort vorbereiten und downloaden
         */
        URI zielVerzeichnis;
        URI benutzerHome;

        zielVerzeichnis = new File(project.getDmsImportImagesPath()).toURI();
        benutzerHome = zielVerzeichnis;

        /* ggf. noch einen Vorgangsordner anlegen */
        if (project.isDmsImportCreateProcessFolder()) {
            zielVerzeichnis = benutzerHome.resolve(File.separator + process.getTitle());
            /* alte Import-Ordner lschen */
            if (!fileService.delete(benutzerHome)) {
                Helper.setFehlerMeldung("Export canceled, Process: " + process.getTitle(),
                        "Import folder could not be cleared");
                return false;
            }
            /* alte Success-Ordner lschen */
            File successFile = new File(project.getDmsImportSuccessPath() + File.separator + process.getTitle());
            if (!fileService.delete(successFile.toURI())) {
                Helper.setFehlerMeldung("Export canceled, Process: " + process.getTitle(),
                        "Success folder could not be cleared");
                return false;
            }
            /* alte Error-Ordner lschen */
            File errorfile = new File(project.getDmsImportErrorPath() + File.separator + process.getTitle());
            if (!fileService.delete(errorfile.toURI())) {
                Helper.setFehlerMeldung("Export canceled, Process: " + process.getTitle(),
                        "Error folder could not be cleared");
                return false;
            }

            if (!fileService.fileExist(benutzerHome)) {
                fileService.createDirectory(benutzerHome, File.separator + process.getTitle());
            }
        }

        /*
         * der eigentliche Download der Images
         */
        try {
            if (exportWithImages) {
                imageDownload(process, benutzerHome, atsPpnBand, DIRECTORY_SUFFIX, fi);
                fulltextDownload(benutzerHome, atsPpnBand, fi);
            } else if (exportFullText) {
                fulltextDownload(benutzerHome, atsPpnBand, fi);
            }

            directoryDownload(process, zielVerzeichnis);
        } catch (Exception e) {
            Helper.setFehlerMeldung("Export canceled, Process: " + process.getTitle(), e);
            return false;
        }

        /*
         * zum Schluss Datei an gewnschten Ort exportieren entweder direkt in
         * den Import-Ordner oder ins Benutzerhome anschliessend den
         * Import-Thread starten
         */
        if (project.isUseDmsImport()) {
            if (MetadataFormat
                    .findFileFormatsHelperByName(project.getFileFormatDmsExport()) == MetadataFormat.METS) {
                /* Wenn METS, dann per writeMetsFile schreiben... */
                writeMetsFile(process, benutzerHome + File.separator + atsPpnBand + ".xml", gdzfile, false);
            } else {
                /* ...wenn nicht, nur ein Fileformat schreiben. */
                gdzfile.write(benutzerHome + File.separator + atsPpnBand + ".xml");
            }

            /* ggf. sollen im Export mets und rdf geschrieben werden */
            if (MetadataFormat
                    .findFileFormatsHelperByName(project.getFileFormatDmsExport()) == MetadataFormat.METS_AND_RDF) {
                writeMetsFile(process, benutzerHome + File.separator + atsPpnBand + ".mets.xml", gdzfile, false);
            }

            Helper.setMeldung(null, process.getTitle() + ": ", "DMS-Export started");

            if (!ConfigCore.getBooleanParameter("exportWithoutTimeLimit")) {
                /* Success-Ordner wieder lschen */
                if (project.isDmsImportCreateProcessFolder()) {
                    File successFile = new File(
                            project.getDmsImportSuccessPath() + File.separator + process.getTitle());
                    fileService.delete(successFile.toURI());
                }
            }
        }
        return true;
    }

    /**
     * Run through all metadata and children of given docstruct to trim the
     * strings calls itself recursively.
     */
    private void trimAllMetadata(DocStruct inStruct) {
        /* trim all metadata values */
        if (inStruct.getAllMetadata() != null) {
            for (ugh.dl.Metadata md : inStruct.getAllMetadata()) {
                if (md.getValue() != null) {
                    md.setValue(md.getValue().trim());
                }
            }
        }

        /* run through all children of docstruct */
        if (inStruct.getAllChildren() != null) {
            for (DocStruct child : inStruct.getAllChildren()) {
                trimAllMetadata(child);
            }
        }
    }

    /**
     * Download full text.
     *
     * @param userHome
     *            safe file
     * @param atsPpnBand
     *            String
     */
    private void fulltextDownload(URI userHome, String atsPpnBand, FolderInformation fi) throws IOException {

        // download sources
        URI sources = fi.getSourceDirectory();
        if (fileService.fileExist(sources) && fileService.getSubUris(sources).size() > 0) {
            URI destination = userHome.resolve(File.separator + atsPpnBand + "_src");
            if (!fileService.fileExist(destination)) {
                fileService.createDirectory(userHome, atsPpnBand + "_src");
            }
            ArrayList<URI> dateien = fileService.getSubUris(sources);
            for (URI aDateien : dateien) {
                if (fileService.isFile(aDateien)) {
                    URI meinZiel = destination
                            .resolve(File.separator + fileService.getFileNameWithExtension(aDateien));
                    fileService.copyFile(aDateien, meinZiel);
                }
            }
        }

        URI ocr = fi.getOcrDirectory();
        if (fileService.fileExist(ocr)) {
            ArrayList<URI> folder = fileService.getSubUris(ocr);
            for (URI dir : folder) {
                if (fileService.isDirectory(dir) && fileService.getSubUris(dir).size() > 0
                        && fileService.getFileName(dir).contains("_")) {
                    String suffix = fileService.getFileNameWithExtension(dir)
                            .substring(fileService.getFileNameWithExtension(dir).lastIndexOf("_"));
                    URI destination = userHome.resolve(File.separator + atsPpnBand + suffix);
                    if (!fileService.fileExist(destination)) {
                        fileService.createDirectory(userHome, atsPpnBand + suffix);
                    }
                    ArrayList<URI> files = fileService.getSubUris(dir);
                    for (URI file : files) {
                        if (fileService.isFile(file)) {
                            URI target = destination
                                    .resolve(File.separator + fileService.getFileNameWithExtension(file));
                            fileService.copyFile(file, target);
                        }
                    }
                }
            }
        }
    }

    /**
     * Download image.
     *
     * @param process
     *            process object
     * @param userHome
     *            safe file
     * @param atsPpnBand
     *            String
     * @param ordnerEndung
     *            String
     */
    public void imageDownload(Process process, URI userHome, String atsPpnBand, final String ordnerEndung,
            FolderInformation fi) throws IOException {

        Project project = process.getProject();
        /*
         * den Ausgangspfad ermitteln
         */
        URI tifOrdner = fi.getImagesTifDirectory(true);

        /*
         * jetzt die Ausgangsordner in die Zielordner kopieren
         */
        if (fileService.fileExist(tifOrdner) && fileService.getSubUris(tifOrdner).size() > 0) {
            URI zielTif = userHome.resolve(File.separator + atsPpnBand + ordnerEndung);

            /* bei Agora-Import einfach den Ordner anlegen */
            if (project.isUseDmsImport()) {
                if (!fileService.fileExist(zielTif)) {
                    fileService.createDirectory(userHome, atsPpnBand + ordnerEndung);
                }
            } else {
                /*
                 * wenn kein Agora-Import, dann den Ordner mit
                 * Benutzerberechtigung neu anlegen
                 */
                User myUser = (User) Helper.getManagedBeanValue("#{LoginForm.myBenutzer}");
                try {
                    fileService.createDirectoryForUser(zielTif, myUser.getLogin());
                } catch (Exception e) {
                    Helper.setFehlerMeldung("Export canceled, error", "could not create destination directory");
                    logger.error("could not create destination directory", e);
                }
            }

            /* jetzt den eigentlichen Kopiervorgang */

            ArrayList<URI> dateien = fileService.getSubUris(Helper.dataFilter, tifOrdner);
            for (URI file : dateien) {
                if (fileService.isFile(file)) {
                    URI target = zielTif.resolve(File.separator + fileService.getFileNameWithExtension(file));
                    fileService.copyFile(file, target);
                }
            }
        }
    }

    /**
     * write MetsFile to given Path.
     *
     * @param process
     *            the Process to use
     * @param targetFileName
     *            the filename where the metsfile should be written
     * @param gdzfile
     *            the FileFormat-Object to use for Mets-Writing
     */
    protected boolean writeMetsFile(Process process, String targetFileName, Fileformat gdzfile,
            boolean writeLocalFilegroup) throws PreferencesException, IOException, WriteException {
        FolderInformation fi = new FolderInformation(process.getId(), process.getTitle());
        Prefs preferences = serviceManager.getRulesetService().getPreferences(process.getRuleset());
        Project project = process.getProject();
        MetsModsImportExport mm = new MetsModsImportExport(preferences);
        mm.setWriteLocal(writeLocalFilegroup);
        URI imageFolderPath = fi.getImagesDirectory();
        File imageFolder = new File(imageFolderPath);
        /*
         * before creating mets file, change relative path to absolute -
         */
        DigitalDocument dd = gdzfile.getDigitalDocument();
        if (dd.getFileSet() == null) {
            Helper.setFehlerMeldung(process.getTitle() + ": digital document does not contain images; aborting");
            return false;
        }

        /*
         * get the topstruct element of the digital document depending on anchor
         * property
         */
        DocStruct topElement = dd.getLogicalDocStruct();
        if (preferences.getDocStrctTypeByName(topElement.getType().getName()).getAnchorClass() != null) {
            if (topElement.getAllChildren() == null || topElement.getAllChildren().size() == 0) {
                throw new PreferencesException(process.getTitle()
                        + ": the topstruct element is marked as anchor, but does not have any children for "
                        + "physical docstrucs");
            } else {
                topElement = topElement.getAllChildren().get(0);
            }
        }

        /*
         * if the top element does not have any image related, set them all
         */
        if (topElement.getAllToReferences("logical_physical") == null
                || topElement.getAllToReferences("logical_physical").size() == 0) {
            if (dd.getPhysicalDocStruct() != null && dd.getPhysicalDocStruct().getAllChildren() != null) {
                Helper.setMeldung(process.getTitle()
                        + ": topstruct element does not have any referenced images yet; temporarily adding them "
                        + "for mets file creation");
                for (DocStruct mySeitenDocStruct : dd.getPhysicalDocStruct().getAllChildren()) {
                    topElement.addReferenceTo(mySeitenDocStruct, "logical_physical");
                }
            } else {
                Helper.setFehlerMeldung(
                        process.getTitle() + ": could not find any referenced images, export aborted");
                return false;
            }
        }

        for (ContentFile cf : dd.getFileSet().getAllFiles()) {
            String location = cf.getLocation();
            // If the file's location string shoes no sign of any protocol,
            // use the file protocol.
            if (!location.contains("://")) {
                location = "file://" + location;
            }
            String url = new URL(location).getFile();
            File f = new File(!url.startsWith(imageFolder.toURL().getPath()) ? imageFolder : null, url);
            cf.setLocation(f.toURI().toString());
        }

        mm.setDigitalDocument(dd);

        /*
         * wenn Filegroups definiert wurden, werden diese jetzt in die
         * Metsstruktur bernommen
         */
        // Replace all paths with the given VariableReplacer, also the file
        // group paths!
        VariableReplacer vp = new VariableReplacer(mm.getDigitalDocument(), preferences, process, null);
        List<ProjectFileGroup> myFilegroups = project.getProjectFileGroups();

        if (myFilegroups != null && myFilegroups.size() > 0) {
            for (ProjectFileGroup pfg : myFilegroups) {
                // check if source files exists
                if (pfg.getFolder() != null && pfg.getFolder().length() > 0) {
                    URI folder = new File(fi.getMethodFromName(pfg.getFolder())).toURI();
                    if (fileService.fileExist(folder)
                            && serviceManager.getFileService().getSubUris(folder).size() > 0) {
                        VirtualFileGroup v = new VirtualFileGroup();
                        v.setName(pfg.getName());
                        v.setPathToFiles(vp.replace(pfg.getPath()));
                        v.setMimetype(pfg.getMimeType());
                        v.setFileSuffix(pfg.getSuffix());
                        mm.getDigitalDocument().getFileSet().addVirtualFileGroup(v);
                    }
                } else {

                    VirtualFileGroup v = new VirtualFileGroup();
                    v.setName(pfg.getName());
                    v.setPathToFiles(vp.replace(pfg.getPath()));
                    v.setMimetype(pfg.getMimeType());
                    v.setFileSuffix(pfg.getSuffix());
                    mm.getDigitalDocument().getFileSet().addVirtualFileGroup(v);
                }
            }
        }

        // Replace rights and digiprov entries.
        mm.setRightsOwner(vp.replace(project.getMetsRightsOwner()));
        mm.setRightsOwnerLogo(vp.replace(project.getMetsRightsOwnerLogo()));
        mm.setRightsOwnerSiteURL(vp.replace(project.getMetsRightsOwnerSite()));
        mm.setRightsOwnerContact(vp.replace(project.getMetsRightsOwnerMail()));
        mm.setDigiprovPresentation(vp.replace(project.getMetsDigiprovPresentation()));
        mm.setDigiprovReference(vp.replace(project.getMetsDigiprovReference()));
        mm.setDigiprovPresentationAnchor(vp.replace(project.getMetsDigiprovPresentationAnchor()));
        mm.setDigiprovReferenceAnchor(vp.replace(project.getMetsDigiprovReferenceAnchor()));

        mm.setPurlUrl(vp.replace(project.getMetsPurl()));
        mm.setContentIDs(vp.replace(project.getMetsContentIDs()));

        // Set mets pointers. MetsPointerPathAnchor or mptrAnchorUrl is the
        // pointer used to point to the superordinate (anchor) file, that is
        // representing a virtual? group such as a series. Several anchors
        // pointer paths can be defined/ since it is possible to define several
        // levels of superordinate structures (such as the complete edition of
        // a daily newspaper, one year ouf of that edition, )
        String anchorPointersToReplace = project.getMetsPointerPath();
        mm.setMptrUrl(null);
        for (String anchorPointerToReplace : anchorPointersToReplace.split(Project.ANCHOR_SEPARATOR)) {
            String anchorPointer = vp.replace(anchorPointerToReplace);
            mm.setMptrUrl(anchorPointer);
        }

        // metsPointerPathAnchor or mptrAnchorUrl is the pointer used to point
        // from the (lowest) superordinate (anchor) file to the lowest level
        // file (the non-anchor file).
        String anchor = project.getMetsPointerPathAnchor();
        String pointer = vp.replace(anchor);
        mm.setMptrAnchorUrl(pointer);

        try {
            // TODO andere Dateigruppen nicht mit image Namen ersetzen
            List<URI> images = fi.getDataFiles();
            List<String> imageStrings = new ArrayList<>();
            for (URI image : images) {
                imageStrings.add(image.getPath());
            }
            int sizeOfPagination = dd.getPhysicalDocStruct().getAllChildren().size();
            int sizeOfImages = images.size();
            if (sizeOfPagination == sizeOfImages) {
                dd.overrideContentFiles(imageStrings);
            } else {
                List<String> param = new ArrayList<>();
                param.add(String.valueOf(sizeOfPagination));
                param.add(String.valueOf(sizeOfImages));
                Helper.setFehlerMeldung(Helper.getTranslation("imagePaginationError", param));
                return false;
            }
        } catch (IndexOutOfBoundsException | InvalidImagesException e) {
            logger.error(e);
        }
        mm.write(targetFileName);
        Helper.setMeldung(null, process.getTitle() + ": ", "ExportFinished");
        return true;
    }

    /**
     * Starts copying all directories configured in kitodo_config.properties
     * parameter "processDirs" to export folder.
     *
     * @param myProcess
     *            the process object
     * @param targetDirectory
     *            the destination directory
     */
    private void directoryDownload(Process myProcess, URI targetDirectory) throws IOException {
        String[] processDirs = ConfigCore.getStringArrayParameter("processDirs");

        for (String processDir : processDirs) {
            URI sourceDirectory = URI.create(getProcessDataDirectory(myProcess).toString() + "/"
                    + processDir.replace("(processtitle)", myProcess.getTitle()));
            URI destinationDirectory = URI.create(
                    targetDirectory.toString() + "/" + processDir.replace("(processtitle)", myProcess.getTitle()));

            if (fileService.isDirectory(sourceDirectory)) {
                fileService.copyFile(sourceDirectory, destinationDirectory);
            }
        }
    }

    /**
     * Creates a List of Docket data for the given processes.
     * 
     * @param processes
     *            the process to create the docket data for.
     * @return A List of docketdata
     */
    public ArrayList<DocketData> getDocketData(List<Process> processes) {
        ArrayList<DocketData> docketdata = new ArrayList<>();
        for (Process process : processes) {
            docketdata.add(getDocketData(process));
        }
        return docketdata;
    }

    /**
     * Creates the DocketData for a given Process.
     * 
     * @param process
     *            The process to create the docket data for.
     * @return The DocketData for the process.
     */
    private DocketData getDocketData(Process process) {
        DocketData docketdata = new DocketData();

        docketdata.setCreationDate(process.getCreationDate().toString());
        docketdata.setProcessId(process.getId().toString());
        docketdata.setProcessName(process.getTitle());
        docketdata.setProjectName(process.getProject().getTitle());
        docketdata.setRulesetName(process.getRuleset().getTitle());
        docketdata.setComment(process.getWikiField());

        if (!process.getTemplates().isEmpty() && process.getTemplates().get(0) != null) {
            docketdata.setTemplateProperties(
                    getDocketDataForProperties(process.getTemplates().get(0).getProperties()));
        }
        if (!process.getWorkpieces().isEmpty() && process.getWorkpieces().get(0) != null) {
            docketdata.setWorkpieceProperties(
                    getDocketDataForProperties(process.getWorkpieces().get(0).getProperties()));
        }
        docketdata.setProcessProperties(getDocketDataForProperties(process.getProperties()));

        return docketdata;
    }

    private ArrayList<org.kitodo.api.docket.Property> getDocketDataForProperties(List<Property> properties) {
        ArrayList<org.kitodo.api.docket.Property> propertiesForDocket = new ArrayList<>();
        for (Property property : properties) {
            org.kitodo.api.docket.Property propertyForDocket = new org.kitodo.api.docket.Property();
            propertyForDocket.setId(property.getId());
            propertyForDocket.setTitle(property.getTitle());
            propertyForDocket.setValue(property.getValue());

            propertiesForDocket.add(propertyForDocket);

        }

        return propertiesForDocket;
    }

    /**
     * Find all processes sorted according to sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findAll(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findAllDocuments(sort), false);
    }

    /**
     * Find all not archived processes sorted according to sort query.
     *
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findNotArchivedProcesses(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findByArchived(false, sort), false);
    }

    /**
     * Find not closed processes sorted according to sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     *
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findNotClosedProcesses(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findBySortHelperStatus(false, sort), false);
    }

    /**
     * Find not closed and not archived processes sorted according to sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findNotClosedAndNotArchivedProcesses(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findBySortHelperStatusAndProjectArchived(false, false, sort), false);
    }

    /**
     * Find not archived templates sorted according to sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findNotArchivedTemplates(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findByArchivedAndTemplate(false, true, sort), false);
    }

    /**
     * Find all templates sorted according to sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findAllTemplates(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findByTemplate(true, sort), false);
    }

    /**
     * Find all processes, which are not a template sorted according to sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findAllWithoutTemplates(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findByTemplate(false, sort), false);
    }

    /**
     * Find all not archived processes which are not a template sorted according to
     * sort query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objectss
     */
    public List<ProcessDTO> findAllNotArchivedWithoutTemplates(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findByArchivedAndTemplate(false, false, sort), false);
    }

    /**
     * Find all not closed and not archived templates sorted according to sort
     * query.
     * 
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findAllNotClosedAndNotArchivedTemplates(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findBySortHelperStatusProjectArchivedAndTemplate(false, false, true, sort),
                false);
    }

    /**
     * Find all not closed templates sorted according to sort query.
     *
     * @param sort
     *            possible sort query according to which results will be sorted
     * @return the list of sorted processes as ProcessDTO objects
     */
    public List<ProcessDTO> findAllNotClosedTemplates(String sort) throws DataException {
        return convertJSONObjectsToDTOs(findBySortHelperStatusAndTemplate(false, true, sort), false);
    }

    /**
     * Get all process templates.
     *
     * @return list of all process templates as Process objects
     */
    public List<Process> getProcessTemplates() {
        return processDAO.getProcessTemplates();
    }

    /**
     * Get process templates for users.
     *
     * @param projects
     *            list of project ids fof user's projects
     * @return list of all process templates for user as Process objects
     */
    public List<Process> getProcessTemplatesForUser(ArrayList<Integer> projects) {
        return processDAO.getProcessTemplatesForUser(projects);
    }
}