Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process parent link #5869

Merged
merged 17 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ public Authority getByTitle(String title) throws DAOException {
if (!authorities.isEmpty()) {
return authorities.get(0);
}
throw new DAOException("Object cannot be found in database");
throw new DAOException(String.format("Authority '%s' cannot be found in database", title));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--
-- (c) Kitodo. Key to digital objects e. V. <[email protected]>
--
-- 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.
--

SET SQL_SAFE_UPDATES = 0;

-- add authorities/permission for linking to parent processes of unassigned projects
INSERT IGNORE INTO authority (title) VALUES ('linkToProcessesOfUnassignedProjects_clientAssignable');

SET SQL_SAFE_UPDATES = 1;
Original file line number Diff line number Diff line change
Expand Up @@ -1108,4 +1108,13 @@ public boolean hasAuthorityToRenameMediaFiles() {
public boolean hasAuthorityToAssignImportConfigurationToClient() {
return securityAccessService.hasAuthorityToAssignImportConfigurationToClient();
}

/**
* Check if the current user has the permission to link processes to parent processes of unassigned projects.
*
* @return true if the current user has the permission to link processes to parent processes of unassigned projects.
*/
public boolean hasAuthorityToLinkToProcessesOfUnassignedProjects() {
return securityAccessService.hasAuthorityToLinkToProcessesOfUnassignedProjects();
}
}
21 changes: 21 additions & 0 deletions Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import java.text.MessageFormat;
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 javax.annotation.PostConstruct;
Expand Down Expand Up @@ -60,6 +62,7 @@
import org.kitodo.production.process.ProcessValidator;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.command.KitodoScriptService;
import org.kitodo.production.services.data.ImportService;
import org.kitodo.production.services.data.ProcessService;
import org.kitodo.production.services.file.FileService;
import org.kitodo.production.services.workflow.WorkflowControllerService;
Expand Down Expand Up @@ -96,6 +99,7 @@ public class ProcessForm extends TemplateBaseForm {
private static final String CREATE_PROCESS_PATH = "/pages/processFromTemplate.jsf?faces-redirect=true";
private static final String PROCESS_TABLE_VIEW_ID = "/pages/processes.xhtml";
private static final String PROCESS_TABLE_ID = "processesTabView:processesForm:processesTable";
private final Map<Integer, Boolean> assignedProcesses = new HashMap<>();
solth marked this conversation as resolved.
Show resolved Hide resolved

@Inject
private CustomListColumnInitializer initializer;
Expand Down Expand Up @@ -1169,4 +1173,21 @@ public String getMediaRenamingConfirmMessage() {
public String getErrorMessage() {
return errorMessage;
}

/**
* Check and return whether process with ID 'processId' belongs to a project assigned to the current user or not.
* @param processId ID of process to check
* @return whether process belongs to project assigned to current user or not
*/
public boolean processInAssignedProject(int processId) {
try {
if (!assignedProcesses.containsKey(processId)) {
assignedProcesses.put(processId, ImportService.processInAssignedProject(processId));
}
return assignedProcesses.get(processId);
} catch (DAOException e) {
Helper.setErrorMessage(e);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Locale.LanguageRange;
import java.util.Objects;
import java.util.Optional;

import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import org.apache.commons.lang3.StringUtils;
Expand All @@ -40,6 +42,7 @@
import org.kitodo.production.helper.Helper;
import org.kitodo.production.metadata.MetadataEditor;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.data.ImportService;
import org.kitodo.production.services.data.ProcessService;
import org.kitodo.production.services.dataformat.MetsService;
import org.omnifaces.util.Ajax;
Expand All @@ -53,6 +56,7 @@
private static final Logger logger = LogManager.getLogger(TitleRecordLinkTab.class);

static final String INSERTION_TREE = "editForm:processFromTemplateTabView:insertionTree";
static final String PARENT_PROCESS_SELECTION = "editForm:processFromTemplateTabView:chooseParentGroup";
private static final MetsService metsService = ServiceManager.getMetsService();
private static final ProcessService processService = ServiceManager.getProcessService();

Expand Down Expand Up @@ -160,12 +164,12 @@
priorityList);
logicalStructure.setExpanded(true);

if (!selectableInsertionPositions.isEmpty()) {
selectedInsertionPosition = (String) ((LinkedList<SelectItem>) selectableInsertionPositions).getLast()
.getValue();
} else {
if (selectableInsertionPositions.isEmpty()) {
selectedInsertionPosition = null;
Helper.setErrorMessage("createProcessForm.titleRecordLinkTab.noInsertionPosition");
} else {
selectedInsertionPosition = (String) ((LinkedList<SelectItem>) selectableInsertionPositions).getLast()

Check warning

Code scanning / CodeQL

Cast from abstract to concrete collection Warning

List
is cast to the concrete type
LinkedList
, losing abstraction.
.getValue();
}
}

Expand Down Expand Up @@ -308,21 +312,31 @@
}
try {
List<ProcessDTO> processes = ServiceManager.getProcessService().findLinkableParentProcesses(searchQuery,
createProcessForm.getProject().getId(), createProcessForm.getTemplate().getRuleset().getId());
createProcessForm.getTemplate().getRuleset().getId());
if (processes.isEmpty()) {
Helper.setMessage("createProcessForm.titleRecordLinkTab.searchButtonClick.noHits");
}
indicationOfMoreHitsVisible = processes.size() > MAXIMUM_NUMBER_OF_HITS;
possibleParentProcesses = new ArrayList<>();
for (ProcessDTO process : processes.subList(0, Math.min(processes.size(), MAXIMUM_NUMBER_OF_HITS))) {
possibleParentProcesses.add(new SelectItem(process.getId().toString(), process.getTitle()));
}
} catch (DataException e) {
possibleParentProcesses = ServiceManager.getImportService()
.getPotentialParentProcesses(processes, MAXIMUM_NUMBER_OF_HITS);
} catch (DataException | DAOException | IOException e) {
Helper.setErrorMessage("createProcessForm.titleRecordLinkTab.searchButtonClick.error", e.getMessage(),
logger, e);
logger, e);
indicationOfMoreHitsVisible = false;
possibleParentProcesses = Collections.emptyList();
}
possibleParentProcesses.sort(Comparator.comparing(SelectItem::getLabel));
for (SelectItem selectItem : possibleParentProcesses) {
if (!selectItem.isDisabled()) {
try {
int processId = Integer.parseInt(selectItem.getValue().toString());
setParentAsTitleRecord(ServiceManager.getProcessService().getById(processId));
break;
} catch (DAOException | NumberFormatException e) {
logger.error(e);
}
}
}
}

/**
Expand Down Expand Up @@ -429,15 +443,25 @@

/**
* Set given process "parentProcess" as parent title record of new process.
*
* @param parentProcess process to set as parent title record
*/
public void setParentAsTitleRecord(Process parentProcess) {
createProcessForm.setEditActiveTabIndex(CreateProcessForm.TITLE_RECORD_LINK_TAB_INDEX);
ArrayList<SelectItem> parentCandidates = new ArrayList<>();
parentCandidates.add(new SelectItem(parentProcess.getId().toString(), parentProcess.getTitle()));
createProcessForm.getTitleRecordLinkTab().setPossibleParentProcesses(parentCandidates);
createProcessForm.getTitleRecordLinkTab().setChosenParentProcess((String)parentCandidates.get(0).getValue());
createProcessForm.getTitleRecordLinkTab().chooseParentProcess();
Ajax.update(INSERTION_TREE);
try {
if (ImportService.userMayLinkToParent(parentProcess.getId())) {
setChosenParentProcess(String.valueOf(parentProcess.getId()));
} else {
setChosenParentProcess(null);
}
} catch (DAOException e) {
Helper.setErrorMessage(e);
}
chooseParentProcess();
// only update UI components if FacesContext exists (not the case during integration tests, for example)
if (Objects.nonNull(FacesContext.getCurrentInstance())) {
Ajax.update(PARENT_PROCESS_SELECTION);
Ajax.update(INSERTION_TREE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
Expand All @@ -32,6 +33,7 @@
import java.util.Objects;
import java.util.stream.Collectors;

import javax.faces.model.SelectItem;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
Expand Down Expand Up @@ -956,14 +958,14 @@ private Process loadParentProcess(Ruleset ruleset, int projectId, String parentI
HashMap<String, String> parentIDMetadata = new HashMap<>();
parentIDMetadata.put(identifierMetadata, parentId);
try {
for (ProcessDTO processDTO : ServiceManager.getProcessService().findByMetadata(parentIDMetadata, true)) {
for (ProcessDTO processDTO : sortProcessesByProjectID(ServiceManager.getProcessService()
.findByMetadataInAllProjects(parentIDMetadata, true), projectId)) {
Process process = ServiceManager.getProcessService().getById(processDTO.getId());
if (Objects.isNull(process.getRuleset()) || Objects.isNull(process.getRuleset().getId())) {
throw new ProcessGenerationException("Ruleset or ruleset ID of potential parent process "
+ process.getId() + " is null!");
}
if (process.getProject().getId() == projectId
&& process.getRuleset().getId().equals(ruleset.getId())) {
if (process.getRuleset().getId().equals(ruleset.getId())) {
parentProcess = process;
break;
}
Expand All @@ -976,6 +978,25 @@ private Process loadParentProcess(Ruleset ruleset, int projectId, String parentI
return parentProcess;
}

/**
* Sorts a list of process DTOs based on a provided projectId.
* Processes which match the provided projectId should come first.
* @param processDTOs list of process DTOs
* @param projectId projectId by which the list gets sorted
*/
public List<ProcessDTO> sortProcessesByProjectID(List<ProcessDTO> processDTOs, int projectId) {
List<ProcessDTO> sortedList = new ArrayList<>(processDTOs);
Comparator<ProcessDTO> comparator = Comparator.comparingInt(obj -> {
if (obj.getProject().getId() == projectId) {
return 0; // Matching value should come first
} else {
return 1; // Non-matching value comes later
}
});
sortedList.sort(comparator);
return sortedList;
}

/**
* Check and return whether the "parentIdSearchField" is configured in the current ImportConfiguration.
*
Expand Down Expand Up @@ -1396,4 +1417,63 @@ public boolean isRecordIdentifierMetadataConfigured(RulesetManagementInterface r
public Collection<RecordIdentifierMissingDetail> getDetailsOfRecordIdentifierMissingError() {
return recordIdentifierMissingDetails;
}

/**
* Create and return list of "SelectItem" objects for given list of "ProcessDTO"s. For each "ProcessDTO" object
* check whether current user can link to it as a parent process.
* - If a specific process belongs to a project that is not assigned to the current user, add a hint to message to
* the corresponding "SelectItem"
* - If the user cannot link to a specific process, because the process belongs to a project which is not assigned
* to him, and he also lacks the special permission to link to processes in unassigned projects, the corresponding
* "SelectItem" is disabled.
* @param parentCandidates list of "ProcessDTO"s
* @param maxNumber limit
* @return list of "SelectItem" objects corresponding to given "ProcessDTO" objects.
* @throws DAOException when checking whether user can link to given "ProcessDTO"s fails
*/
public ArrayList<SelectItem> getPotentialParentProcesses(List<ProcessDTO> parentCandidates, int maxNumber)
throws DAOException {
ArrayList<SelectItem> possibleParentProcesses = new ArrayList<>();
for (ProcessDTO process : parentCandidates.subList(0, Math.min(parentCandidates.size(), maxNumber))) {
Copy link
Collaborator

@BartChris BartChris Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that if we limit to 10 possible results here, it might happen, that we might already filter out all possible parents (because they are not part of the first 10 hits) here and end up with an empty result list.The check for possible parents has to be applied on all search results unfortunately.

SelectItem selectItem = new SelectItem(process.getId().toString(), process.getTitle());
selectItem.setDisabled(!userMayLinkToParent(process.getId()));
if (!processInAssignedProject(process.getId())) {
String problem = Helper.getTranslation("projectNotAssignedToCurrentUser", process.getProject()
.getTitle());
selectItem.setDescription(problem);
selectItem.setLabel(selectItem.getLabel() + " (" + problem + ")");
}
possibleParentProcesses.add(selectItem);
}
return possibleParentProcesses;
}

/**
* Check and return whether the process with the provided ID "processId" belongs to a project that is assigned to
* the current user or not.
* @param processId ID of the process to check
* @return whether the process with the provided ID belongs to a project assigned to the current user or not
* @throws DAOException when retrieving the process with the ID "processId" from the database fails
*/
public static boolean processInAssignedProject(int processId) throws DAOException {
Process process = ServiceManager.getProcessService().getById(processId);
if (Objects.nonNull(process)) {
solth marked this conversation as resolved.
Show resolved Hide resolved
return ServiceManager.getUserService().getCurrentUser().getProjects().contains(process.getProject());
}
return false;
}

/**
* Check and return whether current user is allowed to link to process with provided ID "processId". For this
* method to return "true", one of the following two conditions has to be met:
* 1. the project of the process with the provided ID is assigned to the current user OR
* 2. the current user has the special permission to link to parent processes of unassigned projects
* @param processId the ID of the process to which a link is to be established
* @return whether the current user can link to the process with the provided ID or not
* @throws DAOException when checking whether the process with provided ID fails
*/
public static boolean userMayLinkToParent(int processId) throws DAOException {
return processInAssignedProject(processId)
|| ServiceManager.getSecurityAccessService().hasAuthorityToLinkToProcessesOfUnassignedProjects();
}
}
Loading
Loading