Coverage Summary for Class: ProcessService (org.kitodo.production.services.data)
Class |
Class, %
|
Method, %
|
Line, %
|
ProcessService |
100%
(1/1)
|
47,3%
(70/148)
|
42,8%
(437/1020)
|
/*
* (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
*
* This file is part of the Kitodo project.
*
* It is licensed under GNU General Public License version 3 or later.
*
* For the full copyright and license information, please read the
* GPL3-License.txt file that was distributed with this source code.
*/
package org.kitodo.production.services.data;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
import static org.kitodo.data.database.enums.CorrectionComments.NO_CORRECTION_COMMENTS;
import static org.kitodo.data.database.enums.CorrectionComments.NO_OPEN_CORRECTION_COMMENTS;
import static org.kitodo.data.database.enums.CorrectionComments.OPEN_CORRECTION_COMMENTS;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.search.join.ScoreMode;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.XML;
import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalDivision;
import org.kitodo.api.dataformat.LogicalDivision;
import org.kitodo.api.dataformat.PhysicalDivision;
import org.kitodo.api.dataformat.Workpiece;
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.config.ConfigCore;
import org.kitodo.config.enums.ParameterCore;
import org.kitodo.data.database.beans.Batch;
import org.kitodo.data.database.beans.Comment;
import org.kitodo.data.database.beans.Folder;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Project;
import org.kitodo.data.database.beans.Property;
import org.kitodo.data.database.beans.Role;
import org.kitodo.data.database.beans.Ruleset;
import org.kitodo.data.database.beans.Task;
import org.kitodo.data.database.beans.User;
import org.kitodo.data.database.enums.CommentType;
import org.kitodo.data.database.enums.CorrectionComments;
import org.kitodo.data.database.enums.IndexAction;
import org.kitodo.data.database.enums.TaskStatus;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.database.persistence.BaseDAO;
import org.kitodo.data.database.persistence.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.index.type.enums.BatchTypeField;
import org.kitodo.data.elasticsearch.index.type.enums.ProcessTypeField;
import org.kitodo.data.elasticsearch.search.Searcher;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.exceptions.InvalidImagesException;
import org.kitodo.export.ExportMets;
import org.kitodo.production.dto.BatchDTO;
import org.kitodo.production.dto.ProcessDTO;
import org.kitodo.production.dto.ProjectDTO;
import org.kitodo.production.dto.PropertyDTO;
import org.kitodo.production.dto.TaskDTO;
import org.kitodo.production.enums.ObjectType;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.helper.SearchResultGeneration;
import org.kitodo.production.helper.WebDav;
import org.kitodo.production.helper.metadata.ImageHelper;
import org.kitodo.production.helper.metadata.MetadataHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyDocStructHelperInterface;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetadataHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetadataTypeHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyPrefsHelper;
import org.kitodo.production.metadata.MetadataEditor;
import org.kitodo.production.metadata.copier.CopierData;
import org.kitodo.production.metadata.copier.DataCopier;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.data.base.ProjectSearchService;
import org.kitodo.production.services.dataformat.MetsService;
import org.kitodo.production.services.file.FileService;
import org.kitodo.production.services.workflow.WorkflowControllerService;
import org.kitodo.production.workflow.KitodoNamespaceContext;
import org.kitodo.serviceloader.KitodoServiceLoader;
import org.primefaces.model.charts.ChartData;
import org.primefaces.model.charts.axes.cartesian.linear.CartesianLinearAxes;
import org.primefaces.model.charts.bar.BarChartOptions;
import org.primefaces.model.charts.hbar.HorizontalBarChartDataSet;
import org.primefaces.model.charts.hbar.HorizontalBarChartModel;
import org.primefaces.model.charts.optionconfig.tooltip.Tooltip;
import org.primefaces.model.charts.pie.PieChartDataSet;
import org.primefaces.model.charts.pie.PieChartModel;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ProcessService extends ProjectSearchService<Process, ProcessDTO, ProcessDAO> {
private static final FileService fileService = ServiceManager.getFileService();
private static final Logger logger = LogManager.getLogger(ProcessService.class);
private static volatile ProcessService instance = null;
private static final String JSON_TITLE = "title";
private static final String JSON_VALUE = "value";
private static final String DIRECTORY_PREFIX = ConfigCore.getParameter(ParameterCore.DIRECTORY_PREFIX, "orig");
private static final String DIRECTORY_SUFFIX = ConfigCore.getParameter(ParameterCore.DIRECTORY_SUFFIX, "tif");
private static final String SUFFIX = ConfigCore.getParameter(ParameterCore.METS_EDITOR_DEFAULT_SUFFIX, "");
private static final String PROCESS_TITLE = "(processtitle)";
private static final String METADATA_SEARCH_KEY = ProcessTypeField.METADATA + ".mdWrap.xmlData.kitodo.metadata";
private static final String METADATA_GROUP_SEARCH_KEY = ProcessTypeField.METADATA + ".mdWrap.xmlData.kitodo.metadataGroup.metadata";
private static final String METADATA_FILE_NAME = "meta.xml";
private static final String NEW_LINE_ENTITY = "\n";
private static final boolean USE_ORIG_FOLDER = ConfigCore
.getBooleanParameterOrDefaultValue(ParameterCore.USE_ORIG_FOLDER);
private static final Map<Integer, Collection<String>> RULESET_CACHE_FOR_CREATE_FROM_CALENDAR = new HashMap<>();
private static final Map<Integer, Collection<String>> RULESET_CACHE_FOR_CREATE_CHILD_FROM_PARENT = new HashMap<>();
private static final List<String> BG_COLORS = Arrays
.asList(ConfigCore.getParameterOrDefaultValue(ParameterCore.ISSUE_COLOURS).split(";"));
/**
* Constructor with Searcher and Indexer assigning.
*/
private ProcessService() {
super(new ProcessDAO(), new ProcessType(), new Indexer<>(Process.class), new Searcher(Process.class),
ProcessTypeField.PROJECT_CLIENT_ID.getKey(), ProcessTypeField.PROJECT_ID.getKey());
}
/**
* Return singleton variable of type ProcessService.
*
* @return unique instance of ProcessService
*/
public static ProcessService getInstance() {
ProcessService localReference = instance;
if (Objects.isNull(localReference)) {
synchronized (ProcessService.class) {
localReference = instance;
if (Objects.isNull(localReference)) {
localReference = new ProcessService();
instance = localReference;
}
}
}
return localReference;
}
/**
* Emptys the cache generated from ruleset, so changes in Ruleset are recognized in new session.
*/
public static void emptyCache() {
RULESET_CACHE_FOR_CREATE_CHILD_FROM_PARENT.clear();
RULESET_CACHE_FOR_CREATE_FROM_CALENDAR.clear();
}
/**
* Checks if an imported Process should be created with Tasks and removes them if not,
* depending on the configuration of the doctype.
* @param process the process to check.
* @param docType the doctype to check in the ruleset.
*/
public static void checkTasks(Process process, String docType) throws IOException {
// remove tasks from process, if doctype is configured not to use a workflow
Collection<String> divisionsWithNoWorkflow = ServiceManager.getRulesetService()
.openRuleset(process.getRuleset()).getDivisionsWithNoWorkflow();
if (divisionsWithNoWorkflow.contains(docType)) {
process.getTasks().clear();
}
}
@Override
public Long countDatabaseRows() throws DAOException {
return countDatabaseRows("SELECT COUNT(*) FROM Process WHERE " + BaseDAO.getDateFilter("creationDate"));
}
@Override
public Long countNotIndexedDatabaseRows() throws DAOException {
return countDatabaseRows("SELECT COUNT(*) FROM Process WHERE " + BaseDAO.getDateFilter("creationDate")
+ " AND (indexAction = 'INDEX' OR indexAction IS NULL)");
}
@Override
public Long countResults(Map filters) throws DataException {
return countResults(filters, false, false);
}
public Long countResults(Map filters, boolean showClosedProcesses, boolean showInactiveProjects)
throws DataException {
return countDocuments(createUserProcessesQuery(filters, showClosedProcesses, showInactiveProjects));
}
@Override
public List<Process> getAllNotIndexed() {
return getByQuery("FROM Process WHERE " + BaseDAO.getDateFilter("creationDate")
+ " AND (indexAction = 'INDEX' OR indexAction IS NULL)");
}
@Override
public List<Process> getAllForSelectedClient() {
throw new UnsupportedOperationException();
}
@Override
public void save(Process process) throws DataException {
this.save(process, false);
}
@Override
public void save(Process process, boolean updateRelatedObjectsInIndex) throws DataException {
WorkflowControllerService.updateProcessSortHelperStatus(process);
// save parent processes if they are new and do not have an id yet
List<Process> parents = findParentProcesses(process);
for (Process parent: parents) {
if (Objects.isNull(parent.getId())) {
super.save(parent, updateRelatedObjectsInIndex);
}
}
super.save(process, updateRelatedObjectsInIndex);
// save parent processes in order to refresh ElasticSearch index
for (Process parent : parents) {
super.save(parent, updateRelatedObjectsInIndex);
}
}
@Override
public void saveToIndex(Process process, boolean forceRefresh)
throws CustomResponseException, DataException, IOException {
enrichProcessData(process, false);
super.saveToIndex(process, forceRefresh);
}
/**
* Find all parent processes for a process ordered such that the root parent comes first.
*
* @param process the process whose parents are to be found
* @return the list of parent processes (direct parents and grand parents, and more)
*/
public List<Process> findParentProcesses(Process process) {
List<Process> parents = new ArrayList<Process>();
Process current = process;
while (Objects.nonNull(current.getParent())) {
current = current.getParent();
parents.add(current);
}
Collections.reverse(parents);
return parents;
}
private int getNumberOfImagesForIndex(Workpiece workpiece) {
return Math.toIntExact(Workpiece.treeStream(workpiece.getPhysicalStructure())
.filter(physicalDivision -> Objects.equals(physicalDivision.getType(), PhysicalDivision.TYPE_PAGE)).count());
}
private int getNumberOfMetadata(Workpiece workpiece) {
return Math.toIntExact(MetsService.countLogicalMetadata(workpiece));
}
private int getNumberOfStructures(Workpiece workpiece) {
return Math.toIntExact(Workpiece.treeStream(workpiece.getLogicalStructure()).count());
}
@Override
public void addAllObjectsToIndex(List<Process> processes) throws CustomResponseException, DAOException, IOException {
for (Process process : processes) {
enrichProcessData(process, true);
}
super.addAllObjectsToIndex(processes);
}
private void enrichProcessData(Process process, boolean forIndexingAll) throws IOException {
process.setMetadata(getMetadataForIndex(process, forIndexingAll));
URI metadataFilePath = fileService.getMetadataFilePath(process, false, forIndexingAll);
if (!fileService.fileExist(metadataFilePath)) {
logger.info("No metadata file for indexing: {}", metadataFilePath);
} else {
Workpiece workpiece = ServiceManager.getMetsService().loadWorkpiece(metadataFilePath);
process.setNumberOfImages(getNumberOfImagesForIndex(workpiece));
process.setNumberOfMetadata(getNumberOfMetadata(workpiece));
process.setNumberOfStructures(getNumberOfStructures(workpiece));
process.setBaseType(getBaseType(workpiece));
}
}
/**
* MetadataType aus Preferences eines Prozesses ermitteln.
*
* @param inProzess
* Process object
* @param inName
* String
* @return MetadataType
*/
@Deprecated
public static LegacyMetadataTypeHelper getMetadataType(Process inProzess, String inName) {
LegacyPrefsHelper myPrefs = ServiceManager.getRulesetService().getPreferences(inProzess.getRuleset());
return LegacyPrefsHelper.getMetadataType(myPrefs, inName);
}
@Override
public List<ProcessDTO> loadData(int first, int pageSize, String sortField,
org.primefaces.model.SortOrder sortOrder, Map filters) throws DataException {
return loadData(first, pageSize, sortField, sortOrder, filters, false, false);
}
/**
* Load processes with given parameters.
* @param first index of first process to load
* @param pageSize number of processes to load
* @param sortField name of field by which processes are sorted
* @param sortOrder SortOrder by which processes are sorted - either ascending or descending
* @param filters filter map
* @param showClosedProcesses boolean controlling whether to load closed processes or not
* @param showInactiveProjects boolean controlling whether to load processes of closed projects or not
* @return List of loaded processes
* @throws DataException if processes cannot be loaded from search index
*/
public List<ProcessDTO> loadData(int first, int pageSize, String sortField,
org.primefaces.model.SortOrder sortOrder, Map filters,
boolean showClosedProcesses, boolean showInactiveProjects) throws DataException {
String filter = ServiceManager.getFilterService().parseFilterString(filters);
return findByQuery(getQueryForFilter(showClosedProcesses, showInactiveProjects, filter),
getSortBuilder(sortField, sortOrder), first, pageSize, false);
}
/**
* Gets the query for the current processfilter.
* @param showClosedProcesses if closed processes are shown
* @param showInactiveProjects if inactive projects are shown
* @param filter the filter to build the query for
* @return the query for the filter.
*/
public BoolQueryBuilder getQueryForFilter(boolean showClosedProcesses, boolean showInactiveProjects, String filter) {
return new SearchResultGeneration(filter, showClosedProcesses,
showInactiveProjects).getQueryForFilter(ObjectType.PROCESS);
}
private BoolQueryBuilder readFilters(Map<String, String> filterMap) throws DataException {
BoolQueryBuilder query = new BoolQueryBuilder();
for (Map.Entry<String, String> entry : filterMap.entrySet()) {
query.must(
ServiceManager.getFilterService().queryBuilder(entry.getValue(), ObjectType.PROCESS, false, false));
}
return query;
}
@SuppressWarnings("unchecked")
private BoolQueryBuilder createUserProcessesQuery(Map filters, boolean showClosedProcesses,
boolean showInactiveProjects)
throws DataException {
BoolQueryBuilder query = new BoolQueryBuilder();
if (Objects.nonNull(filters) && !filters.isEmpty()) {
query.must(readFilters(filters));
}
if (!showClosedProcesses) {
query.mustNot(getQueryForClosedProcesses());
}
if (!showInactiveProjects) {
query.mustNot(getQueryProjectActive(false));
}
return query;
}
/**
* 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);
}
/**
* 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, DataException, IOException {
if (process.getIndexAction() == IndexAction.DELETE) {
for (Batch batch : process.getBatches()) {
batch.getProcesses().remove(process);
ServiceManager.getBatchService().saveToIndex(batch, false);
}
} else {
for (Batch batch : process.getBatches()) {
ServiceManager.getBatchService().saveToIndex(batch, false);
}
}
}
/**
* Add process to project, if project is assigned to process.
*
* @param process
* object
*/
private void manageProjectDependenciesForIndex(Process process)
throws CustomResponseException, DataException, IOException {
if (Objects.nonNull(process.getProject())) {
ServiceManager.getProjectService().saveToIndex(process.getProject(), false);
}
}
/**
* 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, false);
}
} 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, false);
}
List<Map<String, Object>> searchResults = ServiceManager.getTaskService().findByProcessId(process.getId());
for (Map<String, Object> object : searchResults) {
index.add(getIdFromJSONObject(object));
}
List<Integer> missingInIndex = findMissingValues(database, index);
List<Integer> notNeededInIndex = findMissingValues(index, database);
for (Integer missing : missingInIndex) {
ServiceManager.getTaskService().saveToIndex(ServiceManager.getTaskService().getById(missing), false);
}
for (Integer notNeeded : notNeededInIndex) {
ServiceManager.getTaskService().removeFromIndex(notNeeded, false);
}
}
/**
* 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 list of processes to database.
*
* @param list
* of processes
*/
public void saveList(List<Process> list) throws DAOException {
dao.saveList(list);
}
@Override
public void refresh(Process process) {
dao.refresh(process);
}
List<Map<String, Object>> findForCurrentSessionClient() throws DataException {
return findDocuments(
getQueryProjectIsAssignedToSelectedClient(ServiceManager.getUserService().getSessionClientId()));
}
/**
* Find processes by metadata. Matches do not need to be exact.
*
* @param metadata
* key is metadata tag and value is metadata content
* @return list of ProcessDTO objects with processes for specific metadata tag
*/
public List<ProcessDTO> findByMetadata(Map<String, String> metadata) throws DataException {
return findByMetadata(metadata, false);
}
/**
* Find processes by metadata.
*
* @param metadata
* key is metadata tag and value is metadata content
* @param exactMatch
* online return exact matches
* @return list of ProcessDTO objects with processes for specific metadata tag
*/
public List<ProcessDTO> findByMetadata(Map<String, String> metadata, boolean exactMatch) throws DataException {
String nameSearchKey = METADATA_SEARCH_KEY + ".name";
String contentSearchKey = METADATA_SEARCH_KEY + ".content";
if (exactMatch) {
nameSearchKey = nameSearchKey + ".keyword";
contentSearchKey = contentSearchKey + ".keyword";
}
BoolQueryBuilder query = new BoolQueryBuilder();
for (Map.Entry<String, String> entry : metadata.entrySet()) {
BoolQueryBuilder pairQuery = new BoolQueryBuilder();
pairQuery.must(matchQuery(nameSearchKey, entry.getKey()));
pairQuery.must(matchQuery(contentSearchKey, entry.getValue()));
query.must(pairQuery);
}
return findByQuery(nestedQuery(METADATA_SEARCH_KEY, query, ScoreMode.Total), true);
}
/**
* Find processes by title.
*
* @param title
* the title
* @return a list of processes
* @throws DataException
* when there is an error on conversion
*/
public List<ProcessDTO> findByTitle(String title) throws DataException {
return convertJSONObjectsToDTOs(findByTitle(title, true), true);
}
/**
* Finds processes by searchQuery for a number of fields.
*
* @param searchQuery
* the query word or phrase
* @return a List of found ProcessDTOs
* @throws DataException
* when accessing the elasticsearch server fails
*/
public List<ProcessDTO> findByAnything(String searchQuery) throws DataException {
NestedQueryBuilder nestedQueryForMetadataContent = nestedQuery(METADATA_SEARCH_KEY,
matchQuery(METADATA_SEARCH_KEY + ".content", searchQuery).operator(Operator.AND), ScoreMode.Total);
NestedQueryBuilder nestedQueryForMetadataGroupContent = nestedQuery(METADATA_GROUP_SEARCH_KEY,
matchQuery(METADATA_GROUP_SEARCH_KEY + ".content", searchQuery).operator(Operator.AND), ScoreMode.Total);
MultiMatchQueryBuilder multiMatchQueryForProcessFields = multiMatchQuery(searchQuery,
ProcessTypeField.TITLE.getKey(),
ProcessTypeField.PROJECT_TITLE.getKey(),
ProcessTypeField.COMMENTS.getKey(),
ProcessTypeField.WIKI_FIELD.getKey(),
ProcessTypeField.TEMPLATE_TITLE.getKey()).operator(Operator.AND);
if (searchQuery.matches("^\\d*$")) {
multiMatchQueryForProcessFields.fields().put(ProcessTypeField.ID.getKey(), 1.0f);
}
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
boolQuery.should(nestedQueryForMetadataContent);
boolQuery.should(nestedQueryForMetadataGroupContent);
boolQuery.should(multiMatchQueryForProcessFields);
if (!searchQuery.contains(" ")) {
QueryBuilder wildcardQueryForProcessTitle = createSimpleWildcardQuery(ProcessTypeField.TITLE.getKey(),
searchQuery);
QueryBuilder wildcardQueryForProjectTitle = createSimpleWildcardQuery(
ProcessTypeField.PROJECT_TITLE.getKey(), searchQuery);
QueryBuilder wildcardQueryForComments = createSimpleWildcardQuery(
ProcessTypeField.COMMENTS_MESSAGE.getKey(), searchQuery);
boolQuery.should(wildcardQueryForProcessTitle);
boolQuery.should(wildcardQueryForProjectTitle);
boolQuery.should(wildcardQueryForComments);
}
return findByQuery(boolQuery, false);
}
/**
* Get query for find process by project title.
*
* @param title
* as String
* @return QueryBuilder object
*/
public QueryBuilder getQueryProjectTitle(String title) {
return createSimpleQuery(ProcessTypeField.PROJECT_TITLE.getKey(), title, true, Operator.AND);
}
/**
* Get query for find process by project id.
*
* @param projectId
* as Integer
* @return QueryBuilder object
*/
public QueryBuilder getQueryProjectId(Integer projectId) {
return createSimpleQuery(ProcessTypeField.PROJECT_ID.getKey(), projectId.toString(), true, Operator.AND);
}
/**
* Find processes by docket id.
*
* @param docketId
* id of docket for search
* @return list of JSON objects with processes for specific docket id
*/
public List<Map<String, Object>> findByDocket(int docketId) throws DataException {
QueryBuilder query = createSimpleQuery(ProcessTypeField.DOCKET.getKey(), docketId, true);
return findDocuments(query);
}
/**
* Find processes by template id.
*
* @param templateId
* id of template for search
* @return list of JSON objects with processes for specific template id
* @throws DataException if documents cannot be retrieved
*/
public List<Map<String, Object>> findByTemplate(int templateId) throws DataException {
QueryBuilder query = createSimpleQuery(ProcessTypeField.TEMPLATE_ID.getKey(), templateId, true);
return findDocuments(query);
}
/**
* Find processes by ruleset id.
*
* @param rulesetId
* id of ruleset for search
* @return list of JSON objects with processes for specific ruleset id
*/
public List<Map<String, Object>> findByRuleset(int rulesetId) throws DataException {
QueryBuilder query = createSimpleQuery(ProcessTypeField.RULESET.getKey(), rulesetId, true);
return findDocuments(query);
}
/**
* Get query for projects assigned to selected client.
*
* @param id
* of selected client
* @return query as QueryBuilder
*/
private QueryBuilder getQueryProjectIsAssignedToSelectedClient(int id) {
return createSimpleQuery(ProcessTypeField.PROJECT_CLIENT_ID.getKey(), id, true);
}
/**
* Searches for linkable processes based on user input. A process can be
* linked if it has the same rule set, belongs to the same client, and the
* topmost element of the logical outline below the selected parent element
* is an allowed child. For the latter, the data file must be read at the
* moment. This will be aborted after a timeout so that the user gets an
* answer (which may be incomplete) in finite time.
*
* @param searchInput
* user input
* @param rulesetId
* the id of the allowed ruleset
* @param allowedStructuralElementTypes
* allowed topmost logical structural elements
* @return found processes
* @throws DataException
* if the search engine fails
*/
public List<ProcessDTO> findLinkableChildProcesses(String searchInput, int rulesetId,
Collection<String> allowedStructuralElementTypes) throws DataException {
BoolQueryBuilder query = new BoolQueryBuilder()
.must(new BoolQueryBuilder()
.should(new MatchQueryBuilder(ProcessTypeField.ID.getKey(), searchInput).lenient(true))
.should(new WildcardQueryBuilder(ProcessTypeField.TITLE.getKey(), "*" + searchInput + "*")))
.must(new MatchQueryBuilder(ProcessTypeField.RULESET.getKey(), rulesetId));
List<ProcessDTO> linkableProcesses = new LinkedList<>();
List<ProcessDTO> processDTOS = findByQuery(query, false);
for (ProcessDTO process : processDTOS) {
if (allowedStructuralElementTypes.contains(getBaseType(process.getId()))) {
linkableProcesses.add(process);
}
}
return linkableProcesses;
}
/**
* Searches for linkable processes based on user input. A process can be
* linked if it has the same rule set, belongs to the same client, and the
* topmost element of the logical outline below the selected parent element
* is an allowed child. For the latter, the data file must be read at the
* moment. This will be aborted after a timeout so that the user gets an
* answer (which may be incomplete) in finite time.
*
* @param searchInput
* user input
* @param projectId
* the id of the allowed project
* @param rulesetId
* the id of the allowed ruleset
* @return found processes
* @throws DataException
* if the search engine fails
*/
public List<ProcessDTO> findLinkableParentProcesses(String searchInput, int projectId, int rulesetId)
throws DataException {
BoolQueryBuilder processQuery = new BoolQueryBuilder()
.should(createSimpleWildcardQuery(ProcessTypeField.TITLE.getKey(), searchInput));
if (searchInput.matches("\\d*")) {
processQuery.should(new MatchQueryBuilder(ProcessTypeField.ID.getKey(), searchInput));
}
BoolQueryBuilder query = new BoolQueryBuilder().must(processQuery)
.must(new MatchQueryBuilder(ProcessTypeField.PROJECT_ID.getKey(), projectId))
.must(new MatchQueryBuilder(ProcessTypeField.RULESET.getKey(), rulesetId));
return findByQuery(query, false);
}
/**
* Find processes by property.
*
* @param title
* of property
* @param value
* of property
* @return list of JSON objects with processes for specific property
*/
public List<ProcessDTO> findByProperty(String title, String value) throws DataException {
return findByQuery(createPropertyQuery(title, value), true);
}
/**
* Creates the query fpr properties with title and value.
* @param title the property title
* @param value the property value
* @return a query for searching for properties.
*/
public QueryBuilder createPropertyQuery(String title, String value) {
String titleSearchKey = ProcessTypeField.PROPERTIES + ".title.keyword";
String valueSearchKey = ProcessTypeField.PROPERTIES + ".value.keyword";
BoolQueryBuilder pairQuery = new BoolQueryBuilder();
if (!WILDCARD.equals(title)) {
pairQuery.must(matchQuery(titleSearchKey, title));
}
if (!WILDCARD.equals(value)) {
pairQuery.must(matchQuery(valueSearchKey, value));
}
return nestedQuery(ProcessTypeField.PROPERTIES.toString(), pairQuery, ScoreMode.Total);
}
List<ProcessDTO> findByProjectIds(Set<Integer> projectIds, boolean related) throws DataException {
QueryBuilder query = createSetQuery("project.id", projectIds, true);
return findByQuery(query, related);
}
/**
* Get Query for closed processes.
*
* @return query as QueryBuilder
*/
public QueryBuilder getQueryForClosedProcesses() {
BoolQueryBuilder query = new BoolQueryBuilder();
query.should(createSimpleQuery(ProcessTypeField.SORT_HELPER_STATUS.getKey(), "100000000", true));
query.should(createSimpleQuery(ProcessTypeField.SORT_HELPER_STATUS.getKey(), "100000000000", true));
return query;
}
/**
* Get query for active projects.
*
* @param active
* true or false
* @return query as QueryBuilder
*/
public QueryBuilder getQueryProjectActive(boolean active) {
return createSimpleQuery(ProcessTypeField.PROJECT_ACTIVE.getKey(), active, true);
}
/**
* Sort results by creation date.
*
* @param sortOrder
* ASC or DESC as SortOrder
* @return sort
*/
public SortBuilder sortByCreationDate(SortOrder sortOrder) {
return SortBuilders.fieldSort(ProcessTypeField.CREATION_DATE.getKey()).order(sortOrder);
}
/**
* Convert list of DTOs to list of beans.
*
* @param dtos
* list of DTO objects
* @return list of beans
*/
public List<Process> convertDtosToBeans(List<ProcessDTO> dtos) throws DAOException {
List<Process> processes = new ArrayList<>();
for (ProcessDTO processDTO : dtos) {
processes.add(getById(processDTO.getId()));
}
return processes;
}
@Override
public ProcessDTO convertJSONObjectToDTO(Map<String, Object> jsonObject, boolean related) throws DataException {
ProcessDTO processDTO = new ProcessDTO();
if (!jsonObject.isEmpty()) {
processDTO.setId(getIdFromJSONObject(jsonObject));
processDTO.setTitle(ProcessTypeField.TITLE.getStringValue(jsonObject));
processDTO.setWikiField(ProcessTypeField.WIKI_FIELD.getStringValue(jsonObject));
processDTO.setCreationDate(ProcessTypeField.CREATION_DATE.getStringValue(jsonObject));
processDTO.setSortHelperArticles(ProcessTypeField.SORT_HELPER_ARTICLES.getIntValue(jsonObject));
processDTO.setSortHelperDocstructs(ProcessTypeField.SORT_HELPER_DOCSTRUCTS.getIntValue(jsonObject));
processDTO.setSortHelperImages(ProcessTypeField.SORT_HELPER_IMAGES.getIntValue(jsonObject));
processDTO.setSortHelperMetadata(ProcessTypeField.SORT_HELPER_METADATA.getIntValue(jsonObject));
processDTO.setSortHelperStatus(ProcessTypeField.SORT_HELPER_STATUS.getStringValue(jsonObject));
processDTO.setProcessBaseUri(ProcessTypeField.PROCESS_BASE_URI.getStringValue(jsonObject));
processDTO.setHasChildren(ProcessTypeField.HAS_CHILDREN.getBooleanValue(jsonObject));
processDTO.setParentID(ProcessTypeField.PARENT_ID.getIntValue(jsonObject));
processDTO.setNumberOfImages(ProcessTypeField.NUMBER_OF_IMAGES.getIntValue(jsonObject));
processDTO.setNumberOfMetadata(ProcessTypeField.NUMBER_OF_METADATA.getIntValue(jsonObject));
processDTO.setNumberOfStructures(ProcessTypeField.NUMBER_OF_STRUCTURES.getIntValue(jsonObject));
processDTO.setBaseType(ProcessTypeField.BASE_TYPE.getStringValue(jsonObject));
processDTO.setLastEditingUser(ProcessTypeField.LAST_EDITING_USER.getStringValue(jsonObject));
processDTO.setCorrectionCommentStatus(ProcessTypeField.CORRECTION_COMMENT_STATUS.getIntValue(jsonObject));
processDTO.setHasComments(!ProcessTypeField.COMMENTS_MESSAGE.getStringValue(jsonObject).isEmpty());
convertLastProcessingDates(jsonObject, processDTO);
convertTaskProgress(jsonObject, processDTO);
List<Map<String, Object>> jsonArray = ProcessTypeField.PROPERTIES.getJsonArray(jsonObject);
List<PropertyDTO> properties = new ArrayList<>();
for (Map<String, Object> stringObjectMap : jsonArray) {
PropertyDTO propertyDTO = new PropertyDTO();
Object title = stringObjectMap.get(JSON_TITLE);
Object value = stringObjectMap.get(JSON_VALUE);
if (Objects.nonNull(title)) {
propertyDTO.setTitle(title.toString());
propertyDTO.setValue(Objects.nonNull(value) ? value.toString() : "");
properties.add(propertyDTO);
}
}
processDTO.setProperties(properties);
if (!related) {
convertRelatedJSONObjects(jsonObject, processDTO);
} else {
ProjectDTO projectDTO = new ProjectDTO();
projectDTO.setId(ProcessTypeField.PROJECT_ID.getIntValue(jsonObject));
projectDTO.setTitle(ProcessTypeField.PROJECT_TITLE.getStringValue(jsonObject));
projectDTO.setActive(ProcessTypeField.PROJECT_ACTIVE.getBooleanValue(jsonObject));
processDTO.setProject(projectDTO);
}
}
return processDTO;
}
/**
* Parses last processing dates from the jsonObject and adds them to the processDTO bean.
*
* @param jsonObject the json object retrieved from elastic search
* @param processDTO the processDTO bean that will receive the processing dates
*/
private void convertLastProcessingDates(Map<String, Object> jsonObject, ProcessDTO processDTO) throws DataException {
String processingBeginLastTask = ProcessTypeField.PROCESSING_BEGIN_LAST_TASK.getStringValue(jsonObject);
processDTO.setProcessingBeginLastTask(Helper.parseDateFromFormattedString(processingBeginLastTask));
String processingEndLastTask = ProcessTypeField.PROCESSING_END_LAST_TASK.getStringValue(jsonObject);
processDTO.setProcessingEndLastTask(Helper.parseDateFromFormattedString(processingEndLastTask));
}
/**
* Parses task progress properties from the jsonObject and adds them to the processDTO bean.
*
* @param jsonObject the json object retrieved from elastic search
* @param processDTO the processDTO bean that will receive the progress information
*/
private void convertTaskProgress(Map<String, Object> jsonObject, ProcessDTO processDTO) throws DataException {
processDTO.setProgressClosed(ProcessTypeField.PROGRESS_CLOSED.getDoubleValue(jsonObject));
processDTO.setProgressInProcessing(ProcessTypeField.PROGRESS_IN_PROCESSING.getDoubleValue(jsonObject));
processDTO.setProgressOpen(ProcessTypeField.PROGRESS_OPEN.getDoubleValue(jsonObject));
processDTO.setProgressLocked(ProcessTypeField.PROGRESS_LOCKED.getDoubleValue(jsonObject));
processDTO.setProgressCombined(ProcessTypeField.PROGRESS_COMBINED.getStringValue(jsonObject));
}
private void convertRelatedJSONObjects(Map<String, Object> jsonObject, ProcessDTO processDTO) throws DataException {
int project = ProcessTypeField.PROJECT_ID.getIntValue(jsonObject);
if (project > 0) {
processDTO.setProject(ServiceManager.getProjectService().findById(project, true));
}
int ruleset = ProcessTypeField.RULESET.getIntValue(jsonObject);
if (ruleset > 0) {
processDTO.setRuleset(ServiceManager.getRulesetService().findById(ruleset, true));
}
processDTO.setBatchID(getBatchID(processDTO));
processDTO.setBatches(getBatchesForProcessDTO(jsonObject));
// TODO: leave it for now - right now it displays only status
processDTO.setTasks(convertRelatedJSONObjectToDTO(jsonObject, ProcessTypeField.TASKS.getKey(),
ServiceManager.getTaskService()));
}
private List<BatchDTO> getBatchesForProcessDTO(Map<String, Object> jsonObject) throws DataException {
List<Map<String, Object>> jsonArray = ProcessTypeField.BATCHES.getJsonArray(jsonObject);
List<BatchDTO> batchDTOList = new ArrayList<>();
for (Map<String, Object> singleObject : jsonArray) {
BatchDTO batchDTO = new BatchDTO();
batchDTO.setId(BatchTypeField.ID.getIntValue(singleObject));
batchDTO.setTitle(BatchTypeField.TITLE.getStringValue(singleObject));
batchDTOList.add(batchDTO);
}
return batchDTOList;
}
/**
* Check if process is assigned only to one batch.
*
* @param batchDTOList
* list of batches for checkout
* @return true or false
*/
boolean isProcessAssignedToOnlyOneBatch(List<BatchDTO> batchDTOList) {
return batchDTOList.size() == 1;
}
/**
* Get directory for TIFF images.
*
* @param useFallBack
* add description
* @param processId
* id of process object
* @param processTitle
* title of process object
* @param processBaseURI
* base URI of process object
* @return tif directory
*/
public URI getImagesTifDirectory(boolean useFallBack, Integer processId, String processTitle, URI processBaseURI) {
URI dir = fileService.getProcessSubTypeURI(processId, processTitle, processBaseURI, ProcessSubType.IMAGE, null);
/* nur die _tif-Ordner anzeigen, die nicht mir orig_ anfangen */
FilenameFilter filterDirectory = new FileNameEndsAndDoesNotBeginWithFilter(DIRECTORY_PREFIX + "_",
"_" + DIRECTORY_SUFFIX);
URI tifDirectory = null;
List<URI> directories = fileService.getSubUris(filterDirectory, dir);
for (URI directory : directories) {
tifDirectory = directory;
}
if (Objects.isNull(tifDirectory) && useFallBack && !SUFFIX.isEmpty()) {
List<URI> folderList = fileService.getSubUrisForProcess(null, processId, processTitle, processBaseURI,
ProcessSubType.IMAGE, "");
for (URI folder : folderList) {
if (folder.toString().endsWith(SUFFIX)) {
tifDirectory = folder;
break;
}
}
}
tifDirectory = getImageDirectory(useFallBack, dir, tifDirectory);
URI result = fileService.getProcessSubTypeURI(processId, processTitle, processBaseURI, ProcessSubType.IMAGE,
null);
if (Objects.isNull(tifDirectory)) {
tifDirectory = URI
.create(result.getRawPath() + Helper.getNormalizedTitle(processTitle) + "_" + DIRECTORY_SUFFIX);
}
return tifDirectory;
}
/**
* Get images origin directory.
*
* @param useFallBack
* as boolean
* @param process
* object
* @return path
*/
public URI getImagesOriginDirectory(boolean useFallBack, Process process) {
if (USE_ORIG_FOLDER) {
URI dir = fileService.getProcessSubTypeURI(process, ProcessSubType.IMAGE, null);
/* nur die _tif-Ordner anzeigen, die mit orig_ anfangen */
FilenameFilter filterDirectory = new FileNameBeginsAndEndsWithFilter(DIRECTORY_PREFIX + "_",
"_" + DIRECTORY_SUFFIX);
URI origDirectory = null;
List<URI> directories = fileService.getSubUris(filterDirectory, dir);
for (URI directory : directories) {
origDirectory = directory;
}
if (Objects.isNull(origDirectory) && useFallBack && !SUFFIX.isEmpty()) {
List<URI> folderList = fileService.getSubUris(dir);
for (URI folder : folderList) {
if (folder.toString().endsWith(SUFFIX)) {
origDirectory = folder;
break;
}
}
}
origDirectory = getImageDirectory(useFallBack, dir, origDirectory);
URI result = fileService.getProcessSubTypeURI(process, ProcessSubType.IMAGE, null);
if (Objects.isNull(origDirectory)) {
origDirectory = URI.create(result.toString() + DIRECTORY_PREFIX + "_"
+ Helper.getNormalizedTitle(process.getTitle()) + "_" + DIRECTORY_SUFFIX);
}
return origDirectory;
} else {
return getImagesTifDirectory(useFallBack, process.getId(), process.getTitle(), process.getProcessBaseUri());
}
}
private URI getImageDirectory(boolean useFallBack, URI directory, URI imageDirectory) {
if (Objects.nonNull(imageDirectory) && useFallBack && !SUFFIX.isEmpty()) {
URI tif = imageDirectory;
List<URI> files = fileService.getSubUris(tif);
if (files.isEmpty()) {
List<URI> folderList = fileService.getSubUris(directory);
for (URI folder : folderList) {
if (folder.toString().endsWith(SUFFIX) && !folder.getPath().startsWith(DIRECTORY_PREFIX)) {
imageDirectory = folder;
break;
}
}
}
}
return imageDirectory;
}
/**
* Returns the URI of the metadata file of a process.
*
* @param process
* object
* @return URI
*/
public URI getMetadataFileUri(Process process) {
URI workPathUri = ServiceManager.getFileService().getProcessBaseUriForExistingProcess(process);
String workDirectoryPath = workPathUri.getPath();
try {
return new URI(workPathUri.getScheme(), workPathUri.getUserInfo(), workPathUri.getHost(),
workPathUri.getPort(),
workDirectoryPath.endsWith("/") ? workDirectoryPath.concat(METADATA_FILE_NAME)
: workDirectoryPath + '/' + METADATA_FILE_NAME,
workPathUri.getQuery(), null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
/**
* Get process data directory.
* Don't save it to the database, if it is for indexingAll.
*
* @param process
* object
* @param forIndexingAll
* if the dataDirectory is created for indexingAll
* @return path
*/
public URI getProcessDataDirectory(Process process, boolean forIndexingAll) {
if (Objects.isNull(process.getProcessBaseUri())) {
process.setProcessBaseUri(fileService.getProcessBaseUriForExistingProcess(process));
if (!forIndexingAll) {
try {
saveToDatabase(process);
} catch (DAOException e) {
logger.error(e.getMessage(), e);
return URI.create("");
}
}
}
return process.getProcessBaseUri();
}
/**
* Get process data directory.
* Don't save it to the database, if it is for indexingAll.
*
* @param processDTO
* processDTO to get the dataDirectory from
* @return path
*/
public String getProcessDataDirectory(ProcessDTO processDTO) {
if (Objects.isNull(processDTO.getProcessBaseUri())) {
processDTO.setProcessBaseUri(fileService.getProcessBaseUriForExistingProcess(processDTO));
}
return processDTO.getProcessBaseUri();
}
/**
* Get process data directory.
*
* @param process
* object
* @return path
*/
public URI getProcessDataDirectory(Process process) {
return getProcessDataDirectory(process, false);
}
/**
* Returns a URI that identifies the process. The URI has the form
* {@code mysql://?process.id=42}, where {@code 42} is the process ID.
*
* @param process
* process for which a URI is to be formed that identifies it
* @return a URI that identifies the process
*/
public URI getProcessURI(Process process) {
return getProcessURI(process.getId());
}
/**
* Returns a URI that identifies the process. The URI has the form
* {@code database://?process.id=42}, where {@code 42} is the process ID.
*
* @param processId
* process ID for which a URI is to be formed that identifies it
* @return a URI that identifies the process
*/
public URI getProcessURI(Integer processId) {
try {
return new URI("database", null, "//", "process.id=" + processId, null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
/**
* 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().isEmpty()) {
return null;
}
StringBuilder result = new StringBuilder();
for (BatchDTO batch : process.getBatches()) {
if (result.length() > 0) {
result.append(", ");
}
result.append(ServiceManager.getBatchService().getLabel(batch));
}
return result.toString();
}
/**
* Get current task.
*
* @param process
* object
* @return current task
*/
public Task getCurrentTask(Process process) {
for (Task task : process.getTasks()) {
if (task.getProcessingStatus().equals(TaskStatus.OPEN)
|| task.getProcessingStatus().equals(TaskStatus.INWORK)) {
return task;
}
}
return null;
}
/**
* Get current tasks.
*
* @param process
* object
* @return current tasks
*/
public List<Task> getCurrentTasks(Process process) {
List<Task> currentTasks = new ArrayList<>();
for (Task task : process.getTasks()) {
if (task.getProcessingStatus().equals(TaskStatus.OPEN)
|| task.getProcessingStatus().equals(TaskStatus.INWORK)) {
currentTasks.add(task);
}
}
return currentTasks;
}
private List<TaskDTO> getOpenTasks(ProcessDTO process) {
return process.getTasks().stream()
.filter(t -> TaskStatus.OPEN.equals(t.getProcessingStatus())).collect(Collectors.toList());
}
private List<TaskDTO> getTasksInWork(ProcessDTO process) {
return process.getTasks().stream()
.filter(t -> TaskStatus.INWORK.equals(t.getProcessingStatus())).collect(Collectors.toList());
}
/**
* Create and return String used as progress tooltip for a given process. Tooltip contains OPEN tasks and tasks
* INWORK.
*
* @param processDTO
* process for which the tooltop is created
* @return String containing the progress tooltip for the given process
*/
public String createProgressTooltip(ProcessDTO processDTO) {
String openTasks = getOpenTasks(processDTO).stream()
.map(t -> " - " + Helper.getTranslation(t.getTitle())).collect(Collectors.joining(NEW_LINE_ENTITY));
if (!openTasks.isEmpty()) {
openTasks = Helper.getTranslation(TaskStatus.OPEN.getTitle()) + ":" + NEW_LINE_ENTITY + openTasks;
}
String tasksInWork = getTasksInWork(processDTO).stream()
.map(t -> " - " + Helper.getTranslation(t.getTitle())).collect(Collectors.joining(NEW_LINE_ENTITY));
if (!tasksInWork.isEmpty()) {
tasksInWork = Helper.getTranslation(TaskStatus.INWORK.getTitle()) + ":" + NEW_LINE_ENTITY + tasksInWork;
}
if (openTasks.isEmpty() && tasksInWork.isEmpty()) {
return "";
} else if (openTasks.isEmpty()) {
return tasksInWork;
} else if (tasksInWork.isEmpty()) {
return openTasks;
} else {
return openTasks + NEW_LINE_ENTITY + tasksInWork;
}
}
/**
* Get current task.
*
* @param processDTO
* DTOobject
* @return current task
*/
public TaskDTO getCurrentTaskDTO(ProcessDTO processDTO) {
for (TaskDTO task : processDTO.getTasks()) {
if (task.getProcessingStatus().equals(TaskStatus.OPEN)
|| task.getProcessingStatus().equals(TaskStatus.INWORK)) {
return task;
}
}
return null;
}
/**
* Read metadata file.
*
* @param process
* object
* @return filer format
*/
public LegacyMetsModsDigitalDocumentHelper readMetadataFile(Process process) throws IOException {
URI metadataFileUri = ServiceManager.getFileService().getMetadataFilePath(process);
// check the format of the metadata - METS, XStream or RDF
String type = MetadataHelper.getMetaFileType(metadataFileUri);
logger.debug("current meta.xml file type for id {}: {}", process.getId(), type);
LegacyMetsModsDigitalDocumentHelper ff = determineFileFormat(type, process);
try {
ff.read(ServiceManager.getFileService().getFile(metadataFileUri).toString());
} catch (IOException e) {
if (e.getMessage().startsWith("Parse error at line -1")) {
Helper.setErrorMessage("metadataCorrupt", logger, e);
} else {
throw e;
}
}
return ff;
}
/**
* Reads the metadata File.
*
* @param metadataFile
* The given metadataFile.
* @param prefs
* The Preferences
* @return The fileFormat.
*/
public LegacyMetsModsDigitalDocumentHelper readMetadataFile(URI metadataFile, LegacyPrefsHelper prefs)
throws IOException {
String type = MetadataHelper.getMetaFileType(metadataFile);
LegacyMetsModsDigitalDocumentHelper fileFormat = determineFileFormat(type, prefs);
fileFormat.read(ConfigCore.getKitodoDataDirectory() + metadataFile.getPath());
return fileFormat;
}
private LegacyMetsModsDigitalDocumentHelper determineFileFormat(String type, Process process) {
RulesetService rulesetService = ServiceManager.getRulesetService();
return determineFileFormat(type, rulesetService.getPreferences(process.getRuleset()));
}
private LegacyMetsModsDigitalDocumentHelper determineFileFormat(String type, LegacyPrefsHelper prefs) {
LegacyMetsModsDigitalDocumentHelper fileFormat;
if ("metsmods".equals(type) || "mets".equals(type)) {
fileFormat = new LegacyMetsModsDigitalDocumentHelper(prefs.getRuleset());
} else {
throw new UnsupportedOperationException("Dead code pending removal");
}
return fileFormat;
}
/**
* Read metadata as template file.
*
* @param process
* object
* @return file format
*/
public LegacyMetsModsDigitalDocumentHelper readMetadataAsTemplateFile(Process process) throws IOException {
URI processSubTypeURI = fileService.getProcessSubTypeURI(process, ProcessSubType.TEMPLATE, null);
if (fileService.fileExist(processSubTypeURI)) {
String type = MetadataHelper.getMetaFileType(processSubTypeURI);
logger.debug("current template.xml file type: {}", type);
LegacyMetsModsDigitalDocumentHelper ff = determineFileFormat(type, process);
String processSubTypePath = fileService.getFile(processSubTypeURI).getAbsolutePath();
ff.read(processSubTypePath);
return ff;
} else {
throw new IOException("File does not exist: " + processSubTypeURI);
}
}
/**
* 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.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.getProcessingStatus() == TaskStatus.INWORK && task.isTypeImagesWrite()) {
return task.getProcessingUser();
}
}
return null;
}
/**
* Download docket for given process.
*
* @param process
* object
* @throws IOException
* when xslt file could not be loaded, or write to output failed
*/
public void downloadDocket(Process process) throws IOException {
logger.debug("generate docket for process with id {}", process.getId());
URI rootPath = Paths.get(ConfigCore.getParameter(ParameterCore.DIR_XSLT)).toUri();
URI xsltFile;
if (Objects.nonNull(process.getDocket())) {
xsltFile = ServiceManager.getFileService().createResource(rootPath, process.getDocket().getFile());
if (!fileService.fileExist(xsltFile)) {
Helper.setErrorMessage("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, Helper.getNormalizedTitle(process.getTitle()) + ".pdf");
Files.deleteIfExists(file.toPath());
}
}
/**
* Writes a multi page docket for a list of processes to an output stream.
*
* @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 {
logger.debug("generate docket for processes {}", processes);
URI rootPath = Paths.get(ConfigCore.getParameter(ParameterCore.DIR_XSLT)).toUri();
URI xsltFile = ServiceManager.getFileService().createResource(rootPath, "docket_multipage.xsl");
FacesContext facesContext = FacesContext.getCurrentInstance();
if (!facesContext.getResponseComplete()) {
DocketInterface module = initialiseDocketModule();
File file = module.generateMultipleDockets(getDocketData(processes),
xsltFile);
writeToOutputStream(facesContext, file, "batch_docket.pdf");
Files.deleteIfExists(file.toPath());
}
}
/**
* Generate result as PDF.
*
* @param filter
* for generating search results
*/
public void generateResultAsPdf(String filter, boolean showClosedProcesses, boolean showInactiveProjects)
throws DocumentException, IOException {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (!facesContext.getResponseComplete()) {
ExternalContext response = prepareHeaderInformation(facesContext, "search.pdf");
try (OutputStream out = response.getResponseOutputStream()) {
SearchResultGeneration sr = new SearchResultGeneration(filter, showClosedProcesses,
showInactiveProjects);
HSSFWorkbook wb = sr.getResult();
List<List<HSSFCell>> rowList = new ArrayList<>();
HSSFSheet mySheet = wb.getSheetAt(0);
Iterator<Row> rowIter = mySheet.rowIterator();
while (rowIter.hasNext()) {
HSSFRow myRow = (HSSFRow) rowIter.next();
Iterator<Cell> cellIter = myRow.cellIterator();
List<HSSFCell> row = new ArrayList<>();
while (cellIter.hasNext()) {
HSSFCell myCell = (HSSFCell) cellIter.next();
row.add(myCell);
}
rowList.add(row);
}
Document document = new Document();
Rectangle rectangle = new Rectangle(PageSize.A3.getHeight(), PageSize.A3.getWidth());
PdfWriter.getInstance(document, out);
document.setPageSize(rectangle);
document.open();
if (!rowList.isEmpty()) {
Paragraph paragraph = new Paragraph(rowList.get(0).get(0).toString());
document.add(paragraph);
document.add(getPdfTable(rowList));
}
document.close();
out.flush();
facesContext.responseComplete();
}
}
}
/**
* Generate result set.
*
* @param filter
* for generating search results
*/
public void generateResult(String filter, boolean showClosedProcesses, boolean showInactiveProjects)
throws IOException {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (!facesContext.getResponseComplete()) {
ExternalContext response = prepareHeaderInformation(facesContext, "search.xls");
try (OutputStream out = response.getResponseOutputStream()) {
SearchResultGeneration sr = new SearchResultGeneration(filter, showClosedProcesses,
showInactiveProjects);
HSSFWorkbook wb = sr.getResult();
wb.write(out);
out.flush();
facesContext.responseComplete();
}
}
}
/**
* Good explanation how it should be implemented:
* https://stackoverflow.com/a/9394237/2701807.
*
* @param facesContext
* context
* @param file
* temporal file which contains content to save
* @param fileName
* name of the new docket file
*/
private void writeToOutputStream(FacesContext facesContext, File file, String fileName) throws IOException {
ExternalContext externalContext = prepareHeaderInformation(facesContext, fileName);
try (OutputStream outputStream = externalContext.getResponseOutputStream();
FileInputStream fileInputStream = new FileInputStream(file)) {
byte[] bytes = IOUtils.toByteArray(fileInputStream);
outputStream.write(bytes);
outputStream.flush();
}
facesContext.responseComplete();
}
private ExternalContext prepareHeaderInformation(FacesContext facesContext, String outputFileName) {
ExternalContext externalContext = facesContext.getExternalContext();
externalContext.responseReset();
String contentType = externalContext.getMimeType(outputFileName);
externalContext.setResponseContentType(contentType);
externalContext.setResponseHeader("Content-Disposition", "attachment;filename=\"" + outputFileName + "\"");
return externalContext;
}
private PdfPTable getPdfTable(List<List<HSSFCell>> rowList) throws DocumentException {
// create formatter for cells with default locale
DataFormatter formatter = new DataFormatter();
PdfPTable table = new PdfPTable(8);
table.setSpacingBefore(20);
table.setWidths(new int[] {4, 1, 2, 1, 1, 1, 2, 2 });
for (List<HSSFCell> row : rowList) {
for (HSSFCell hssfCell : row) {
String stringCellValue = formatter.formatCellValue(hssfCell);
table.addCell(stringCellValue);
}
}
return table;
}
private static DocketInterface initialiseDocketModule() {
KitodoServiceLoader<DocketInterface> loader = new KitodoServiceLoader<>(DocketInterface.class);
return loader.loadModule();
}
/**
* Returns the digital act of this process.
*
* @return the digital act of this process
* @throws IOException
* if creating the process directory or reading the meta data file
* fails
*/
public LegacyMetsModsDigitalDocumentHelper getDigitalDocument(Process process) throws IOException {
return readMetadataFile(process).getDigitalDocument();
}
/**
* Returns the type of the top element of the logical structure, and thus the
* type of the workpiece of the process.
*
* @param process
* process whose root type is to be determined
* @return the type of the logical structure of the workpiece, "" if unreadable
*/
public String getBaseType(Process process) {
try {
URI metadataFilePath = ServiceManager.getFileService().getMetadataFilePath(process);
return ServiceManager.getMetsService().getBaseType(metadataFilePath);
} catch (IOException | IllegalArgumentException e) {
logger.info("Could not determine base type for process {}: {}", process, e.getMessage());
return "";
}
}
/**
* Returns the type of the top element of the logical structure, and thus the
* type of the workpiece of the process.
*
* @param workpiece
* workpiece whose root type is to be determined
* @return the type of the logical structure of the workpiece, "" if unreadable
*/
public String getBaseType(Workpiece workpiece) {
try {
return ServiceManager.getMetsService().getBaseType(workpiece);
} catch (IllegalArgumentException e) {
logger.info("Could not determine base type for process {}: {}", workpiece.getId(), e.getMessage());
return "";
}
}
/**
* Returns the type of the top element of the logical structure, and thus the
* type of the workpiece of the process.
*
* @param processId
* id of the process whose root type is to be determined
* @return the type of root element of the logical structure of the workpiece
* @throws DataException
* if the type cannot be found in the index (e.g. because the process
* cannot be found in the index)
*/
public String getBaseType(int processId) throws DataException {
ProcessDTO processDTO = findById(processId, true);
if (Objects.nonNull(processDTO)) {
return processDTO.getBaseType();
}
return "";
}
/**
* Filter for correction / solution messages.
*
* @param lpe
* List of process properties
* @return List of filtered correction / solution messages
*/
private List<PropertyDTO> filterForCorrectionSolutionMessages(List<PropertyDTO> lpe) {
List<PropertyDTO> filteredList = new ArrayList<>();
if (lpe.isEmpty()) {
return filteredList;
}
List<String> translationList = Arrays.asList("Correction required", "Correction performed",
"Korrektur notwendig", "Korrektur durchgef\u00FChrt");
// filtering for correction and solution messages
for (PropertyDTO property : lpe) {
if (translationList.contains(property.getTitle())) {
filteredList.add(property);
}
}
return filteredList;
}
/**
* Find amount of processes for given title.
*
* @param title
* as String
* @return amount as Long
*/
public Long findNumberOfProcessesWithTitle(String title) throws DataException {
return count(createSimpleQuery(ProcessTypeField.TITLE.getKey(), title, true, Operator.AND));
}
/**
* Sanitizes a possibly dirty reference URI and extracts from it the
* operation ID of the referenced process. In case of error, a speaking
* exception is thrown.
*
* @param uri
* URI of the reference to the process number
* @return process number
* @throws SecurityException
* if the URI names a forbidden protocol or column
* @throws IllegalArgumentException
* if the URI tries to reference a foreign database or if the
* query has a wrong format
* @throws ClassCastException
* if the URI references a wrong class / table
*/
public int processIdFromUri(URI uri) {
if (!"database".equals(uri.getScheme())) {
throw new SecurityException("Protocol not allowed: " + uri.getScheme());
}
if (Objects.nonNull(uri.getAuthority()) || Objects.nonNull(uri.getPath()) && !uri.getPath().isEmpty()) {
throw new IllegalArgumentException("Linking across databases is not supported");
}
if (Objects.isNull(uri.getQuery())) {
throw new IllegalArgumentException("No query in database request");
}
String[] queryArguments = uri.getQuery().split("&");
String[] key = queryArguments[0].split("=", 2);
String[] keySegments = key[0].split("\\.");
if (queryArguments.length > 1 || keySegments.length > 2) {
throw new IllegalArgumentException("Complex queries is not supported");
}
if (keySegments.length > 1 && !"id".equals(keySegments[1])) {
throw new SecurityException("Filtering on '" + keySegments[1] + "' is not allowed");
}
if (!"process".equals(keySegments[0])) {
throw new ClassCastException("'" + keySegments[0] + "' cannot be cast to 'process'");
}
return Integer.parseInt(key[1]);
}
/**
* Method for avoiding redundant code for exception handling. //TODO: should
* this exceptions be handled that way?
*
* @param newFile
* as Fileformat
* @param process
* as Process object
* @return false if no exception appeared
*/
public boolean handleExceptionsForConfiguration(LegacyMetsModsDigitalDocumentHelper newFile, Process process) {
Optional<String> rules = ConfigCore.getOptionalString(ParameterCore.COPY_DATA_ON_EXPORT);
if (rules.isPresent()) {
try {
new DataCopier(rules.get()).process(new CopierData(newFile, process));
} catch (ConfigurationException e) {
Helper.setErrorMessage("dataCopier.syntaxError", logger, e);
return true;
}
}
return false;
}
/**
* Run through all metadata and children of given docStruct to trim the strings
* calls itself recursively.
*
* @param docStruct
* metadata to be trimmed
*/
private void trimAllMetadata(LegacyDocStructHelperInterface docStruct) {
// trim all metadata values
for (LegacyMetadataHelper md : docStruct.getAllMetadata()) {
if (Objects.nonNull(md.getValue())) {
md.setStringValue(md.getValue().trim());
}
}
// run through all children of docStruct
for (LegacyDocStructHelperInterface child : docStruct.getAllChildren()) {
trimAllMetadata(child);
}
}
/**
* Download full text.
*
* @param userHome
* safe file
* @param atsPpnBand
* String
*/
private void downloadFullText(Process process, URI userHome, String atsPpnBand) throws IOException {
downloadSources(process, userHome, atsPpnBand);
downloadOCR(process, userHome, atsPpnBand);
}
private void downloadSources(Process process, URI userHome, String atsPpnBand) throws IOException {
URI source = fileService.getSourceDirectory(process);
if (fileService.fileExist(source) && !fileService.getSubUris(source).isEmpty()) {
URI destination = userHome.resolve(File.separator + atsPpnBand + "_src");
if (!fileService.fileExist(destination)) {
fileService.createDirectory(userHome, atsPpnBand + "_src");
}
copyProcessFiles(source, destination, null);
}
}
private void downloadOCR(Process process, URI userHome, String atsPpnBand) throws IOException {
URI ocr = fileService.getOcrDirectory(process);
if (fileService.fileExist(ocr)) {
List<URI> directories = fileService.getSubUris(ocr);
for (URI directory : directories) {
if (fileService.isDirectory(directory) && !fileService.getSubUris(directory).isEmpty()
&& fileService.getFileName(directory).contains("_")) {
String suffix = fileService.getFileNameWithExtension(directory)
.substring(fileService.getFileNameWithExtension(directory).lastIndexOf('_'));
URI destination = userHome.resolve(File.separator + atsPpnBand + suffix);
if (!fileService.fileExist(destination)) {
fileService.createDirectory(userHome, atsPpnBand + suffix);
}
copyProcessFiles(directory, destination, null);
}
}
}
}
/**
* Download images.
*
* @param process
* process object
* @param userHome
* save file
* @param atsPpnBand
* String
* @param directorySuffix
* String
*/
public void downloadImages(Process process, URI userHome, String atsPpnBand, final String directorySuffix)
throws IOException {
Project project = process.getProject();
// determine the output path
URI tifDirectory = getImagesTifDirectory(true, process.getId(), process.getTitle(),
process.getProcessBaseUri());
// copy the source folder to the destination folder
if (fileService.fileExist(tifDirectory) && !fileService.getSubUris(tifDirectory).isEmpty()) {
URI destination = userHome.resolve(File.separator + atsPpnBand + directorySuffix);
// with Agora import simply create the folder
if (!fileService.fileExist(destination)) {
fileService.createDirectory(userHome, atsPpnBand + directorySuffix);
}
copyProcessFiles(tifDirectory, destination, ImageHelper.dataFilter);
}
}
private void copyProcessFiles(URI source, URI destination, FilenameFilter filter) throws IOException {
List<URI> files = fileService.getSubUris(filter, source);
for (URI file : files) {
if (fileService.isFile(file)) {
URI target = destination.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, LegacyMetsModsDigitalDocumentHelper gdzfile)
throws IOException {
LegacyPrefsHelper preferences = ServiceManager.getRulesetService().getPreferences(process.getRuleset());
LegacyMetsModsDigitalDocumentHelper mm = new LegacyMetsModsDigitalDocumentHelper(preferences.getRuleset());
// before creating mets file, change relative path to absolute -
LegacyMetsModsDigitalDocumentHelper dd = gdzfile.getDigitalDocument();
if (Objects.isNull(dd.getFileSet())) {
Helper.setErrorMessage(process.getTitle() + ": digital document does not contain images; aborting");
return false;
}
//get the topstruct element of the digital document depending on anchor property
LegacyDocStructHelperInterface topElement = dd.getLogicalDocStruct();
//if the top element does not have any image related, set them all
if (topElement.getAllToReferences("logical_physical").isEmpty()) {
if (Objects.nonNull(dd.getPhysicalDocStruct()) && dd.getPhysicalDocStruct().getAllChildren().isEmpty()) {
Helper.setMessage(process.getTitle()
+ ": topstruct element does not have any referenced images yet; temporarily adding them "
+ "for mets file creation");
for (LegacyDocStructHelperInterface mySeitenDocStruct : dd.getPhysicalDocStruct().getAllChildren()) {
topElement.addReferenceTo(mySeitenDocStruct, "logical_physical");
}
} else {
Helper.setErrorMessage(process.getTitle() + ": could not find any referenced images, export aborted");
return false;
}
}
mm.setDigitalDocument(dd);
mm.write(targetFileName);
Helper.setMessage(process.getTitle() + ": ", "exportFinished");
return true;
}
/**
* Get data files.
*
* @return String
*/
public List<URI> getDataFiles(Process process) throws InvalidImagesException {
URI dir;
try {
dir = getImagesTifDirectory(true, process.getId(), process.getTitle(), process.getProcessBaseUri());
} catch (RuntimeException e) {
throw new InvalidImagesException(e);
}
List<URI> dataList = new ArrayList<>();
List<URI> files = fileService.getSubUris(ImageHelper.dataFilter, dir);
if (!files.isEmpty()) {
dataList.addAll(files);
Collections.sort(dataList);
}
return dataList;
}
/**
* 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(ParameterCore.PROCESS_DIRS);
String normalizedTitle = Helper.getNormalizedTitle(myProcess.getTitle());
for (String processDir : processDirs) {
URI sourceDirectory = URI.create(getProcessDataDirectory(myProcess).toString() + "/"
+ processDir.replace(PROCESS_TITLE, normalizedTitle));
URI destinationDirectory = URI
.create(targetDirectory.toString() + "/" + processDir.replace(PROCESS_TITLE, normalizedTitle));
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 objects
*/
private List<DocketData> getDocketData(List<Process> processes) throws IOException {
List<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 static DocketData getDocketData(Process process) throws IOException {
DocketData docketdata = new DocketData();
docketdata.setCreationDate(process.getCreationDate().toString());
URI metadataFilePath = fileService.getMetadataFilePath(process);
docketdata.setMetadataFile(fileService.getFile(metadataFilePath).toURI());
if (Objects.nonNull(process.getParent())) {
docketdata.setParent(getDocketData(process.getParent()));
}
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()) {
docketdata.setTemplateProperties(getDocketDataForProperties(process.getTemplates()));
}
if (!process.getWorkpieces().isEmpty()) {
docketdata.setWorkpieceProperties(getDocketDataForProperties(process.getWorkpieces()));
}
docketdata.setProcessProperties(getDocketDataForProperties(process.getProperties()));
return docketdata;
}
private static 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;
}
private List<Map<String, Object>> getMetadataForIndex(Process process) {
return getMetadataForIndex(process, false);
}
@SuppressWarnings("unchecked")
private List<Map<String, Object>> getMetadataForIndex(Process process, boolean forIndexingAll) {
try {
URI metadataFileUri = ServiceManager.getFileService().getMetadataFilePath(process, false, true);
if (!ServiceManager.getFileService().fileExist(metadataFileUri)) {
logger.info("No metadata file for indexing: {}", metadataFileUri);
return Collections.emptyList();
}
String metadataFile;
try (InputStream inputStream = ServiceManager.getFileService().readMetadataFile(process, forIndexingAll)) {
metadataFile = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
JSONObject xmlJSONObject = XML.toJSONObject(metadataFile);
Map<String, Object> json = iterateOverJsonObject(xmlJSONObject);
if (json.containsKey("mets")) {
Map<String, Object> mets = (Map<String, Object>) json.get("mets");
Object dmdSec = mets.get("dmdSec");
List<Map<String, Object>> metadata = new ArrayList<>();
if (dmdSec instanceof List) {
metadata = (List<Map<String, Object>>) dmdSec;
} else if (dmdSec instanceof Map) {
metadata.add((Map<String, Object>) dmdSec);
}
return metadata;
}
} catch (NullPointerException | IOException e) {
logger.warn(e.getMessage(), e);
}
return Collections.emptyList();
}
private Map<String, Object> iterateOverJsonObject(JSONObject xmlJSONObject) {
Iterator<String> keys = xmlJSONObject.keys();
Map<String, Object> json = new HashMap<>();
while (keys.hasNext()) {
String key = keys.next();
Object value = xmlJSONObject.get(key);
if (value instanceof String || value instanceof Integer) {
json.put(prepareKey(key), value);
} else if (value instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) value;
Map<String, Object> map = iterateOverJsonObject(jsonObject);
json.put(prepareKey(key), map);
} else if (value instanceof JSONArray) {
json.put(prepareKey(key), iterateOverJsonArray((JSONArray) value));
}
}
return json;
}
private Object iterateOverJsonArray(JSONArray jsonArray) {
int jsonArraySize = jsonArray.length();
List<Object> json = new ArrayList<>(jsonArraySize);
for (int i = 0; i < jsonArraySize; i++) {
Object value = jsonArray.get(i);
if (value instanceof JSONObject) {
json.add(iterateOverJsonObject((JSONObject) value));
} else if (value instanceof String) {
json.add(value);
} else if (value instanceof JSONArray) {
json.add(iterateOverJsonArray((JSONArray) value));
}
}
return json;
}
private String prepareKey(String key) {
if (key.contains(":")) {
return key.substring(key.indexOf(':') + 1);
}
return key;
}
/**
* Retrieve and return process property value of property with given name
* 'propertyName' from given ProcessDTO 'process'.
*
* @param process
* the ProcessDTO object from which the property value is retrieved
* @param propertyName
* name of the property for the property value is retrieved
* @return property value if process has property with name 'propertyName',
* empty String otherwise
*/
public static String getPropertyValue(ProcessDTO process, String propertyName) {
for (PropertyDTO property : process.getProperties()) {
if (property.getTitle().equals(propertyName)) {
return property.getValue();
}
}
return "";
}
/**
* Calculate and return duration/age of given process as a String.
*
* @param process
* ProcessDTO object for which duration/age is calculated
* @return process age of given process
*/
public static String getProcessDuration(ProcessDTO process) {
String creationDateTimeString = process.getCreationDate();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime createLocalDate = LocalDateTime.parse(creationDateTimeString, formatter);
Duration duration = Duration.between(createLocalDate, LocalDateTime.now());
return String.format("%sd; %sh", duration.toDays(),
duration.toHours() - TimeUnit.DAYS.toHours(duration.toDays()));
}
/**
* Updates the linked child processes to the level specified in the root
* element. Processes linked in the logical structure are linked in the database.
* For processes that are not linked in the logical structure, the link in the
* database is removed.
*
* @param process
* parent process
* @param logicalStructure
* the current state of the logical structure
* @throws DAOException
* if a process is referenced with a URI whose ID does not
* appear in the database
* @throws DataException
* if the process cannot be saved
*/
public void updateChildrenFromLogicalStructure(Process process, LogicalDivision logicalStructure)
throws DAOException, DataException {
removeLinksFromNoLongerLinkedProcesses(process, logicalStructure);
addNewLinks(process, logicalStructure);
}
private void removeLinksFromNoLongerLinkedProcesses(Process process, LogicalDivision logicalStructure)
throws DAOException, DataException {
ArrayList<Process> childrenToRemove = new ArrayList<>(process.getChildren());
childrenToRemove.removeAll(getProcessesLinkedInLogicalDivision(logicalStructure));
for (Process childToRemove : childrenToRemove) {
childToRemove.setParent(null);
process.getChildren().remove(childToRemove);
save(childToRemove);
}
if (!childrenToRemove.isEmpty()) {
save(process);
}
}
private void addNewLinks(Process process, LogicalDivision logicalStructure)
throws DAOException, DataException {
HashSet<Process> childrenToAdd = getProcessesLinkedInLogicalDivision(logicalStructure);
process.getChildren().forEach(childrenToAdd::remove);
for (Process childToAdd : childrenToAdd) {
childToAdd.setParent(process);
process.getChildren().add(childToAdd);
save(childToAdd);
}
if (!childrenToAdd.isEmpty()) {
save(process);
}
}
private HashSet<Process> getProcessesLinkedInLogicalDivision(
LogicalDivision logicalDivision) throws DAOException {
HashSet<Process> processesLinkedInLogicalDivision = new HashSet<>();
if (Objects.nonNull(logicalDivision.getLink())) {
int processId = processIdFromUri(logicalDivision.getLink().getUri());
processesLinkedInLogicalDivision.add(getById(processId));
}
for (LogicalDivision child : logicalDivision.getChildren()) {
processesLinkedInLogicalDivision.addAll(getProcessesLinkedInLogicalDivision(child));
}
return processesLinkedInLogicalDivision;
}
/**
* Set given Process "parentProcess" as parent of given Process "childProcess" and Process "childProcess" as child
* of given Process "parentProcess".
* @param parentProcess
* parentProcess of given childProcess
* @param childProcess
* childProcess of given parentProcess
*/
public static void setParentRelations(Process parentProcess, Process childProcess) {
childProcess.setParent(parentProcess);
parentProcess.getChildren().add(childProcess);
}
/**
* Get all parent processes of given Process recursively.
* @param process the Process to get the parent process for
* @return List of parent Processes
*/
public static List<Process> getAllParentProcesses(Process process) {
List<Process> parents = new ArrayList<>();
while (Objects.nonNull(process.getParent())) {
parents.add(0, process.getParent());
process = process.getParent();
}
return parents;
}
/**
* Get the number of direct children of the given process.
* @param processId id of the process
* @return number of direct children as int
* @throws DAOException when query to database fails
*/
public int getNumberOfChildren(int processId) throws DAOException {
return Math.toIntExact(countDatabaseRows("SELECT COUNT(*) FROM Process WHERE parent_id = " + processId));
}
public static void deleteProcess(int processID) throws DAOException, DataException, IOException {
Process process = ServiceManager.getProcessService().getById(processID);
deleteProcess(process);
}
/**
* Delete given process.
*
* @param processToDelete process to delete
*/
public static void deleteProcess(Process processToDelete) throws DataException, IOException {
deleteMetadataDirectory(processToDelete);
processToDelete.getProject().getProcesses().remove(processToDelete);
processToDelete.setProject(null);
processToDelete.getTemplate().getProcesses().remove(processToDelete);
processToDelete.setTemplate(null);
Process parent = processToDelete.getParent();
if (Objects.nonNull(parent)) {
parent.getChildren().remove(processToDelete);
processToDelete.setParent(null);
MetadataEditor.removeLink(parent, processToDelete.getId());
ServiceManager.getProcessService().save(processToDelete);
ServiceManager.getProcessService().save(parent);
}
List<Batch> batches = new CopyOnWriteArrayList<>(processToDelete.getBatches());
for (Batch batch : batches) {
batch.getProcesses().remove(processToDelete);
processToDelete.getBatches().remove(batch);
ServiceManager.getBatchService().save(batch);
}
ServiceManager.getProcessService().remove(processToDelete);
}
private static void deleteMetadataDirectory(Process process) {
for (Task task : process.getTasks()) {
deleteSymlinksFromUserHomes(task);
}
try {
FileService fileService = ServiceManager.getFileService();
fileService.delete(ServiceManager.getProcessService().getProcessDataDirectory(process));
URI ocrDirectory = fileService.getOcrDirectory(process);
if (fileService.fileExist(ocrDirectory)) {
fileService.delete(ocrDirectory);
}
} catch (IOException | RuntimeException e) {
Helper.setErrorMessage("errorDirectoryDeleting", new Object[] {Helper.getTranslation("metadata") }, logger,
e);
}
}
/**
* Delete symlinks from user home directories.
*
* @param task Task for which symlinks are removed
*/
public static void deleteSymlinksFromUserHomes(Task task) {
if (Objects.nonNull(task.getProcessingUser()) && (task.isTypeImagesRead() || task.isTypeImagesWrite())) {
WebDav webDav = new WebDav();
try {
webDav.uploadFromHome(task.getProcessingUser(), task.getProcess());
} catch (RuntimeException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
}
/**
* Get the node list from metadata file by the xpath.
*
* @param process
* The process for which the metadata file is searched for
* @param xpath
* The xpath to get to the node list
* @return The node list of process by the of xpath
*/
public NodeList getNodeListFromMetadataFile(Process process, String xpath) throws IOException {
try (InputStream fileInputStream = ServiceManager.getFileService().readMetadataFile(process)) {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
org.w3c.dom.Document xmlDocument = builder.parse(fileInputStream);
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new KitodoNamespaceContext());
return (NodeList) xPath.compile(xpath).evaluate(xmlDocument, XPathConstants.NODESET);
} catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
logger.error(e.getMessage(), e);
throw new IOException(e);
}
}
/**
* Export Mets.
*
* @param processId
* Id of which process should be exported
* @throws DAOException
* Thrown on database error
* @throws DataException
* Thrown on index error
* @throws IOException
* Thrown on I/O error
*/
public static void exportMets(int processId) throws DAOException, DataException, IOException {
Process process = ServiceManager.getProcessService().getById(processId);
ExportMets export = new ExportMets();
export.startExport(process);
}
/**
* Link a list of given processes to user home directory.
*
* @param processes List of processes
* @throws DAOException Thrown on database like error
*/
public static void downloadToHome(List<Process> processes) throws DAOException {
WebDav webDav = new WebDav();
for (Process processForDownload : processes) {
downloadToHome(webDav, processForDownload.getId());
}
}
/**
* Download to home for single process. First check if this volume is currently
* being edited by another user and placed in his home directory, otherwise
* download.
*
* @param webDav
* for download
* @param processId
* ID of process for which download is going to be performed
*/
public static void downloadToHome(WebDav webDav, int processId) throws DAOException {
Process process = ServiceManager.getProcessService().getById(processId);
if (ServiceManager.getProcessService().isImageFolderInUse(process)) {
Helper.setMessage(
Helper.getTranslation("directory ") + " " + process.getTitle() + " "
+ Helper.getTranslation("isInUse"),
ServiceManager.getUserService()
.getFullName(ServiceManager.getProcessService().getImageFolderInUseUser(process)));
webDav.downloadToHome(process, true);
} else {
webDav.downloadToHome(process, false);
}
}
/**
* Upload from home for list of processes for current user.
* Deletes symlinks in home directory of current user.
*
* @param processes the list of processes
*/
public static void uploadFromHome(List<Process> processes) {
WebDav webDav = new WebDav();
User currentUser = ServiceManager.getUserService().getCurrentUser();
for (Process process : processes) {
try {
webDav.uploadFromHome(currentUser, process);
} catch (RuntimeException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
}
/**
* Check and return whether the process with the ID 'processId' has any correction comments or not.
*
* @param processID
* ID of process to check
* @return CorrectionComment status of process
*/
public static CorrectionComments hasCorrectionComment(int processID) throws DAOException {
Process process = ServiceManager.getProcessService().getById(processID);
List<Comment> correctionComments = ServiceManager.getCommentService().getAllCommentsByProcess(process)
.stream().filter(c -> CommentType.ERROR.equals(c.getType())).collect(Collectors.toList());
if (correctionComments.size() < 1) {
return NO_CORRECTION_COMMENTS;
} else if (correctionComments.stream().anyMatch(c -> !c.isCorrected())) {
return OPEN_CORRECTION_COMMENTS;
} else {
return NO_OPEN_CORRECTION_COMMENTS;
}
}
/**
* Retrieve comments for the given process.
*
* @param processDTO
* process for which the tooltip is created
* @return List containing comments of given process
*
* @throws DAOException thrown when process cannot be loaded from database
*/
public List<Comment> getComments(ProcessDTO processDTO) throws DAOException {
Process process = ServiceManager.getProcessService().getById(processDTO.getId());
return ServiceManager.getCommentService().getAllCommentsByProcess(process);
}
/**
* Check and return if child process for given ProcessDTO processDTO can be created via calendar or not.
*
* @param processDTO ProcessDTO for which child processes may be created via calendar
* @return whether child processes for the given ProcessDTO can be created via the calendar or not
* @throws DAOException if process could not be loaded from database
* @throws IOException if ruleset file could not be read
*/
public static boolean canCreateProcessWithCalendar(ProcessDTO processDTO)
throws DAOException, IOException {
Collection<String> functionalDivisions;
if (Objects.isNull(processDTO.getRuleset())) {
return false;
}
Integer rulesetId = processDTO.getRuleset().getId();
if (RULESET_CACHE_FOR_CREATE_FROM_CALENDAR.containsKey(rulesetId)) {
functionalDivisions = RULESET_CACHE_FOR_CREATE_FROM_CALENDAR.get(rulesetId);
} else {
Ruleset ruleset = ServiceManager.getRulesetService().getById(rulesetId);
functionalDivisions = ServiceManager.getRulesetService().openRuleset(ruleset)
.getFunctionalDivisions(FunctionalDivision.CREATE_CHILDREN_WITH_CALENDAR);
RULESET_CACHE_FOR_CREATE_FROM_CALENDAR.put(rulesetId, functionalDivisions);
}
return functionalDivisions.contains(processDTO.getBaseType());
}
/**
* Check and return if child process for given ProcessDTO processDTO can be created or not.
*
* @param processDTO ProcessDTO for which child processes may be created
* @return whether child processes for the given ProcessDTO can be created via the calendar or not
* @throws DAOException if process could not be loaded from database
* @throws IOException if ruleset file could not be read
*/
public static boolean canCreateChildProcess(ProcessDTO processDTO) throws DAOException,
IOException {
Collection<String> functionalDivisions;
if (Objects.isNull(processDTO.getRuleset())) {
return false;
}
Integer rulesetId = processDTO.getRuleset().getId();
if (RULESET_CACHE_FOR_CREATE_CHILD_FROM_PARENT.containsKey(rulesetId)) {
functionalDivisions = RULESET_CACHE_FOR_CREATE_CHILD_FROM_PARENT.get(rulesetId);
} else {
Ruleset ruleset = ServiceManager.getRulesetService().getById(rulesetId);
functionalDivisions = ServiceManager.getRulesetService().openRuleset(ruleset)
.getFunctionalDivisions(FunctionalDivision.CREATE_CHILDREN_FROM_PARENT);
RULESET_CACHE_FOR_CREATE_CHILD_FROM_PARENT.put(rulesetId, functionalDivisions);
}
return functionalDivisions.contains(processDTO.getBaseType());
}
/**
* Starts generation of xml logfile for current process.
*/
public static void createXML(Process process, User user) throws IOException {
DocketInterface xmlExport = initialiseDocketModule();
String directory = new File(ServiceManager.getUserService().getHomeDirectory(user)).getPath();
String destination = directory + "/" + Helper.getNormalizedTitle(process.getTitle()) + "_log.xml";
xmlExport.exportXmlLog(getDocketData(process), destination);
}
/**
* Create and return PieChartModel for given process values.
*
* @param processValues Map containing process values
* @return PieChartModel
*/
public PieChartModel getPieChardModel(Map<String, Integer> processValues) {
PieChartDataSet dataSet = new PieChartDataSet();
List<Number> values = new ArrayList<>(processValues.values());
dataSet.setData(values);
dataSet.setBackgroundColor(BG_COLORS);
ChartData data = new ChartData();
data.addChartDataSet(dataSet);
ArrayList<String> labels = new ArrayList<>();
for (Map.Entry<String, Integer> processValueEntry : processValues.entrySet()) {
labels.add(processValueEntry.getKey().concat(" ").concat(processValueEntry.getValue().toString()));
}
data.setLabels(labels);
PieChartModel pieModel = new PieChartModel();
pieModel.setData(data);
return pieModel;
}
/**
* Create and return HorizontalBarChartModel for given processes.
*
* @param processes List of processes
* @return HorizontalBarChartModel
*/
public HorizontalBarChartModel getBarChartModel(List<Process> processes) {
LinkedHashMap<String, LinkedHashMap<String,Integer>> durationOfTasks = new LinkedHashMap<>();
for (Process selectedProcess : processes) {
LinkedHashMap<String,Integer> taskValues = new LinkedHashMap<>();
for (Task task : selectedProcess.getTasks()) {
long durationInDays = ServiceManager.getTaskService().getDurationInDays(task);
taskValues.put(task.getTitle(), Math.toIntExact(durationInDays));
}
durationOfTasks.put(selectedProcess.getTitle(), taskValues);
}
ChartData data = new ChartData();
boolean isTask;
int i = 0;
while (true) {
isTask = false;
HorizontalBarChartDataSet barDataSet = new HorizontalBarChartDataSet();
List<Number> taskDurations = new ArrayList<>();
for (String processTitle : durationOfTasks.keySet()) {
LinkedHashMap<String, Integer> tasksForProcess = durationOfTasks.get(processTitle);
ArrayList<Integer> durations = new ArrayList<>(tasksForProcess.values());
Integer taskDuration = 0;
if (durations.size() > i) {
barDataSet.setLabel(new ArrayList<>(tasksForProcess.keySet()).get(i));
taskDuration = durations.get(i);
isTask = true;
}
taskDurations.add(taskDuration);
}
if (isTask) {
barDataSet.setStack("Stack 0");
barDataSet.setBackgroundColor(BG_COLORS.get(i % BG_COLORS.size()));
barDataSet.setData(taskDurations);
data.addChartDataSet(barDataSet);
i++;
} else {
break;
}
}
List<String> labels = new ArrayList<>(durationOfTasks.keySet());
data.setLabels(labels);
HorizontalBarChartModel horizontalBarChartModel = new HorizontalBarChartModel();
horizontalBarChartModel.setData(data);
horizontalBarChartModel.setOptions(getBarChartOptions());
return horizontalBarChartModel;
}
private BarChartOptions getBarChartOptions() {
CartesianLinearAxes linearAxes = new CartesianLinearAxes();
linearAxes.setStacked(true);
BarChartOptions options = new BarChartOptions();
Tooltip tooltip = new Tooltip();
tooltip.setMode("index");
tooltip.setIntersect(false);
options.setTooltip(tooltip);
return options;
}
/**
* Aggregate and return statistical data about task status of given processes.
*
* @param processes List of processes for which statistical data is aggregated.
* @return statistical data about tasks status of given processes
*/
public Map<String, Integer> getProcessTaskStates(List<Process> processes) {
Map<String, Integer> processTaskStates = new LinkedHashMap<>();
for (Process process : processes) {
Task currentTask = ServiceManager.getProcessService().getCurrentTask(process);
if (Objects.nonNull(currentTask)) {
String currentTaskTitle = currentTask.getTitle();
if (processTaskStates.containsKey(currentTaskTitle)) {
processTaskStates.put(currentTaskTitle, Math.addExact(processTaskStates.get(currentTaskTitle), 1));
} else {
processTaskStates.put(currentTaskTitle, 1);
}
}
}
return processTaskStates;
}
/**
* Get all tasks of given process which should be visible to the user.
* @param processDTO process as DTO object
* @param user user to filter the tasks for
* @return List of filtered tasks as DTO objects
*/
public List<TaskDTO> getCurrentTasksForUser(ProcessDTO processDTO, User user) {
Set<Integer> userRoles = user.getRoles().stream()
.map(Role::getId)
.collect(Collectors.toSet());
return processDTO.getTasks().stream()
.filter(task -> TaskStatus.OPEN.equals(task.getProcessingStatus()) || TaskStatus.INWORK.equals(task.getProcessingStatus()))
.filter(task -> !task.getRoleIds().stream()
.filter(userRoles::contains)
.collect(Collectors.toSet()).isEmpty())
.collect(Collectors.toList());
}
/**
* Checks and returns whether the process with the given ID 'processId' can be exported or not.
* @param processId process ID
* @return whether process can be exported or not
*/
public static boolean canBeExported(int processId) throws IOException, DAOException {
Process process = ServiceManager.getProcessService().getById(processId);
// superordinate processes normally do not contain images but should always be exportable
if (!process.getChildren().isEmpty()) {
return true;
}
Folder generatorSource = process.getProject().getGeneratorSource();
// processes without a generator source should be exportable because they may contain multimedia files
// that are not used as generator sources
if (Objects.isNull(generatorSource)) {
return true;
}
return FileService.hasImages(process, generatorSource);
}
/**
* Get template processes sorted by title.
* @return template processes sorted by title
*/
public List<Process> getTemplateProcesses() throws DataException, DAOException {
List<Process> templateProcesses = new ArrayList<>();
BoolQueryBuilder inChoiceListShownQuery = new BoolQueryBuilder();
MatchQueryBuilder matchQuery = matchQuery(ProcessTypeField.IN_CHOICE_LIST_SHOWN.getKey(), true);
inChoiceListShownQuery.must(matchQuery);
for (ProcessDTO processDTO : ServiceManager.getProcessService().findByQuery(matchQuery, true)) {
templateProcesses.add(getById(processDTO.getId()));
}
templateProcesses.sort(Comparator.comparing(Process::getTitle));
return templateProcesses;
}
/**
* Sort results by id.
*
* @param order
* ASC or DESC as SortOrder
* @return sort as String
*/
public SortBuilder sortById(SortOrder order) {
return SortBuilders.fieldSort(ProcessTypeField.ID.getKey()).order(order);
}
}