Coverage Summary for Class: DataEditorForm (org.kitodo.production.forms.dataeditor)
Class |
Method, %
|
Line, %
|
DataEditorForm |
6,8%
(5/73)
|
6,7%
(23/343)
|
DataEditorForm$1 |
0%
(0/1)
|
0%
(0/1)
|
Total |
6,8%
(5/74)
|
6,7%
(23/344)
|
/*
* (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
*
* This file is part of the Kitodo project.
*
* It is licensed under GNU General Public License version 3 or later.
*
* For the full copyright and license information, please read the
* GPL3-License.txt file that was distributed with this source code.
*/
package org.kitodo.production.forms.dataeditor;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale.LanguageRange;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface;
import org.kitodo.api.dataformat.LogicalDivision;
import org.kitodo.api.dataformat.PhysicalDivision;
import org.kitodo.api.dataformat.View;
import org.kitodo.api.dataformat.Workpiece;
import org.kitodo.api.validation.State;
import org.kitodo.api.validation.ValidationResult;
import org.kitodo.config.ConfigCore;
import org.kitodo.data.database.beans.DataEditorSetting;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Project;
import org.kitodo.data.database.beans.User;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.exceptions.InvalidImagesException;
import org.kitodo.exceptions.InvalidMetadataValueException;
import org.kitodo.exceptions.MediaNotFoundException;
import org.kitodo.exceptions.NoSuchMetadataFieldException;
import org.kitodo.production.enums.ObjectType;
import org.kitodo.production.forms.createprocess.ProcessDetail;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.interfaces.MetadataTreeTableInterface;
import org.kitodo.production.interfaces.RulesetSetupInterface;
import org.kitodo.production.metadata.MetadataLock;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.dataeditor.DataEditorService;
import org.omnifaces.cdi.ViewScoped;
import org.primefaces.PrimeFaces;
import org.primefaces.model.TreeNode;
@Named("DataEditorForm")
@ViewScoped
public class DataEditorForm implements MetadataTreeTableInterface, RulesetSetupInterface, Serializable {
private static final Logger logger = LogManager.getLogger(DataEditorForm.class);
/**
* A filter on the rule set depending on the workflow step. So far this is
* not configurable anywhere and is therefore on “edit”.
*/
private final String acquisitionStage;
/**
* Backing bean for the add doc struc type dialog.
*/
private final AddDocStrucTypeDialog addDocStrucTypeDialog;
/**
* Dialog for adding metadata.
*/
private final AddMetadataDialog addMetadataDialog;
/**
* Backing bean for the add PhysicalDivision dialog.
*/
private final AddPhysicalDivisionDialog addPhysicalDivisionDialog;
/**
* Backing bean for the change doc struc type dialog.
*/
private final ChangeDocStrucTypeDialog changeDocStrucTypeDialog;
/**
* Backing bean for the edit pages dialog.
*/
private final EditPagesDialog editPagesDialog;
private final UploadFileDialog uploadFileDialog ;
/**
* Backing bean for the gallery panel.
*/
private final GalleryPanel galleryPanel;
/**
* The current process children.
*/
private final Set<Process> currentChildren = new HashSet<>();
/**
* The path to the main file, to save it later.
*/
private URI mainFileUri;
/**
* Backing bean for the metadata panel.
*/
private final MetadataPanel metadataPanel;
/**
* Backing bean for the pagination panel.
*/
private final PaginationPanel paginationPanel;
/**
* The language preference list of the editing user for displaying the
* metadata labels. We cache this because it’s used thousands of times and
* otherwise the access would always go through the search engine, which
* would delay page creation.
*/
private List<LanguageRange> priorityList;
/**
* Process whose workpiece is under edit.
*/
private Process process;
private String referringView = "desktop";
/**
* The ruleset that the file is based on.
*/
private RulesetManagementInterface ruleset;
/**
* Backing bean for the structure panel.
*/
private final StructurePanel structurePanel;
/**
* User sitting in front of the editor.
*/
private User user;
/**
* The file content.
*/
private Workpiece workpiece;
/**
* Original state of workpiece. Used to check whether any unsaved changes exist when leaving the editor.
*/
private Workpiece workpieceOriginalState;
/**
* This List of Pairs stores all selected physical elements and the logical elements in which the physical element was selected.
* It is necessary to store the logical elements as well, because a physical element can be assigned to multiple logical elements.
*/
private List<Pair<PhysicalDivision, LogicalDivision>> selectedMedia;
/**
* The id of the template's task corresponding to the current task that is under edit.
* This is used for saving and loading the metadata editor settings.
* The current task, the corresponding template task id and the settings are only available
* if the user opened the editor from a task.
*/
private int templateTaskId;
private DataEditorSetting dataEditorSetting;
private static final String DESKTOP_LINK = "/pages/desktop.jsf";
private List<PhysicalDivision> unsavedDeletedMedia = new ArrayList<>();
private List<PhysicalDivision> unsavedUploadedMedia = new ArrayList<>();
private boolean folderConfigurationComplete = false;
private int numberOfScans = 0;
private String errorMessage;
@Inject
private MediaProvider mediaProvider;
private boolean mediaUpdated = false;
/**
* Public constructor.
*/
public DataEditorForm() {
this.structurePanel = new StructurePanel(this);
this.metadataPanel = new MetadataPanel(this);
this.galleryPanel = new GalleryPanel(this);
this.paginationPanel = new PaginationPanel(this);
this.addDocStrucTypeDialog = new AddDocStrucTypeDialog(this);
this.addMetadataDialog = new AddMetadataDialog(this);
this.addPhysicalDivisionDialog = new AddPhysicalDivisionDialog(this);
this.changeDocStrucTypeDialog = new ChangeDocStrucTypeDialog(this);
this.editPagesDialog = new EditPagesDialog(this);
this.uploadFileDialog = new UploadFileDialog(this);
acquisitionStage = "edit";
}
/**
* Checks if the process is correctly set. Otherwise, redirect to desktop,
* because metadata editor doesn't work without a process.
*/
public void initMetadataEditor() {
if (Objects.isNull(process)) {
try {
Helper.setErrorMessage("noProcessSelected");
FacesContext context = FacesContext.getCurrentInstance();
String path = context.getExternalContext().getRequestContextPath() + DESKTOP_LINK;
context.getExternalContext().redirect(path);
} catch (IOException e) {
Helper.setErrorMessage("noProcessSelected");
}
} else {
if (mediaUpdated) {
PrimeFaces.current().executeScript("PF('fileReferencesUpdatedDialog').show();");
}
}
}
/**
* Open the metadata file of the process with the given ID in the metadata editor.
*
* @param processID
* ID of the process that is opened
* @param referringView
* JSF page the user came from
*/
public void open(String processID, String referringView, String taskId) {
try {
this.referringView = referringView;
this.process = ServiceManager.getProcessService().getById(Integer.parseInt(processID));
this.currentChildren.addAll(process.getChildren());
this.user = ServiceManager.getUserService().getCurrentUser();
this.checkProjectFolderConfiguration();
if (StringUtils.isNotBlank(taskId) && StringUtils.isNumeric(taskId)) {
this.templateTaskId = Integer.parseInt(taskId);
}
this.loadDataEditorSettings();
errorMessage = "";
User blockedUser = MetadataLock.getLockUser(process.getId());
if (Objects.nonNull(blockedUser) && !blockedUser.equals(this.user)) {
errorMessage = Helper.getTranslation("blocked");
}
String metadataLanguage = user.getMetadataLanguage();
priorityList = LanguageRange.parse(metadataLanguage.isEmpty() ? "en" : metadataLanguage);
ruleset = ServiceManager.getRulesetService().openRuleset(process.getRuleset());
try {
mediaUpdated = openMetsFile();
} catch (MediaNotFoundException e) {
mediaUpdated = false;
Helper.setWarnMessage(e.getMessage());
}
if (!workpiece.getId().equals(process.getId().toString())) {
errorMessage = Helper.getTranslation("metadataConfusion", String.valueOf(process.getId()),
workpiece.getId());
}
selectedMedia = new LinkedList<>();
unsavedUploadedMedia = new ArrayList<>();
init();
if (Objects.isNull(errorMessage) || errorMessage.isEmpty()) {
MetadataLock.setLocked(process.getId(), user);
} else {
PrimeFaces.current().executeScript("PF('metadataLockedDialog').show();");
}
} catch (IOException | DAOException | InvalidImagesException | NoSuchElementException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
}
private void checkProjectFolderConfiguration() {
if (Objects.nonNull(this.process)) {
Project project = this.process.getProject();
if (Objects.nonNull(project)) {
this.folderConfigurationComplete = Objects.nonNull(project.getGeneratorSource())
&& Objects.nonNull(project.getMediaView()) && Objects.nonNull(project.getPreview());
} else {
this.folderConfigurationComplete = false;
}
} else {
this.folderConfigurationComplete = false;
}
}
private void loadDataEditorSettings() {
if (templateTaskId > 0) {
dataEditorSetting = ServiceManager.getDataEditorSettingService().loadDataEditorSetting(user.getId(),
templateTaskId);
if (Objects.isNull(dataEditorSetting)) {
dataEditorSetting = new DataEditorSetting();
dataEditorSetting.setUserId(user.getId());
dataEditorSetting.setTaskId(templateTaskId);
}
} else {
dataEditorSetting = null;
}
}
/**
* Opens the METS file.
*
* @throws IOException
* if filesystem I/O fails
*/
private boolean openMetsFile() throws IOException, InvalidImagesException, MediaNotFoundException {
mainFileUri = ServiceManager.getProcessService().getMetadataFileUri(process);
workpiece = ServiceManager.getMetsService().loadWorkpiece(mainFileUri);
workpieceOriginalState = ServiceManager.getMetsService().loadWorkpiece(mainFileUri);
if (Objects.isNull(workpiece.getId())) {
logger.warn("Workpiece has no ID. Cannot verify workpiece ID. Setting workpiece ID.");
workpiece.setId(process.getId().toString());
}
setNumberOfScans(workpiece.getNumberOfAllPhysicalDivisionChildrenFilteredByTypes(PhysicalDivision.TYPES));
return ServiceManager.getFileService().searchForMedia(process, workpiece);
}
private void init() {
final long begin = System.nanoTime();
List<PhysicalDivision> severalAssignments = new LinkedList<>();
initSeveralAssignments(workpiece.getPhysicalStructure(), severalAssignments);
structurePanel.getSeveralAssignments().addAll(severalAssignments);
structurePanel.show();
structurePanel.getSelectedLogicalNode().setSelected(true);
structurePanel.getSelectedPhysicalNode().setSelected(true);
metadataPanel.showLogical(getSelectedStructure());
metadataPanel.showPhysical(getSelectedPhysicalDivision());
galleryPanel.setGalleryViewMode(GalleryViewMode.getByName(user.getDefaultGalleryViewMode()).name());
galleryPanel.show();
paginationPanel.show();
editPagesDialog.prepare();
if (logger.isTraceEnabled()) {
logger.trace("Initializing editor beans took {} ms",
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin));
}
}
/**
* Clears all remaining content from the data editor form.
*
* @return the referring view, to return there
*/
public String closeAndReturn() {
if (referringView.contains("?")) {
return referringView + "&faces-redirect=true";
} else {
return referringView + "?faces-redirect=true";
}
}
/**
* Close method called before destroying ViewScoped DataEditorForm bean instance. Cleans up various properties
* and releases metadata lock of current process if current user equals user of metadata lock.
*/
@PreDestroy
public void close() {
deleteNotSavedUploadedMedia();
unsavedDeletedMedia.clear();
metadataPanel.clear();
structurePanel.clear();
workpiece = null;
workpieceOriginalState = null;
mainFileUri = null;
ruleset = null;
currentChildren.clear();
selectedMedia.clear();
if (!FacesContext.getCurrentInstance().isPostback()) {
mediaProvider.resetMediaResolverForProcess(process.getId());
}
// do not unlock process if this locked process was opened by a different user opening editor
// directly via URL bookmark and 'preDestroy' method was being triggered redirecting him to desktop page
if (this.user.equals(MetadataLock.getLockUser(process.getId()))) {
MetadataLock.setFree(process.getId());
}
process = null;
user = null;
mediaUpdated = false;
}
private void deleteUnsavedDeletedMedia() {
URI uri = Paths.get(ConfigCore.getKitodoDataDirectory(),
ServiceManager.getProcessService().getProcessDataDirectory(this.process).getPath()).toUri();
for (PhysicalDivision physicalDivision : this.unsavedDeletedMedia) {
for (URI fileURI : physicalDivision.getMediaFiles().values()) {
try {
ServiceManager.getFileService().delete(uri.resolve(fileURI));
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
}
/**
* Get unsavedDeletedMedia.
*
* @return value of unsavedDeletedMedia
*/
public List<PhysicalDivision> getUnsavedDeletedMedia() {
return unsavedDeletedMedia;
}
private void deleteNotSavedUploadedMedia() {
URI uri = Paths.get(ConfigCore.getKitodoDataDirectory(),
ServiceManager.getProcessService().getProcessDataDirectory(this.process).getPath()).toUri();
for (PhysicalDivision mediaUnit : this.unsavedUploadedMedia) {
for (URI fileURI : mediaUnit.getMediaFiles().values()) {
try {
ServiceManager.getFileService().delete(uri.resolve(fileURI));
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
this.unsavedUploadedMedia.clear();
}
/**
* Validate the structure and metadata.
*
* @return whether the validation was successful or not
*/
public boolean validate() {
try {
ValidationResult validationResult = ServiceManager.getMetadataValidationService().validate(workpiece,
ruleset);
State state = validationResult.getState();
switch (state) {
case ERROR:
Helper.setErrorMessage(Helper.getTranslation("dataEditor.validation.state.error"));
for (String message : validationResult.getResultMessages()) {
Helper.setErrorMessage(message);
}
return false;
case WARNING:
Helper.setWarnMessage(Helper.getTranslation("dataEditor.validation.state.warning"));
for (String message : validationResult.getResultMessages()) {
Helper.setWarnMessage(message);
}
return true;
default:
Helper.setMessage(Helper.getTranslation("dataEditor.validation.state.success"));
for (String message : validationResult.getResultMessages()) {
Helper.setMessage(message);
}
return true;
}
} catch (DAOException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
return false;
}
}
/**
* Save the structure and metadata.
*/
public void save() {
save(false);
}
private String save(boolean close) {
try {
metadataPanel.preserve();
structurePanel.preserve();
ServiceManager.getProcessService().updateChildrenFromLogicalStructure(process, workpiece.getLogicalStructure());
ServiceManager.getFileService().createBackupFile(process);
try (OutputStream out = ServiceManager.getFileService().write(mainFileUri)) {
ServiceManager.getMetsService().save(workpiece, out);
ServiceManager.getProcessService().saveToIndex(process,false);
unsavedUploadedMedia.clear();
deleteUnsavedDeletedMedia();
if (close) {
return closeAndReturn();
} else {
PrimeFaces.current().executeScript("PF('notifications').renderMessage({'summary':'"
+ Helper.getTranslation("metadataSaved") + "','severity':'info'})");
workpieceOriginalState = ServiceManager.getMetsService().loadWorkpiece(mainFileUri);
PrimeFaces.current().executeScript("setUnsavedChanges(false);");
}
} catch (IOException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
} catch (Exception e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
}
PrimeFaces.current().executeScript("PF('sticky-notifications').removeAll();");
PrimeFaces.current().ajax().update("notifications");
return null;
}
/**
* Save the structure and metadata.
*
* @return navigation target
*/
public String saveAndExit() {
return save(true);
}
private void initSeveralAssignments(PhysicalDivision physicalDivision, List<PhysicalDivision> severalAssignments) {
if (physicalDivision.getLogicalDivisions().size() > 1) {
severalAssignments.add(physicalDivision);
}
for (PhysicalDivision child : physicalDivision.getChildren()) {
initSeveralAssignments(child, severalAssignments);
}
}
/**
* Deletes the selected outline point from the logical outline. This method
* is called by PrimeFaces to inform the application that the user has
* clicked on the shortcut menu entry to clear the outline point.
*/
public void deleteStructure() {
structurePanel.deleteSelectedStructure();
}
/**
* Deletes the selected physical division from the media list. The associated files
* on the drive are not deleted. The next time the editor is started, files
* that are not yet in the media list will be inserted there again. This
* method is called by PrimeFaces to inform the application that the user
* clicked on the context menu entry to delete the physical division.
*/
public void deletePhysicalDivision() {
structurePanel.deleteSelectedPhysicalDivision();
}
@Override
public String getAcquisitionStage() {
return acquisitionStage;
}
/**
* Get unsavedUploadedMedia.
*
* @return value of unsavedUploadedMedia
*/
public List<PhysicalDivision> getUnsavedUploadedMedia() {
return unsavedUploadedMedia;
}
/**
* Returns the backing bean for the add doc struc type dialog. This function
* is used by PrimeFaces to access the elements of the add doc struc type
* dialog.
*
* @return the backing bean for the add doc struc type dialog
*/
public AddDocStrucTypeDialog getAddDocStrucTypeDialog() {
return addDocStrucTypeDialog;
}
/**
* Get addMetadataDialog.
*
* @return value of addMetadataDialog
*/
public AddMetadataDialog getAddMetadataDialog() {
return addMetadataDialog;
}
/**
* Returns the backing bean for the add media dialog. This function is used
* by PrimeFaces to access the elements of the add media dialog.
*
* @return the backing bean for the add media dialog
*/
public AddPhysicalDivisionDialog getAddPhysicalDivisionDialog() {
return addPhysicalDivisionDialog;
}
/**
* Returns the backing bean for the change doc struc type dialog. This
* function is used by PrimeFaces to access the elements of the change doc
* struc type dialog.
*
* @return the backing bean for the change doc struc type dialog
*/
public ChangeDocStrucTypeDialog getChangeDocStrucTypeDialog() {
return changeDocStrucTypeDialog;
}
/**
* Returns the backing bean for the edit pages dialog. This function is used
* by PrimeFaces to access the elements of the edit pages dialog.
*
* @return the backing bean for the edit pages dialog
*/
public EditPagesDialog getEditPagesDialog() {
return editPagesDialog;
}
/**
* Returns the backing bean for the gallery panel. This function is used by
* PrimeFaces to access the elements of the gallery panel.
*
* @return the backing bean for the gallery panel
*/
public GalleryPanel getGalleryPanel() {
return galleryPanel;
}
Set<Process> getCurrentChildren() {
return currentChildren;
}
/**
* Returns the backing bean for the metadata panel. This function is used
* by PrimeFaces to access the elements of the metadata panel.
*
* @return the backing bean for the metadata panel
*/
public MetadataPanel getMetadataPanel() {
return metadataPanel;
}
/**
* Returns the backing bean for the pagination panel. This function is used
* by PrimeFaces to access the elements of the pagination panel.
*
* @return the backing bean for the pagination panel
*/
public PaginationPanel getPaginationPanel() {
return paginationPanel;
}
@Override
public List<LanguageRange> getPriorityList() {
return priorityList;
}
/**
* Get process.
*
* @return value of process
*/
public Process getProcess() {
return process;
}
/**
* Get process title.
*
* @return value of process title
*/
public String getProcessTitle() {
return process.getTitle();
}
@Override
public RulesetManagementInterface getRulesetManagement() {
return ruleset;
}
public Optional<LogicalDivision> getSelectedStructure() {
return structurePanel.getSelectedStructure();
}
Optional<PhysicalDivision> getSelectedPhysicalDivision() {
return structurePanel.getSelectedPhysicalDivision();
}
/**
* Check if the passed LogicalDivision is part of the selection.
* @param structure LogicalDivision to be checked
* @return boolean representing selection status
*/
public boolean isStripeSelected(LogicalDivision structure) {
Optional<LogicalDivision> selectedStructure = structurePanel.getSelectedStructure();
return selectedStructure.filter(logicalDivision -> Objects.equals(structure, logicalDivision)).isPresent();
}
/**
* Return structurePanel.
*
* @return structurePanel
*/
public StructurePanel getStructurePanel() {
return structurePanel;
}
Workpiece getWorkpiece() {
return workpiece;
}
void refreshStructurePanel() {
structurePanel.show(true);
galleryPanel.updateStripes();
}
void setProcess(Process process) {
this.process = process;
}
/**
* Get selectedMedia.
*
* @return value of selectedMedia
*/
public List<Pair<PhysicalDivision, LogicalDivision>> getSelectedMedia() {
return selectedMedia;
}
/**
* Checks and returns if consecutive physical divisions in one structure element are selected or not.
*
* <p>Note: This method is called potentially thousands of times when rendering large galleries.</p>
*/
public boolean consecutivePagesSelected() {
if (selectedMedia.isEmpty()) {
return false;
}
int maxOrder = selectedMedia.stream().mapToInt(m -> m.getLeft().getOrder()).max().orElseThrow(NoSuchElementException::new);
int minOrder = selectedMedia.stream().mapToInt(m -> m.getLeft().getOrder()).min().orElseThrow(NoSuchElementException::new);
// Check whether the set of selected media all belong to the same logical division, otherwise the selection
// is not consecutive. However, do not use stream().distinct(), which will do pairwise comparisons, which is
// slow for large amounts of selected images. Instead, just check whether the first logical division matches
// all others in a simple loop.
boolean theSameLogicalDivisions = true;
LogicalDivision firstSelectedMediaLogicalDivision = null;
for (Pair<PhysicalDivision, LogicalDivision> pair : selectedMedia) {
if (Objects.isNull(firstSelectedMediaLogicalDivision)) {
firstSelectedMediaLogicalDivision = pair.getRight();
} else {
if (!Objects.equals(firstSelectedMediaLogicalDivision, pair.getRight())) {
theSameLogicalDivisions = false;
break;
}
}
}
return selectedMedia.size() - 1 == maxOrder - minOrder && theSameLogicalDivisions;
}
void setSelectedMedia(List<Pair<PhysicalDivision, LogicalDivision>> media) {
this.selectedMedia = media;
}
/**
* Check if the passed PhysicalDivision is selected.
* @param physicalDivision PhysicalDivision object to check for selection
* @param logicalDivision object to check whether the PhysicalDivision is selected as a child of this LogicalDivision.
* A PhysicalDivision can be assigned to multiple logical divisionss but can be selected
* in one of these LogicalDivisions.
* @return boolean whether the PhysicalDivision is selected at the specified position
*/
public boolean isSelected(PhysicalDivision physicalDivision, LogicalDivision logicalDivision) {
if (Objects.nonNull(physicalDivision) && Objects.nonNull(logicalDivision)) {
return selectedMedia.contains(new ImmutablePair<>(physicalDivision, logicalDivision));
}
return false;
}
void switchStructure(Object treeNodeData, boolean updateGalleryAndPhysicalTree) throws NoSuchMetadataFieldException {
try {
metadataPanel.preserveLogical();
} catch (InvalidMetadataValueException e) {
logger.info(e.getLocalizedMessage(), e);
}
Optional<LogicalDivision> selectedStructure = structurePanel.getSelectedStructure();
metadataPanel.showLogical(selectedStructure);
if (treeNodeData instanceof StructureTreeNode) {
StructureTreeNode structureTreeNode = (StructureTreeNode) treeNodeData;
if (Objects.nonNull(structureTreeNode.getDataObject())) {
if (structureTreeNode.getDataObject() instanceof LogicalDivision
&& selectedStructure.isPresent()) {
// Logical structure element selected
if (structurePanel.isSeparateMedia()) {
LogicalDivision structuralElement = selectedStructure.get();
if (!structuralElement.getViews().isEmpty()) {
ArrayList<View> views = new ArrayList<>(structuralElement.getViews());
if (Objects.nonNull(views.get(0)) && updateGalleryAndPhysicalTree) {
updatePhysicalStructureTree(views.get(0));
updateGallery(views.get(0));
}
} else {
updatePhysicalStructureTree(null);
}
} else {
getSelectedMedia().clear();
}
} else if (structureTreeNode.getDataObject() instanceof View) {
// Page selected in logical tree
View view = (View) structureTreeNode.getDataObject();
metadataPanel.showPageInLogical(view.getPhysicalDivision());
if (updateGalleryAndPhysicalTree) {
updateGallery(view);
}
// no need to update physical tree because pages can only be clicked in logical tree if physical tree is hidden!
}
}
}
paginationPanel.preparePaginationSelectionSelectedItems();
}
void switchPhysicalDivision() throws NoSuchMetadataFieldException {
try {
metadataPanel.preservePhysical();
} catch (InvalidMetadataValueException e) {
logger.info(e.getLocalizedMessage(), e);
}
Optional<PhysicalDivision> selectedPhysicalDivision = structurePanel.getSelectedPhysicalDivision();
metadataPanel.showPhysical(selectedPhysicalDivision);
if (selectedPhysicalDivision.isPresent()) {
// update gallery
galleryPanel.updateSelection(selectedPhysicalDivision.get(), null);
// update logical tree
for (GalleryMediaContent galleryMediaContent : galleryPanel.getMedias()) {
if (Objects.nonNull(galleryMediaContent.getView())
&& Objects.equals(selectedPhysicalDivision.get(), galleryMediaContent.getView().getPhysicalDivision())) {
structurePanel.updateLogicalNodeSelection(galleryMediaContent, null);
break;
}
}
}
}
private void updatePhysicalStructureTree(View view) {
GalleryMediaContent galleryMediaContent = this.galleryPanel.getGalleryMediaContent(view);
structurePanel.updatePhysicalNodeSelection(galleryMediaContent);
}
private void updateGallery(View view) {
PhysicalDivision physicalDivision = view.getPhysicalDivision();
if (Objects.nonNull(physicalDivision)) {
galleryPanel.updateSelection(physicalDivision, structurePanel.getPageStructure(view, workpiece.getLogicalStructure()));
}
}
void assignView(LogicalDivision logicalDivision, View view, Integer index) {
if (Objects.nonNull(index) && index >= 0 && index < logicalDivision.getViews().size()) {
logicalDivision.getViews().add(index, view);
} else {
logicalDivision.getViews().add(view);
}
view.getPhysicalDivision().getLogicalDivisions().add(logicalDivision);
}
void unassignView(LogicalDivision logicalDivision, View view, boolean removeLast) {
// if View was moved within one element, we need to distinguish two possible directions it could have been moved
if (removeLast) {
logicalDivision.getViews().removeLastOccurrence(view);
} else {
logicalDivision.getViews().removeFirstOccurrence(view);
}
view.getPhysicalDivision().getLogicalDivisions().remove(logicalDivision);
}
/**
* Retrieve and return 'title' value of given Object 'dataObject' if Object is instance of
* 'LogicalDivision' and if it does have a title. Uses a configurable list of metadata keys to determine
* which metadata keys should be considered.
* Return empty string otherwise.
*
* @param dataObject
* StructureTreeNode containing the LogicalDivision whose title is returned
* @return 'title' value of the LogicalDivision contained in the given StructureTreeNode 'treeNode'
*/
public String getStructureElementTitle(Object dataObject) {
if (dataObject instanceof LogicalDivision) {
return DataEditorService.getTitleValue((LogicalDivision) dataObject, structurePanel.getTitleMetadata());
}
return "";
}
/**
* Get referringView.
*
* @return value of referringView
*/
public String getReferringView() {
return referringView;
}
/**
* Check and return whether the given ProcessDetail 'processDetail' is contained in the current list of addable
* metadata types in the addDocStrucTypeDialog.
*
* @param treeNode treeNode to be added
* @return whether the given ProcessDetail can be added or not
*/
public boolean canBeAdded(TreeNode treeNode) {
if (Objects.isNull(treeNode.getParent().getParent())) {
if (Objects.nonNull(metadataPanel.getSelectedMetadataTreeNode()) || Objects.isNull(addMetadataDialog.getAddableMetadata())) {
this.addMetadataDialog.prepareAddableMetadataForStructure(treeNode.getParent().getChildren());
}
} else if (!Objects.equals(metadataPanel.getSelectedMetadataTreeNode(), treeNode.getParent())
|| Objects.isNull(addMetadataDialog.getAddableMetadata())) {
prepareAddableMetadataForGroup(treeNode.getParent());
}
if (Objects.nonNull(addMetadataDialog.getAddableMetadata())) {
return addMetadataDialog.getAddableMetadata().stream()
.map(SelectItem::getValue).collect(Collectors.toList()).contains(((ProcessDetail) treeNode.getData()).getMetadataID());
}
return false;
}
@Override
public boolean canBeDeleted(ProcessDetail processDetail) {
return processDetail.getOccurrences() > 1 && processDetail.getOccurrences() > processDetail.getMinOccurs()
|| (!processDetail.isRequired() && !this.ruleset.isAlwaysShowingForKey(processDetail.getMetadataID()));
}
/**
* Check for changes in workpiece.
*/
public void checkForChanges() {
if (Objects.nonNull(PrimeFaces.current())) {
boolean unsavedChanges = !this.workpiece.equals(workpieceOriginalState);
PrimeFaces.current().executeScript("setUnsavedChanges(" + unsavedChanges + ");");
}
}
/**
* Get the shortcuts for the current user.
*
* @return shortcuts as java.lang.String
*/
public String getShortcuts() {
try {
return ServiceManager.getUserService().getShortcuts(user.getId());
} catch (DAOException e) {
Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
return "{}";
}
}
/**
* Get templateTaskId.
*
* @return value of templateTaskId
*/
public int getTemplateTaskId() {
return templateTaskId;
}
/**
* Get dataEditorSetting.
*
* @return value of dataEditorSetting
*/
public DataEditorSetting getDataEditorSetting() {
return dataEditorSetting;
}
/**
* Set dataEditorSetting.
*
* @param dataEditorSetting as org.kitodo.data.database.beans.DataEditorSetting
*/
public void setDataEditorSetting(DataEditorSetting dataEditorSetting) {
this.dataEditorSetting = dataEditorSetting;
}
/**
* Gets numberOfScans.
*
* @return value of numberOfScans
*/
public int getNumberOfScans() {
return numberOfScans;
}
/**
* Sets numberOfScans.
*
* @param numberOfScans value of numberOfScans
*/
public void setNumberOfScans(int numberOfScans) {
this.numberOfScans = numberOfScans;
}
/**
* Save current metadata editor layout.
*/
public void saveDataEditorSetting() {
if (Objects.nonNull(dataEditorSetting) && dataEditorSetting.getTaskId() > 0) {
try {
ServiceManager.getDataEditorSettingService().saveToDatabase(dataEditorSetting);
PrimeFaces.current().executeScript("PF('dataEditorSavingResultDialog').show();");
} catch (DAOException e) {
Helper.setErrorMessage("errorSaving", new Object[] {ObjectType.USER.getTranslationSingular() }, logger, e);
}
} else {
logger.error("Could not save DataEditorSettings with userId {} and templateTaskId {}", user.getId(),
templateTaskId);
}
}
/**
* Get uploadFileDialog.
*
* @return value of uploadFileDialog
*/
public UploadFileDialog getUploadFileDialog() {
return uploadFileDialog;
}
/**
* Get folderConfigurationComplete.
*
* @return value of folderConfigurationComplete
*/
public boolean isFolderConfigurationComplete() {
return folderConfigurationComplete;
}
/**
* Preserve changes in the metadata panel.
*/
public void preserveMetadataPanel() {
try {
metadataPanel.preserve();
} catch (InvalidMetadataValueException | NoSuchMetadataFieldException e) {
logger.info(e.getMessage());
}
}
/**
* Check and return whether given TreeNode contains ProcessFieldedMetadata and if any further metadata can
* be added to it or not.
*
* @return whether given TreeNode contains ProcessFieldedMetadata and if any further metadata can be added to it
*/
public boolean metadataAddableToGroup(TreeNode metadataNode) {
return metadataPanel.metadataAddableToGroup(metadataNode);
}
/**
* Prepare addable metadata for metadata group.
*/
public void prepareAddableMetadataForGroup(TreeNode treeNode) {
addMetadataDialog.prepareAddableMetadataForGroup(treeNode);
}
/**
* Get errorMessage.
*
* @return value of errorMessage
*/
public String getErrorMessage() {
return errorMessage;
}
/**
* Get mediaProvider.
*
* @return value of mediaProvider
*/
public MediaProvider getMediaProvider() {
return mediaProvider;
}
/**
* Get mediaUpdated.
*
* @return value of mediaUpdated
*/
public boolean isMediaUpdated() {
return mediaUpdated;
}
/**
* Set mediaUpdated.
*
* @param mediaUpdated as boolean
*/
public void setMediaUpdated(boolean mediaUpdated) {
this.mediaUpdated = mediaUpdated;
}
}