Coverage Summary for Class: TemplateService (org.kitodo.production.services.data)
Class |
Method, %
|
Line, %
|
TemplateService |
46,9%
(15/32)
|
56,6%
(81/143)
|
TemplateService$1 |
0%
(0/2)
|
0%
(0/2)
|
Total |
44,1%
(15/34)
|
55,9%
(81/145)
|
/*
* (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 java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.kitodo.config.ConfigCore;
import org.kitodo.data.database.beans.Project;
import org.kitodo.data.database.beans.Task;
import org.kitodo.data.database.beans.Template;
import org.kitodo.data.database.enums.IndexAction;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.database.persistence.TemplateDAO;
import org.kitodo.data.elasticsearch.exceptions.CustomResponseException;
import org.kitodo.data.elasticsearch.index.Indexer;
import org.kitodo.data.elasticsearch.index.type.TemplateType;
import org.kitodo.data.elasticsearch.index.type.enums.TemplateTypeField;
import org.kitodo.data.elasticsearch.search.Searcher;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.exceptions.ProcessGenerationException;
import org.kitodo.production.dto.TaskDTO;
import org.kitodo.production.dto.TemplateDTO;
import org.kitodo.production.dto.WorkflowDTO;
import org.kitodo.production.enums.ObjectType;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.data.base.ClientSearchService;
import org.primefaces.model.SortOrder;
public class TemplateService extends ClientSearchService<Template, TemplateDTO, TemplateDAO> {
private static final Logger logger = LogManager.getLogger(TemplateService.class);
private static volatile TemplateService instance = null;
private boolean showInactiveTemplates = false;
/**
* Constructor with Searcher and Indexer assigning.
*/
private TemplateService() {
super(new TemplateDAO(), new TemplateType(), new Indexer<>(Template.class), new Searcher(Template.class),
TemplateTypeField.CLIENT_ID.getKey());
}
/**
* Return singleton variable of type TemplateService.
*
* @return unique instance of TemplateService
*/
public static TemplateService getInstance() {
TemplateService localReference = instance;
if (Objects.isNull(localReference)) {
synchronized (TemplateService.class) {
localReference = instance;
if (Objects.isNull(localReference)) {
localReference = new TemplateService();
instance = localReference;
}
}
}
return localReference;
}
@Override
public Long countDatabaseRows() throws DAOException {
return countDatabaseRows("SELECT COUNT(*) FROM Template");
}
@Override
public Long countNotIndexedDatabaseRows() throws DAOException {
return countDatabaseRows("SELECT COUNT(*) FROM Template WHERE indexAction = 'INDEX' OR indexAction IS NULL");
}
@Override
public Long countResults(Map filters) throws DataException {
return countDocuments(createUserTemplatesQuery(filters));
}
@Override
public List<Template> getAllNotIndexed() {
return getByQuery("FROM Template WHERE indexAction = 'INDEX' OR indexAction IS NULL");
}
@Override
public List<Template> getAllForSelectedClient() {
return dao.getActiveTemplates(ServiceManager.getUserService().getSessionClientId());
}
@Override
public List<TemplateDTO> loadData(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters)
throws DataException {
return findByQuery(createUserTemplatesQuery(filters), getSortBuilder(sortField, sortOrder), first, pageSize,
false);
}
/**
* Method saves or removes tasks and project related to modified template.
*
* @param template
* object
*/
@Override
protected void manageDependenciesForIndex(Template template)
throws CustomResponseException, DAOException, DataException, IOException {
manageProjectDependenciesForIndex(template);
manageTaskDependenciesForIndex(template);
}
/**
* Find all templates available to assign to the edited project. It will be
* displayed in the templateAddPopup.
*
* @param projectId
* id of project which is going to be edited
* @return list of all matching templates
*/
public List<TemplateDTO> findAllAvailableForAssignToProject(Integer projectId) throws DataException {
return findAvailableForAssignToUser(projectId);
}
private List<TemplateDTO> findAvailableForAssignToUser(Integer projectId) throws DataException {
BoolQueryBuilder query = new BoolQueryBuilder();
if (Objects.nonNull(projectId)) {
query.must(createSimpleQuery(TemplateTypeField.PROJECTS + ".id", projectId, false));
}
query.must(getQueryForSelectedClient());
query.must(getQueryForActive(true));
return findByQuery(query, true);
}
/**
* Duplicate the template with the given ID 'itemId'.
*
* @return the duplicated Template
*/
public Template duplicateTemplate(Template baseTemplate) {
Template duplicatedTemplate = new Template();
duplicatedTemplate.setTitle(baseTemplate.getTitle() + "_" + Helper.generateRandomString(3));
duplicatedTemplate.setCreationDate(new Date());
duplicatedTemplate.setClient(baseTemplate.getClient());
duplicatedTemplate.setDocket(baseTemplate.getDocket());
duplicatedTemplate.setRuleset(baseTemplate.getRuleset());
// tasks don't need to be duplicated - will be created out of copied workflow
duplicatedTemplate.setWorkflow(baseTemplate.getWorkflow());
// TODO: make sure if copy should be assigned automatically to all projects
for (Project project : baseTemplate.getProjects()) {
duplicatedTemplate.getProjects().add(project);
project.getTemplates().add(duplicatedTemplate);
}
return duplicatedTemplate;
}
@Override
public TemplateDTO convertJSONObjectToDTO(Map<String, Object> jsonObject, boolean related) throws DataException {
TemplateDTO templateDTO = new TemplateDTO();
templateDTO.setId(getIdFromJSONObject(jsonObject));
templateDTO.setTitle(TemplateTypeField.TITLE.getStringValue(jsonObject));
templateDTO.setActive(TemplateTypeField.ACTIVE.getBooleanValue(jsonObject));
templateDTO.setCreationDate(TemplateTypeField.CREATION_DATE.getStringValue(jsonObject));
templateDTO.setDocket(
ServiceManager.getDocketService().findById(TemplateTypeField.DOCKET.getIntValue(jsonObject)));
templateDTO.setRuleset(
ServiceManager.getRulesetService().findById(TemplateTypeField.RULESET_ID.getIntValue(jsonObject)));
WorkflowDTO workflowDTO = new WorkflowDTO();
workflowDTO.setTitle(TemplateTypeField.WORKFLOW_TITLE.getStringValue(jsonObject));
templateDTO.setWorkflow(workflowDTO);
templateDTO.setTasks(convertRelatedJSONObjectToDTO(jsonObject, TemplateTypeField.TASKS.getKey(),
ServiceManager.getTaskService()));
templateDTO.setCanBeUsedForProcess(hasCompleteTasks(templateDTO.getTasks()));
if (!related) {
convertRelatedJSONObjects(jsonObject, templateDTO);
}
return templateDTO;
}
private void convertRelatedJSONObjects(Map<String, Object> jsonObject, TemplateDTO templateDTO)
throws DataException {
templateDTO.setProjects(convertRelatedJSONObjectToDTO(jsonObject, TemplateTypeField.PROJECTS.getKey(),
ServiceManager.getProjectService()));
}
/**
* Find templates by docket id.
*
* @param docketId
* id of docket for search
* @return list of JSON objects with templates for specific docket id
*/
public List<Map<String, Object>> findByDocket(int docketId) throws DataException {
QueryBuilder query = createSimpleQuery(TemplateTypeField.DOCKET.getKey(), docketId, true);
return findDocuments(query);
}
/**
* Find templates by ruleset id.
*
* @param rulesetId
* id of ruleset for search
* @return list of JSON objects with templates for specific ruleset id
*/
public List<Map<String, Object>> findByRuleset(int rulesetId) throws DataException {
QueryBuilder query = createSimpleQuery(TemplateTypeField.RULESET_ID.getKey(), rulesetId, true);
return findDocuments(query);
}
/**
* Get diagram image for current template.
*
* @return diagram image file
*/
public InputStream getTasksDiagram(String fileName) {
if (Objects.nonNull(fileName) && !fileName.isEmpty()) {
File tasksDiagram = new File(ConfigCore.getKitodoDiagramDirectory(), fileName + ".svg");
try {
return new FileInputStream(tasksDiagram);
} catch (FileNotFoundException e) {
logger.error(e.getMessage(), e);
return getEmptyInputStream();
}
}
return getEmptyInputStream();
}
private InputStream getEmptyInputStream() {
return new InputStream() {
@Override
public int read() {
return -1;
}
};
}
/**
* Check for unreachable tasks. Unreachable task is this one which has no roles
* assigned to itself. Other possibility is that given list is empty. It means
* that whole workflow is unreachable.
*
* @param tasks
* list of tasks for check
*/
public void checkForUnreachableTasks(List<Task> tasks) throws ProcessGenerationException {
if (tasks.isEmpty()) {
throw new ProcessGenerationException(Helper.getTranslation("noStepsInWorkflow"));
}
for (Task task : tasks) {
if (ServiceManager.getTaskService().getRolesSize(task) == 0) {
throw new ProcessGenerationException(
Helper.getTranslation("noUserInStep", task.getTitle()));
}
}
}
/**
* Check whether the tasks assigned to template are complete. If it contains
* tasks that are not assigned to a user or user group - tasks are not complete.
*
* @param tasks
* list of tasks for testing
* @return true or false
*/
boolean hasCompleteTasks(List<TaskDTO> tasks) {
if (tasks.isEmpty()) {
return false;
}
for (TaskDTO task : tasks) {
if (task.getRolesSize() == 0) {
return false;
}
}
return true;
}
/**
* Set show inactive templates.
*
* @param showInactiveTemplates
* as boolean
*/
public void setShowInactiveTemplates(boolean showInactiveTemplates) {
this.showInactiveTemplates = showInactiveTemplates;
}
/**
* Add process to project, if project is assigned to process.
*
* @param template
* object
*/
private void manageProjectDependenciesForIndex(Template template)
throws CustomResponseException, DataException, IOException {
for (Project project : template.getProjects()) {
if (template.getIndexAction().equals(IndexAction.DELETE)) {
project.getTemplates().remove(template);
ServiceManager.getProjectService().saveToIndex(project, false);
} else {
ServiceManager.getProjectService().saveToIndex(project, false);
}
}
}
/**
* Check IndexAction flag in for process object. If DELETE remove all tasks from
* index, if other call saveOrRemoveTaskInIndex() method.
*
* @param template
* object
*/
private void manageTaskDependenciesForIndex(Template template)
throws CustomResponseException, DAOException, IOException, DataException {
if (template.getIndexAction().equals(IndexAction.DELETE)) {
for (Task task : template.getTasks()) {
ServiceManager.getTaskService().removeFromIndex(task, false);
}
} else {
saveOrRemoveTasksInIndex(template);
}
}
/**
* Compare index and database, according to comparisons results save or remove
* tasks.
*
* @param template
* object
*/
private void saveOrRemoveTasksInIndex(Template template)
throws CustomResponseException, DAOException, IOException, DataException {
List<Integer> database = new ArrayList<>();
List<Integer> index = new ArrayList<>();
for (Task task : template.getTasks()) {
database.add(task.getId());
ServiceManager.getTaskService().saveToIndex(task, false);
}
List<Map<String, Object>> searchResults = ServiceManager.getTaskService().findByTemplateId(template.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;
}
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.TEMPLATE, false, false));
}
return query;
}
/**
* Creates and returns a query to retrieve templates for which the currently
* logged in user is eligible.
*
* @param filters
* map of applicable filters
* @return query to retrieve templates for which the user eligible
*/
@SuppressWarnings("unchecked")
private BoolQueryBuilder createUserTemplatesQuery(Map filters) throws DataException {
BoolQueryBuilder query = new BoolQueryBuilder();
if (Objects.nonNull(filters) && !filters.isEmpty()) {
Map<String, String> filterMap = filters;
query.must(readFilters(filterMap));
}
query.must(getQueryForSelectedClient());
if (!showInactiveTemplates) {
query.must(getQueryForActive(true));
}
return query;
}
/**
* Get query for projects assigned to selected client.
*
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForActive(boolean active) {
return createSimpleQuery(TemplateTypeField.ACTIVE.getKey(), active, true);
}
/**
* Get query for projects assigned to selected client.
*
* @return query as QueryBuilder
*/
private QueryBuilder getQueryForSelectedClient() {
return createSimpleQuery(TemplateTypeField.CLIENT_ID.getKey(),
ServiceManager.getUserService().getSessionClientId(), true);
}
/**
* Get all process templates for given title.
*
* @param title
* of Template
* @return list of all process templates as Template objects
*/
public List<Template> getProcessTemplatesWithTitle(String title) {
return dao.getTemplatesWithTitle(title);
}
/**
* Get all process templates for given title and client id.
*
* @param title
* of Template
* @param clientId
* id of client
* @return list of all process templates as Template objects
*/
public List<Template> getTemplatesWithTitleAndClient(String title, Integer clientId) {
String query = "SELECT t FROM Template AS t INNER JOIN t.client AS c WITH c.id = :clientId WHERE t.title = :title";
Map<String, Object> parameters = new HashMap<>();
parameters.put("clientId", clientId);
parameters.put("title", title);
return getByQuery(query, parameters);
}
}