org.kitodo.production.metadata.MetadataProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.kitodo.production.metadata.MetadataProcessor.java

Source

/*
 * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
 *
 * This file is part of the Kitodo project.
 *
 * It is licensed under GNU General Public License version 3 or later.
 *
 * For the full copyright and license information, please read the
 * GPL3-License.txt file that was distributed with this source code.
 */

package org.kitodo.production.metadata;

import de.unigoettingen.sub.commons.contentlib.exceptions.ImageManagerException;
import de.unigoettingen.sub.commons.contentlib.exceptions.ImageManipulatorException;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;

import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.name.Rename;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.api.MdSec;
import org.kitodo.api.Metadata;
import org.kitodo.api.MetadataEntry;
import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface;
import org.kitodo.api.dataeditor.rulesetmanagement.StructuralElementViewInterface;
import org.kitodo.api.dataformat.IncludedStructuralElement;
import org.kitodo.api.dataformat.MediaUnit;
import org.kitodo.api.dataformat.View;
import org.kitodo.api.filemanagement.ProcessSubType;
import org.kitodo.api.filemanagement.filters.IsDirectoryFilter;
import org.kitodo.config.ConfigCore;
import org.kitodo.config.enums.ParameterCore;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Task;
import org.kitodo.data.database.beans.User;
import org.kitodo.data.database.exceptions.DAOException;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.production.enums.PositionOfNewDocStrucElement;
import org.kitodo.production.enums.SortType;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.helper.HelperComparator;
import org.kitodo.production.helper.XmlArticleCounter;
import org.kitodo.production.helper.XmlArticleCounter.CountType;
import org.kitodo.production.helper.batch.BatchTaskHelper;
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.LegacyLogicalDocStructTypeHelper;
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.helper.metadata.legacytypeimplementations.LegacyReferenceHelper;
import org.kitodo.production.metadata.display.Modes;
import org.kitodo.production.metadata.display.enums.BindState;
import org.kitodo.production.metadata.display.helper.ConfigDisplayRules;
import org.kitodo.production.metadata.elements.selectable.SelectOne;
import org.kitodo.production.metadata.elements.selectable.Separator;
import org.kitodo.production.metadata.pagination.Paginator;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.file.FileService;
import org.kitodo.production.workflow.Problem;
import org.primefaces.PrimeFaces;
import org.primefaces.event.DragDropEvent;
import org.primefaces.event.NodeSelectEvent;
import org.primefaces.event.TreeDragDropEvent;
import org.primefaces.model.DefaultTreeNode;
import org.primefaces.model.TreeNode;

/**
 * Die Klasse Schritt ist ein Bean fr einen einzelnen Schritt mit dessen
 * Eigenschaften und erlaubt die Bearbeitung der Schrittdetails.
 *
 * @author Steffen Hankiewicz
 * @version 1.00 - 17.01.2005
 */
public class MetadataProcessor {
    private static final Logger logger = LogManager.getLogger(MetadataProcessor.class);

    private static final String METADATA = "Metadata";
    private static final String STRUCTURE_ELEMENT = "StructureElement";

    private ImageHelper imageHelper;
    private MetadataHelper metaHelper;
    private LegacyMetsModsDigitalDocumentHelper gdzfile;
    private LegacyDocStructHelperInterface docStruct;
    private List<MetadataImpl> myMetadaten = new LinkedList<>();
    private Metadata currentMetadata;
    private LegacyMetsModsDigitalDocumentHelper digitalDocument;
    private Process process;
    private LegacyPrefsHelper myPrefs;
    private String userId;
    private String tempTyp;
    private String tempPersonRecord;
    private String tempPersonVorname;
    private String tempPersonNachname;
    private String tempPersonRolle;
    private URI currentTifFolder;
    private List<URI> allTifFolders;
    /* Variablen fr die Zuweisung der Seiten zu Strukturelementen */
    private String allPagesSelectionFirstPage;
    private String allPagesSelectionLastPage;
    private String[] allPagesSelection;
    private String[] structSeitenAuswahl;
    private String[] allPages;
    private MetadataImpl[] allPagesNew;
    private List<MetadataImpl> tempMetadataList = new ArrayList<>();
    private MetadataImpl selectedMetadata;
    private String currentRepresentativePage = "";
    private boolean showPagination = false;

    private boolean showNewComment = false;
    private boolean correctionComment = false;
    private String addToWikiField;
    protected User user;
    private Problem problem = new Problem();

    private String viewMode = "list";
    private String currentImage = "";

    private String paginationValue;
    // Spalten auf einem Image,
    // 3=nur jede zweite Seite hat
    // Seitennummer
    private boolean fictitious = false;
    private SelectItem[] structSeiten;
    private MetadataImpl[] structurePageNew;
    private LegacyDocStructHelperInterface logicalTopstruct;
    private TreeNodeStruct3 treeNodeStruct;
    private URI image;
    private int imageNumber = 0;
    private int imageCounter = 0;
    private int imageSize = 30;
    private int imageRotation = 0;
    private boolean displayImage = true;
    private boolean imageToStructuralElement = false;
    private final MetadataLock metadataLock = new MetadataLock();
    private String ajaxPageStart = "";
    private String ajaxPageEnd = "";
    private String pagesStart = "";
    private String pagesEnd = "";
    private Map<String, Boolean> treeProperties;
    private final ReentrantLock xmlReadingLock = new ReentrantLock();
    private final FileService fileService = ServiceManager.getFileService();
    private Paginator paginator = new Paginator();
    private TreeNode selectedTreeNode;
    private PositionOfNewDocStrucElement positionOfNewDocStrucElement = PositionOfNewDocStrucElement.AFTER_CURRENT_ELEMENT;
    private int metadataElementsToAdd = 1;
    private String addMetaDataType;
    private String addMetaDataValue;
    private boolean addServeralStructuralElementsMode = false;
    private static final String BLOCK_EXPIRED = "SperrungAbgelaufen";

    private URI imagesFolderURI = ConfigCore.getTempImagesPathAsCompleteDirectory();
    private Path imageFolderPath = Paths.get(imagesFolderURI);
    private File imageFolderFile = imageFolderPath.toFile();
    private String imagesFolder = imageFolderFile.toString().replace("pages", "images").replace("imagesTemp", "");
    private String fullsizePath = "";
    private String thumbnailPath = "";
    private String subfolderName = "";
    private static final String THUMBNAIL_FOLDER_NAME = "thumbnails";
    private static final String FULLSIZE_FOLDER_NAME = "fullsize";
    private int numberOfConvertedImages = 0;
    private int numberOfImagesToAdd = 0;
    private List<String> metadataEditorComponents = Arrays.asList("structureTreeForm:structureWrapperPanel",
            "paginationForm:paginationWrapperPanel", "metadataWrapperPanel", "commentWrapperPanel",
            "galleryWrapperPanel");
    private String referringView = "desktop";
    private RulesetManagementInterface rulesetManagement;
    private String selectedStructureType;
    private String selectedMetadataType;
    private String selectedMetadataValue;

    /**
     * Public constructor.
     */
    public MetadataProcessor() {
        this.treeProperties = new HashMap<>();
        this.treeProperties.put("showtreelevel", Boolean.FALSE);
        this.treeProperties.put("showtitle", Boolean.FALSE);
        this.treeProperties.put("fullexpanded", Boolean.TRUE);
        this.treeProperties.put("showfirstpagenumber", Boolean.FALSE);
        this.treeProperties.put("showpagesasajax", Boolean.TRUE);
    }

    /**
     * Add person.
     */
    public void addPerson() {
        this.tempPersonNachname = "";
        this.tempPersonRecord = ConfigCore.getParameter(ParameterCore.AUTHORITY_DEFAULT, "");
        this.tempPersonVorname = "";
    }

    /**
     * Save metadata to Xml file.
     */
    public String saveMetadataToXmlAndGoToProcessPage() {
        calculateMetadataAndImages();
        cleanupMetadata();
        if (storeMetadata()) {
            return referringView;
        } else {
            Helper.setMessage("XML could not be saved");
            return "";
        }
    }

    /**
     * Copy.
     *
     */
    public void copy() {

        if (this.currentMetadata instanceof MetadataEntry) {
            MetadataEntry currentMetadataEntry = (MetadataEntry) this.currentMetadata;

            MetadataEntry newMetadataEntry = new MetadataEntry();
            newMetadataEntry.setValue(currentMetadataEntry.getValue());
            newMetadataEntry.setKey(currentMetadataEntry.getKey());
            newMetadataEntry.setDomain(currentMetadataEntry.getDomain());

            setCurrentMetadata(newMetadataEntry);
        }
    }

    /**
     * Copy person.
     *
     */
    public void copyPerson() {
        throw new UnsupportedOperationException("Dead code pending removal");
    }

    /**
     * Add new metadata.
     */
    public void addMetadata() {
        if (this.selectedTreeNode.getData() instanceof IncludedStructuralElement) {
            MetadataEntry metadataEntry = new MetadataEntry();
            metadataEntry.setDomain(MdSec.DMD_SEC);
            metadataEntry.setKey(this.selectedMetadataType);
            metadataEntry.setValue(this.selectedMetadataValue);

            ((IncludedStructuralElement) this.selectedTreeNode.getData()).getMetadata().add(metadataEntry);
            this.selectedMetadataValue = "";
        } else {
            Helper.setErrorMessage("TreeNode data does not contain structure element!");
        }
    }

    /**
     * Save person.
     */
    public void savePerson() {
        throw new UnsupportedOperationException("Dead code pending removal");
    }

    /**
     * Delete.
     *
     */
    public void delete() {
        if (this.selectedTreeNode.getData() instanceof IncludedStructuralElement) {
            IncludedStructuralElement includedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode
                    .getData();
            includedStructuralElement.getMetadata().remove(this.currentMetadata);
        } else {
            Helper.setErrorMessage("Node does not contain structure element!");
        }
    }

    /**
     * Delete person.
     *
     */
    public void deletePerson() {
        throw new UnsupportedOperationException("Dead code pending removal");
    }

    /**
     * Get allowed roles.
     *
     * @return list of allowed roles as SelectItems
     */
    public List<SelectItem> getAddableRollen() {
        return this.metaHelper.getAddablePersonRoles(this.docStruct, "");
    }

    /**
     * Gets addable metadata types.
     *
     * @return The metadata types.
     */
    public List<SelectItem> getAddableMetadataTypes() {
        return getAddableMetadataTypes(docStruct, this.tempMetadataList);
    }

    private ArrayList<SelectItem> getAddableMetadataTypes(LegacyDocStructHelperInterface myDocStruct,
            List<MetadataImpl> tempMetadataList) {
        ArrayList<SelectItem> selectItems = new ArrayList<>();

        // determine all addable metadata types
        List<LegacyMetadataTypeHelper> types = myDocStruct.getAddableMetadataTypes();
        if (types == null) {
            return selectItems;
        }

        // alle Metadatentypen, die keine Person sind, oder mit einem
        // Unterstrich anfangen rausnehmen
        for (LegacyMetadataTypeHelper mdt : new ArrayList<>(types)) {
            if (mdt.isPerson()) {
                types.remove(mdt);
            }
        }

        // sort the metadata types
        HelperComparator c = new HelperComparator();
        c.setSortType(SortType.METADATA_TYPE);
        types.sort(c);

        int counter = types.size();

        for (LegacyMetadataTypeHelper mdt : types) {
            selectItems.add(new SelectItem(mdt.getName(), this.metaHelper.getMetadatatypeLanguage(mdt)));
            LegacyMetadataHelper md = new LegacyMetadataHelper(mdt);
            MetadataImpl mdum = new MetadataImpl(md, counter, this.myPrefs, this.process);
            counter++;
            if (tempMetadataList != null) {
                tempMetadataList.add(mdum);
            }
        }
        return selectItems;
    }

    /**
     * Gets addable metadata types from tempTyp.
     *
     * @return The addable metadata types from tempTyp.
     */
    public List<SelectItem> getAddableMetadataTypesFromTempType() {
        LegacyLogicalDocStructTypeHelper dst = this.myPrefs.getDocStrctTypeByName(this.tempTyp);
        LegacyDocStructHelperInterface ds = this.digitalDocument.createDocStruct(dst);

        return getAddableMetadataTypes(ds, this.tempMetadataList);
    }

    /**
     * Metadaten Einlesen.
     *
     */
    public String readXml() {
        String redirect = "";
        if (xmlReadingLock.tryLock()) {
            try {
                readXmlAndBuildTree();
            } catch (RuntimeException e) {
                logger.error(e.getMessage(), e);
            } finally {
                xmlReadingLock.unlock();
            }
        } else {
            Helper.setErrorMessage("metadataEditorThreadLock");
            return redirect;
        }
        redirect = "/pages/metadataEditor?faces-redirect=true";
        return redirect;
    }

    private void readXmlAndBuildTree() {
        // re-reading the config for display rules
        ConfigDisplayRules.getInstance().refresh();

        Modes.setBindState(BindState.EDIT);
        try {
            Integer id = Integer.valueOf(Helper.getRequestParameter("ProzesseID"));
            this.process = ServiceManager.getProcessService().getById(id);
            this.rulesetManagement = ServiceManager.getRulesetManagementService().getRulesetManagement();
            rulesetManagement.load(new File(
                    ConfigCore.getParameter(ParameterCore.DIR_RULESETS) + this.process.getRuleset().getFile()));
        } catch (NumberFormatException | DAOException e) {
            Helper.setErrorMessage("error while loading process data" + e.getMessage(), logger, e);
        } catch (IOException e) {
            Helper.setErrorMessage("Could not load ruleset file " + this.process.getRuleset().getFile());
        }
        this.userId = Helper.getRequestParameter("BenutzerID");
        this.allPagesSelectionFirstPage = "";
        this.allPagesSelectionLastPage = "";
        this.treeNodeStruct = null;
        try {
            readXmlStart();
        } catch (IOException e) {
            Helper.setErrorMessage("error while loading metadata" + e.getMessage(), logger, e);
        }

        expandTree();
        this.metadataLock.setLocked(this.process.getId(), this.userId);
    }

    /**
     * Read metadata.
     */
    public void readXmlStart() throws IOException {
        currentRepresentativePage = "";
        this.myPrefs = ServiceManager.getRulesetService().getPreferences(this.process.getRuleset());
        // TODO: Make file pattern configurable
        this.image = null;
        this.imageNumber = 1;
        this.imageRotation = 0;
        this.currentTifFolder = null;
        readAllTifFolders();

        /*
         * Dokument einlesen
         */
        this.gdzfile = ServiceManager.getProcessService().readMetadataFile(this.process);
        this.digitalDocument = this.gdzfile.getDigitalDocument();
        this.digitalDocument.addAllContentFiles();
        this.metaHelper = new MetadataHelper(this.myPrefs);
        this.imageHelper = new ImageHelper(this.myPrefs, this.digitalDocument);

        /*
         * Das Hauptelement ermitteln
         */

        // TODO: think something up, how to handle a not matching ruleset
        // causing logicalDocstruct to be null
        this.logicalTopstruct = this.digitalDocument.getLogicalDocStruct();

        // this exception needs some serious feedback because data is corrupted
        if (this.logicalTopstruct == null) {
            throw new IOException(Helper.getTranslation("metadataError"));
        }

        retrieveAllImages();
        if (ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.WITH_AUTOMATIC_PAGINATION)
                && (this.digitalDocument.getPhysicalDocStruct() == null
                        || this.digitalDocument.getPhysicalDocStruct().getAllChildren() == null
                        || this.digitalDocument.getPhysicalDocStruct().getAllChildren().isEmpty())) {
            createPagination();
        }

        List<LegacyMetadataHelper> allMetadata = this.digitalDocument.getPhysicalDocStruct().getAllMetadata();
        if (Objects.nonNull(allMetadata)) {
            for (LegacyMetadataHelper md : allMetadata) {
                if (md.getMetadataType().getName().equals("_representative")) {
                    try {
                        Integer value = Integer.valueOf(md.getValue());
                        currentRepresentativePage = String.valueOf(value - 1);
                    } catch (RuntimeException e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        }

        createDefaultValues(this.logicalTopstruct);
        saveMetadataAsBean(this.logicalTopstruct);
        readMetadataAsFirstTree();

    }

    private void createDefaultValues(LegacyDocStructHelperInterface element) {
        if (ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.METS_EDITOR_ENABLE_DEFAULT_INITIALISATION)) {
            saveMetadataAsBean(element);
            List allChildren = element.getAllChildren();
            if (Objects.nonNull(allChildren)) {
                for (LegacyDocStructHelperInterface ds : element.getAllChildren()) {
                    createDefaultValues(ds);
                }
            }
        }
    }

    private void calculateMetadataAndImages() {
        // go again through the metadata for the process and save the data
        XmlArticleCounter counter = new XmlArticleCounter();

        this.process.setSortHelperDocstructs(
                counter.getNumberOfUghElements(this.logicalTopstruct, CountType.DOCSTRUCT));
        this.process
                .setSortHelperMetadata(counter.getNumberOfUghElements(this.logicalTopstruct, CountType.METADATA));
        try {
            this.process.setSortHelperImages(fileService.getNumberOfFiles(
                    ServiceManager.getProcessService().getImagesOriginDirectory(true, this.process)));
            ServiceManager.getProcessService().save(this.process);
        } catch (DataException e) {
            Helper.setErrorMessage("errorSaving", new Object[] { Helper.getTranslation("process") }, logger, e);
        }
    }

    private void cleanupMetadata() {
        // before save remove all unused docstructs
        this.metaHelper.deleteAllUnusedElements(this.digitalDocument.getLogicalDocStruct());

        if (currentRepresentativePage != null && currentRepresentativePage.length() > 0) {
            boolean match = false;
            LegacyDocStructHelperInterface physicalDocStruct = this.digitalDocument.getPhysicalDocStruct();
            if (Objects.nonNull(physicalDocStruct) && Objects.nonNull(physicalDocStruct.getAllMetadata())) {
                for (LegacyMetadataHelper md : this.digitalDocument.getPhysicalDocStruct().getAllMetadata()) {
                    if (md.getMetadataType().getName().equals("_representative")) {
                        Integer value = Integer.valueOf(currentRepresentativePage);
                        md.setStringValue(String.valueOf(value + 1));
                        match = true;
                    }
                }
            }
            if (!match) {
                LegacyMetadataTypeHelper mdt = myPrefs.getMetadataTypeByName("_representative");
                addMetadataToPhysicalDocStruct(mdt);
            }
        }
    }

    private void addMetadataToPhysicalDocStruct(LegacyMetadataTypeHelper mdt) {
        LegacyMetadataHelper md = new LegacyMetadataHelper(mdt);
        Integer value = Integer.valueOf(currentRepresentativePage);
        md.setStringValue(String.valueOf(value + 1));
        this.digitalDocument.getPhysicalDocStruct().addMetadata(md);
    }

    private boolean storeMetadata() {
        boolean result = true;
        try {
            fileService.writeMetadataFile(this.gdzfile, this.process);
        } catch (IOException | RuntimeException e) {
            Helper.setErrorMessage("errorSaving", new Object[] { Helper.getTranslation("metadata") }, logger, e);
            result = false;
        }
        return result;
    }

    /**
     * vom aktuellen Strukturelement alle Metadaten einlesen.
     *
     * @param inStrukturelement
     *            DocStruct object
     */
    private void saveMetadataAsBean(LegacyDocStructHelperInterface inStrukturelement) {
        this.docStruct = inStrukturelement;
        LinkedList<MetadataImpl> lsMeta = new LinkedList<>();

        /*
         * alle Metadaten und die DefaultDisplay-Werte anzeigen
         */
        List<? extends LegacyMetadataHelper> tempMetadata = this.metaHelper.getMetadataInclDefaultDisplay(
                inStrukturelement, ServiceManager.getUserService().getAuthenticatedUser().getMetadataLanguage(),
                this.process);

        for (LegacyMetadataHelper metadata : tempMetadata) {
            MetadataImpl meta = new MetadataImpl(metadata, 0, this.myPrefs, this.process);
            meta.getSelectedItem();
            lsMeta.add(meta);
        }

        this.myMetadaten = lsMeta;

        determinePagesStructure(this.docStruct);
    }

    /*
     * Treeview
     */

    @SuppressWarnings("rawtypes")
    private void readMetadataAsFirstTree() {
        HashMap map;
        TreeNodeStruct3 nodes;
        List<LegacyDocStructHelperInterface> status = new ArrayList<>();

        // capture the pop-up state of all nodes
        if (this.treeNodeStruct != null) {
            for (HashMap childrenList : this.treeNodeStruct.getChildrenAsList()) {
                map = childrenList;
                nodes = (TreeNodeStruct3) map.get("node");
                if (nodes.isExpanded()) {
                    status.add(nodes.getStruct());
                }
            }
        }

        /*
         * Die Struktur als Tree3 aufbereiten
         */
        String label = this.logicalTopstruct.getDocStructType()
                .getNameByLanguage(ServiceManager.getUserService().getAuthenticatedUser().getMetadataLanguage());
        if (label == null) {
            label = this.logicalTopstruct.getDocStructType().getName();
        }

        this.treeNodeStruct = new TreeNodeStruct3(label, this.logicalTopstruct);
        readMetadataAsSecondTree(this.logicalTopstruct, this.treeNodeStruct);

        /*
         * den Ausklappzustand nach dem neu-Einlesen wieder herstellen
         */
        for (HashMap childrenList : this.treeNodeStruct.getChildrenAsListAlle()) {
            map = childrenList;
            nodes = (TreeNodeStruct3) map.get("node");
            // Ausklappstatus wiederherstellen
            if (status.contains(nodes.getStruct())) {
                nodes.setExpanded(true);
            }
            // Selection wiederherstellen
            if (this.docStruct == nodes.getStruct()) {
                nodes.setSelected(true);
            }
        }

    }

    /**
     * Metadaten in Tree3 ausgeben.
     *
     * @param inStrukturelement
     *            DocStruct object
     * @param upperNode
     *            TreeNodeStruct3 object
     */
    private void readMetadataAsSecondTree(LegacyDocStructHelperInterface inStrukturelement,
            TreeNodeStruct3 upperNode) {
        upperNode.setMainTitle(determineMetadata(inStrukturelement, "TitleDocMain"));
        upperNode.setZblNummer(determineMetadata(inStrukturelement, "ZBLIdentifier"));
        upperNode.setZblSeiten(determineMetadata(inStrukturelement, "ZBLPageNumber"));
        upperNode.setPpnDigital(determineMetadata(inStrukturelement, "IdentifierDigital"));
        upperNode.setFirstImage(
                this.metaHelper.getImageNumber(inStrukturelement, MetadataHelper.getPageNumberFirst()));
        upperNode.setLastImage(
                this.metaHelper.getImageNumber(inStrukturelement, MetadataHelper.getPageNumberLast()));
        // wenn es ein Heft ist, die Issue-Number mit anzeigen
        if (inStrukturelement.getDocStructType().getName().equals("PeriodicalIssue")) {
            upperNode.setDescription(
                    upperNode.getDescription() + " " + determineMetadata(inStrukturelement, "CurrentNo"));
        }

        // wenn es ein Periodical oder PeriodicalVolume ist, dann ausklappen
        if (inStrukturelement.getDocStructType().getName().equals("Periodical")
                || inStrukturelement.getDocStructType().getName().equals("PeriodicalVolume")) {
            upperNode.setExpanded(true);
        }

        // vom aktuellen Strukturelement alle Kinder in den Tree packen
        List<LegacyDocStructHelperInterface> children = inStrukturelement.getAllChildren();
        String language = ServiceManager.getUserService().getAuthenticatedUser().getMetadataLanguage();
        if (children != null) {
            // es gibt Kinder-Strukturelemente
            for (LegacyDocStructHelperInterface child : children) {
                String label = child.getDocStructType().getNameByLanguage(language);
                if (label == null) {
                    label = child.getDocStructType().getName();
                }
                TreeNodeStruct3 tns = new TreeNodeStruct3(label, child);
                upperNode.addChild(tns);
                readMetadataAsSecondTree(child, tns);
            }
        }
    }

    /**
     * Metadaten gezielt zurckgeben.
     *
     * @param inStrukturelement
     *            DocStruct object
     * @param type
     *            String
     */
    private String determineMetadata(LegacyDocStructHelperInterface inStrukturelement, String type) {
        StringBuilder result = new StringBuilder();
        List<LegacyMetadataHelper> allMDs = inStrukturelement.getAllMetadata();
        if (allMDs != null) {
            for (LegacyMetadataHelper md : allMDs) {
                if (md.getMetadataType().getName().equals(type)) {
                    result.append(md.getValue() == null ? "" : md.getValue());
                    result.append(" ");
                }
            }
        }
        return result.toString().trim();
    }

    /**
     * Set my structure element.
     *
     * @param inStruct
     *            DocStruct
     */
    @SuppressWarnings("rawtypes")
    public void setMyStrukturelement(LegacyDocStructHelperInterface inStruct) {
        Modes.setBindState(BindState.EDIT);
        saveMetadataAsBean(inStruct);

        /*
         * die Selektion kenntlich machen
         */
        for (HashMap childrenList : this.treeNodeStruct.getChildrenAsListAlle()) {
            TreeNodeStruct3 nodes = (TreeNodeStruct3) childrenList.get("node");
            // restore Selection
            nodes.setSelected(this.docStruct == nodes.getStruct());
        }

        updateBlocked();
    }

    /**
     * Delete selected tree node.
     */
    public void deleteNode() {
        if (this.selectedTreeNode != null && this.selectedTreeNode.getParent() != null
                && this.selectedTreeNode.getData() instanceof IncludedStructuralElement
                && this.selectedTreeNode.getParent().getData() instanceof IncludedStructuralElement) {
            IncludedStructuralElement parentIncludedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode
                    .getParent().getData();
            IncludedStructuralElement includedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode
                    .getData();
            parentIncludedStructuralElement.getChildren().remove(includedStructuralElement);
        }
    }

    /**
     * Gets position of new inserted DocStruc elements.
     *
     * @return The position of new inserted DocStruc elements.
     */
    public PositionOfNewDocStrucElement getPositionOfNewDocStrucElement() {
        return this.positionOfNewDocStrucElement;
    }

    /**
     * Sets position of new inserted DocStruc elements.
     *
     * @param positionOfNewDocStrucElement
     *            The position of new inserted DocStruc elements.
     */
    public void setPositionOfNewDocStrucElement(PositionOfNewDocStrucElement positionOfNewDocStrucElement) {
        this.positionOfNewDocStrucElement = positionOfNewDocStrucElement;
    }

    /**
     * Gets all possible positions of new DocStruct elements.
     *
     * @return The positions of new DocStruct elements.
     */
    public PositionOfNewDocStrucElement[] getPositionsOfNewDocStrucElement() {
        return PositionOfNewDocStrucElement.values();
    }

    /**
     * Adds a single new included structural element element to the current
     * included structural element tree.
     */
    public void addSingleNodeWithPages() {
        IncludedStructuralElement selectedIncludedStructuralElement = null;
        IncludedStructuralElement parentIncludedStructuralElement;
        if (this.positionOfNewDocStrucElement.equals(PositionOfNewDocStrucElement.FIRST_CHILD_OF_CURRENT_ELEMENT)
                || this.positionOfNewDocStrucElement
                        .equals(PositionOfNewDocStrucElement.LAST_CHILD_OF_CURRENT_ELEMENT)) {
            if (this.selectedTreeNode.getData() instanceof IncludedStructuralElement) {
                parentIncludedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode.getData();
            } else {
                Helper.setErrorMessage("Node does not contain structure element!");
                return;
            }
        } else {
            if (this.selectedTreeNode.getParent().getData() instanceof IncludedStructuralElement
                    && this.selectedTreeNode.getData() instanceof IncludedStructuralElement) {
                parentIncludedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode.getParent()
                        .getData();
                selectedIncludedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode.getData();
            } else {
                Helper.setErrorMessage("Parent node does not contain structure element!");
                return;
            }
        }

        IncludedStructuralElement newElement = new IncludedStructuralElement();
        newElement.setType(this.selectedStructureType);
        switch (this.positionOfNewDocStrucElement) {
        case FIRST_CHILD_OF_CURRENT_ELEMENT:
            parentIncludedStructuralElement.getChildren().add(0, newElement);
            break;
        case LAST_CHILD_OF_CURRENT_ELEMENT:
            parentIncludedStructuralElement.getChildren().add(newElement);
            break;
        case BEFOR_CURRENT_ELEMENT:
            parentIncludedStructuralElement.getChildren().add(
                    parentIncludedStructuralElement.getChildren().indexOf(selectedIncludedStructuralElement),
                    newElement);
            break;
        case AFTER_CURRENT_ELEMENT:
            parentIncludedStructuralElement.getChildren().add(
                    parentIncludedStructuralElement.getChildren().indexOf(selectedIncludedStructuralElement) + 1,
                    newElement);
            break;
        default:
            Helper.setErrorMessage(
                    "\"" + this.positionOfNewDocStrucElement.getLabel() + "\" is not a valid position");
            break;
        }
    }

    /**
     * Adds a several new DocStruct elements to the current DocStruct tree and
     * sets specified metadata.
     */
    public void addSeveralNodesWithMetadata() {
        LegacyLogicalDocStructTypeHelper docStructType = this.myPrefs.getDocStrctTypeByName(this.tempTyp);
        addNode(this.docStruct, this.digitalDocument, docStructType, this.positionOfNewDocStrucElement,
                this.metadataElementsToAdd, this.addMetaDataType, this.addMetaDataValue);
        readMetadataAsFirstTree();
    }

    private void addNewDocStructToExistingDocStruct(LegacyDocStructHelperInterface existingDocStruct,
            LegacyDocStructHelperInterface newDocStruct, int index) {

        throw new UnsupportedOperationException("Dead code pending removal");
    }

    private LegacyDocStructHelperInterface addNode(LegacyDocStructHelperInterface docStruct,
            LegacyMetsModsDigitalDocumentHelper digitalDocument, LegacyLogicalDocStructTypeHelper docStructType,
            PositionOfNewDocStrucElement positionOfNewDocStrucElement, int quantity, String metadataType,
            String value) {

        ArrayList<LegacyDocStructHelperInterface> createdElements = new ArrayList<>(quantity);

        for (int i = 0; i < quantity; i++) {

            LegacyDocStructHelperInterface createdElement = digitalDocument.createDocStruct(docStructType);
            if (docStructType != null && value != null && metadataType != null) {
                throw new UnsupportedOperationException("Dead code pending removal");
            }
            createdElements.add(createdElement);
        }

        if (positionOfNewDocStrucElement.equals(PositionOfNewDocStrucElement.LAST_CHILD_OF_CURRENT_ELEMENT)) {
            for (LegacyDocStructHelperInterface element : createdElements) {
                docStruct.addChild(element);
            }
        } else {
            LegacyDocStructHelperInterface edited = positionOfNewDocStrucElement
                    .equals(PositionOfNewDocStrucElement.FIRST_CHILD_OF_CURRENT_ELEMENT) ? docStruct
                            : docStruct.getParent();
            if (edited == null) {
                logger.debug("The selected element cannot investigate the father.");
            } else {
                List<LegacyDocStructHelperInterface> childrenBefore = edited.getAllChildren();
                if (childrenBefore == null) {
                    for (LegacyDocStructHelperInterface element : createdElements) {
                        edited.addChild(element);
                    }
                } else {
                    // Build a new list of children for the edited element
                    List<LegacyDocStructHelperInterface> newChildren = new ArrayList<>(childrenBefore.size() + 1);
                    if (positionOfNewDocStrucElement
                            .equals(PositionOfNewDocStrucElement.FIRST_CHILD_OF_CURRENT_ELEMENT)) {
                        newChildren.addAll(createdElements);
                    }
                    for (LegacyDocStructHelperInterface child : childrenBefore) {
                        if (child == docStruct && positionOfNewDocStrucElement
                                .equals(PositionOfNewDocStrucElement.BEFOR_CURRENT_ELEMENT)) {
                            newChildren.addAll(createdElements);
                        }
                        newChildren.add(child);
                        if (child == docStruct && positionOfNewDocStrucElement
                                .equals(PositionOfNewDocStrucElement.AFTER_CURRENT_ELEMENT)) {
                            newChildren.addAll(createdElements);
                        }
                    }

                    // Remove the existing children
                    for (LegacyDocStructHelperInterface child : newChildren) {
                        edited.removeChild(child);
                    }

                    // Set the new children on the edited element
                    for (LegacyDocStructHelperInterface child : newChildren) {
                        edited.addChild(child);
                    }
                }
            }
        }
        return createdElements.iterator().next();
    }

    /**
     * Returns possible DocStruct types which can be added to current DocStruct.
     *
     * @return The DocStruct types.
     */
    public SelectItem[] getAddableDocStructTypes() {
        switch (positionOfNewDocStrucElement) {
        case BEFOR_CURRENT_ELEMENT:
        case AFTER_CURRENT_ELEMENT:
            return this.metaHelper.getAddableDocStructTypes(this.docStruct, true);

        case FIRST_CHILD_OF_CURRENT_ELEMENT:
        case LAST_CHILD_OF_CURRENT_ELEMENT:
            return this.metaHelper.getAddableDocStructTypes(this.docStruct, false);

        default:
            logger.error("Invalid positionOfNewDocStrucElement");
            return new SelectItem[0];
        }
    }

    /**
     * Markus baut eine Seitenstruktur aus den vorhandenen Images.
     */
    public void createPagination() {
        this.imageHelper.createPagination(this.process, this.currentTifFolder);
        retrieveAllImages();

        // added new
        LegacyDocStructHelperInterface log = this.digitalDocument.getLogicalDocStruct();
        while (log.getDocStructType().getAnchorClass() != null && log.getAllChildren() != null
                && !log.getAllChildren().isEmpty()) {
            log = log.getAllChildren().get(0);
        }
        if (log.getDocStructType().getAnchorClass() != null) {
            return;
        }

        if (log.getAllChildren() != null) {
            for (LegacyDocStructHelperInterface child : log.getAllChildren()) {
                List<LegacyReferenceHelper> childRefs = child.getAllReferences("to");
                for (LegacyReferenceHelper toAdd : childRefs) {
                    boolean match = false;
                    for (LegacyReferenceHelper ref : log.getAllReferences("to")) {
                        if (ref.getTarget().equals(toAdd.getTarget())) {
                            match = true;
                            break;
                        }
                    }
                    if (!match) {
                        log.getAllReferences("to").add(toAdd);
                    }

                }
            }
        }
    }

    /**
     * alle Seiten ermitteln.
     */
    public void retrieveAllImages() {
        LegacyMetsModsDigitalDocumentHelper document;
        document = this.gdzfile.getDigitalDocument();

        List<LegacyDocStructHelperInterface> meineListe = document.getPhysicalDocStruct().getAllChildren();
        if (meineListe == null) {
            this.allPages = null;
            return;
        }
        int zaehler = meineListe.size();
        this.allPages = new String[zaehler];
        this.allPagesNew = new MetadataImpl[zaehler];
        zaehler = 0;
        LegacyMetadataTypeHelper mdt = this.myPrefs.getMetadataTypeByName("logicalPageNumber");
        for (LegacyDocStructHelperInterface mySeitenDocStruct : meineListe) {
            List<? extends LegacyMetadataHelper> mySeitenDocStructMetadaten = mySeitenDocStruct
                    .getAllMetadataByType(mdt);
            for (LegacyMetadataHelper page : mySeitenDocStructMetadaten) {
                this.allPagesNew[zaehler] = new MetadataImpl(page, zaehler, this.myPrefs, this.process);
                this.allPages[zaehler] = determineMetadata(page.getDocStruct(), "physPageNumber").trim() + ": "
                        + page.getValue();
            }
            zaehler++;
        }
    }

    /**
     * alle Seiten des aktuellen Strukturelements ermitteln.
     */
    private void determinePagesStructure(LegacyDocStructHelperInterface inStrukturelement) {
        if (inStrukturelement == null) {
            return;
        }
        List<LegacyReferenceHelper> references = inStrukturelement.getAllReferences("to");
        int zaehler = 0;
        int imageNr = 0;
        if (references != null) {
            references.sort((firstObject, secondObject) -> {
                Integer firstPage = 0;
                int secondPage = 0;

                LegacyMetadataTypeHelper mdt = this.myPrefs.getMetadataTypeByName("physPageNumber");
                List<? extends LegacyMetadataHelper> listMetadata = firstObject.getTarget()
                        .getAllMetadataByType(mdt);
                if (Objects.nonNull(listMetadata) && !listMetadata.isEmpty()) {
                    LegacyMetadataHelper page = listMetadata.get(0);
                    firstPage = Integer.parseInt(page.getValue());
                }
                listMetadata = secondObject.getTarget().getAllMetadataByType(mdt);
                if (Objects.nonNull(listMetadata) && !listMetadata.isEmpty()) {
                    LegacyMetadataHelper page = listMetadata.get(0);
                    secondPage = Integer.parseInt(page.getValue());
                }
                return firstPage.compareTo(secondPage);
            });

            /* die Gre der Arrays festlegen */
            this.structSeiten = new SelectItem[references.size()];
            this.structurePageNew = new MetadataImpl[references.size()];

            /* alle Referenzen durchlaufen und deren Metadaten ermitteln */
            for (LegacyReferenceHelper ref : references) {
                LegacyDocStructHelperInterface target = ref.getTarget();
                determineSecondPagesStructure(target, zaehler);
                if (imageNr == 0) {
                    imageNr = determineThirdPagesStructure(target);
                }
                zaehler++;
            }

        }

        /*
         * Wenn eine Verknpfung zwischen Strukturelement und Bildern sein soll,
         * das richtige Bild anzeigen
         */
        if (this.imageToStructuralElement) {
            identifyImage(imageNr - this.imageNumber);
        }
    }

    /**
     * alle Seiten des aktuellen Strukturelements ermitteln 2.
     */
    private void determineSecondPagesStructure(LegacyDocStructHelperInterface inStrukturelement, int inZaehler) {
        LegacyMetadataTypeHelper mdt = this.myPrefs.getMetadataTypeByName("logicalPageNumber");
        List<? extends LegacyMetadataHelper> listMetadaten = inStrukturelement.getAllMetadataByType(mdt);
        if (listMetadaten == null || listMetadaten.isEmpty()) {
            return;
        }
        for (LegacyMetadataHelper meineSeite : listMetadaten) {
            this.structurePageNew[inZaehler] = new MetadataImpl(meineSeite, inZaehler, this.myPrefs, this.process);
            this.structSeiten[inZaehler] = new SelectItem(String.valueOf(inZaehler),
                    determineMetadata(meineSeite.getDocStruct(), "physPageNumber") + ": " + meineSeite.getValue());
        }
    }

    /**
     * noch fr Testzweck zum direkten ffnen der richtigen Startseite 3.
     */
    private int determineThirdPagesStructure(LegacyDocStructHelperInterface inStrukturelement) {
        LegacyMetadataTypeHelper mdt = this.myPrefs.getMetadataTypeByName("physPageNumber");
        List<? extends LegacyMetadataHelper> listMetadaten = inStrukturelement.getAllMetadataByType(mdt);
        if (listMetadaten == null || listMetadaten.isEmpty()) {
            return 0;
        }
        int result = 0;
        for (LegacyMetadataHelper page : listMetadaten) {
            result = Integer.parseInt(page.getValue());
        }
        return result;
    }

    /**
     * Gets paginator instance.
     *
     * @return The paginator instance.
     */
    public Paginator getPaginator() {
        return paginator;
    }

    /**
     * Sets paginator instance.
     *
     * @param paginator
     *            The paginator instance.
     */
    public void setPaginator(Paginator paginator) {
        this.paginator = paginator;
    }

    /**
     * die Paginierung ndern.
     */
    public String changePagination() {

        int[] pageSelection = new int[allPagesSelection.length];
        for (int i = 0; i < allPagesSelection.length; i++) {
            pageSelection[i] = Integer.parseInt(allPagesSelection[i].split(":")[0]) - 1;
        }

        try {
            paginator.setPageSelection(pageSelection);
            paginator.setPagesToPaginate(allPagesNew);
            paginator.setFictitious(fictitious);
            paginator.setPaginationSeparator(paginationSeparators.getObject().getSeparatorString());
            paginator.setPaginationStartValue(paginationValue);
            paginator.run();
        } catch (IllegalArgumentException e) {
            Helper.setErrorMessage("fehlerBeimEinlesen", logger, e);
        }

        /*
         * zum Schluss nochmal alle Seiten neu einlesen
         */
        allPagesSelection = null;
        retrieveAllImages();
        if (!updateBlocked()) {
            return BLOCK_EXPIRED;
        }

        return null;
    }

    private void expandTree() {
        if (this.treeNodeStruct != null) {
            this.treeNodeStruct.expandNodes(this.treeProperties.get("fullexpanded"));
        }
    }

    public List<URI> getAllTifFolders() {
        return this.allTifFolders;
    }

    /**
     * Read all tif folders.
     */
    public void readAllTifFolders() {
        this.allTifFolders = new ArrayList<>();

        /* nur die _tif-Ordner anzeigen, die mit orig_ anfangen */
        FilenameFilter filterDirectory = new IsDirectoryFilter();
        List<URI> subUris = fileService.getSubUrisForProcess(filterDirectory, this.process, ProcessSubType.IMAGE,
                "");
        this.allTifFolders.addAll(subUris);

        Optional<String> suffix = ConfigCore.getOptionalString(ParameterCore.METS_EDITOR_DEFAULT_SUFFIX);
        if (suffix.isPresent()) {
            for (URI directoryUri : this.allTifFolders) {
                if (directoryUri.toString().endsWith(suffix.get())
                        || directoryUri.toString().endsWith(suffix.get().concat("/"))) {
                    this.currentTifFolder = directoryUri;
                    break;
                }
            }
        }

        if (!this.allTifFolders.contains(this.currentTifFolder)) {
            this.currentTifFolder = ServiceManager.getProcessService().getImagesTifDirectory(true,
                    this.process.getId(), this.process.getTitle(), this.process.getProcessBaseUri());
        }
    }

    /**
     * Identify image. If the images are not displayed, we do not need
     * to recalculate the image, else we recalculate them.
     *
     * @param pageNumber
     *            int
     */
    public void identifyImage(int pageNumber) {
        logger.trace("start identifyImage 1");
        if (!this.displayImage) {
            logger.trace("end identifyImage 1");
            return;
        }
        logger.trace("ocr identifyImage");

        logger.trace("dataList");
        List<URI> dataList = this.imageHelper.getImageFiles(digitalDocument.getPhysicalDocStruct());
        logger.trace("dataList 2");
        if (ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.WITH_AUTOMATIC_PAGINATION)
                && dataList.isEmpty()) {
            createPagination();
            dataList = this.imageHelper.getImageFiles(digitalDocument.getPhysicalDocStruct());
        }
        if (!dataList.isEmpty()) {
            logger.trace("dataList not null");
            logger.trace("myBildLetztes");
            if (this.image == null) {
                this.image = dataList.get(0);
            }
            if (this.currentTifFolder != null) {
                logger.trace("currentTifFolder: {}", this.currentTifFolder);
                dataList = this.imageHelper.getImageFiles(this.currentTifFolder);
            }

            if (dataList.size() >= pageNumber) {
                this.image = dataList.get(pageNumber - 1);
            } else {
                Helper.setErrorMessage("Image file for page " + pageNumber + " not found in metadata folder: "
                        + this.currentTifFolder);
                this.image = null;
            }

            this.imageNumber = pageNumber;

            URI pagesDirectory = ConfigCore.getTempImagesPathAsCompleteDirectory();

            this.imageCounter++;

            FacesContext context = FacesContext.getCurrentInstance();
            HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
            String currentPngFile = session.getId() + "_" + this.imageCounter + ".png";
            logger.trace("facescontext");

            assignNewImage(pagesDirectory, currentPngFile);
        }
    }

    private void assignNewImage(URI pagesDirectory, String currentPngFile) {
        File temporaryTifFile = null;
        try {
            temporaryTifFile = File.createTempFile("tempTif_", ".tif");
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }

        if (this.image != null) {
            try {
                URI tifFile = this.currentTifFolder.resolve(this.image);
                logger.trace("tiffconverterpfad: {}", tifFile);
                if (!fileService.fileExist(tifFile)) {
                    tifFile = ServiceManager.getProcessService().getImagesTifDirectory(true, this.process.getId(),
                            this.process.getTitle(), this.process.getProcessBaseUri()).resolve(this.image);
                    Helper.setErrorMessage("formularOrdner:TifFolders", "",
                            "image " + this.image + " does not exist in folder " + this.currentTifFolder
                                    + ", using image from "
                                    + new File(ServiceManager.getProcessService().getImagesTifDirectory(true,
                                            this.process.getId(), this.process.getTitle(),
                                            this.process.getProcessBaseUri())).getName());
                }

                // Copy tif-file to temporary folder
                try (InputStream tifFileInputStream = fileService.read(tifFile)) {
                    if (temporaryTifFile != null) {
                        FileUtils.copyInputStreamToFile(tifFileInputStream, temporaryTifFile);
                        this.imageHelper.scaleFile(temporaryTifFile.toURI(), pagesDirectory.resolve(currentPngFile),
                                this.imageSize, this.imageRotation);
                        logger.trace("scaleFile");
                    }
                }
            } catch (IOException | ImageManipulatorException | ImageManagerException | RuntimeException e) {
                Helper.setErrorMessage("could not getById image folder", logger, e);
            } finally {
                if (temporaryTifFile != null) {
                    try {
                        if (!fileService.delete(temporaryTifFile.toURI())) {
                            logger.error("Error while deleting temporary tif file: "
                                    + temporaryTifFile.getAbsolutePath());
                        }
                        // not working
                    } catch (IOException e) {
                        logger.error("Error while deleting temporary tif file: " + e.getMessage());
                    }
                }
            }
        }
    }

    /*
     * Sperrung der Metadaten aktualisieren oder prfen
     */

    private boolean updateBlocked() {
        /*
         * wenn die Sperrung noch aktiv ist und auch fr den aktuellen Nutzer
         * gilt, Sperrung aktualisieren
         */
        if (MetadataLock.isLocked(this.process.getId())
                && this.metadataLock.getLockUser(this.process.getId()).equals(this.userId)) {
            this.metadataLock.setLocked(this.process.getId(), this.userId);
            return true;
        } else {
            return false;
        }
    }

    /*
     * Navigationsanweisungen
     */

    /*
     * aus einer Liste von PPNs Strukturelemente aus dem Opac ## holen und dem
     * aktuellen Strukturelement unterordnen
     */

    /**
     * Metadata validation.
     */
    public void validate() {
        ServiceManager.getMetadataValidationService().validate(this.gdzfile, this.myPrefs, this.process);
        saveMetadataAsBean(this.docStruct);
    }

    public String getPagesEnd() {
        return this.pagesEnd;
    }

    public String getPagesStart() {
        return this.pagesStart;
    }

    public void setPagesEnd(String pagesEnd) {
        this.pagesEnd = pagesEnd;
    }

    public void setPagesStart(String pagesStart) {
        this.pagesStart = pagesStart;
    }

    private SelectOne<Separator> paginationSeparators = new SelectOne<>(
            Separator.factory(ConfigCore.getParameterOrDefaultValue(ParameterCore.PAGE_SEPARATORS)));

    /**
     * Set the first and the last page via AJAX request.
     *
     * @param docStruct
     *            the doc structure for which the pages are set
     */
    public void setFirstAndLastPageViaAjax(LegacyDocStructHelperInterface docStruct) {
        boolean startPageOk = false;
        boolean endPageOk = false;

        // go through all the pages and check if the page you have set exists
        for (String selectItem : this.allPages) {
            if (selectItem.equals(this.ajaxPageStart)) {
                startPageOk = true;
                this.allPagesSelectionFirstPage = selectItem;
            }
            if (selectItem.equals(this.ajaxPageEnd)) {
                endPageOk = true;
                this.allPagesSelectionLastPage = selectItem;
            }
        }

        // if pages are ok
        if (startPageOk && endPageOk) {
            setPageStartAndEnd(docStruct);
        } else {
            Helper.setErrorMessage("Selected image(s) unavailable");
        }
    }

    public void setPageStartAndEnd() {
        setPageStartAndEnd(this.docStruct);
    }

    /**
     * die erste und die letzte Seite festlegen und alle dazwischen zuweisen.
     *
     * @param docStruct
     *            the doc structure for which the pages are set
     */
    public void setPageStartAndEnd(LegacyDocStructHelperInterface docStruct) {
        int startPage = Integer.parseInt(this.allPagesSelectionFirstPage.split(":")[0]) - 1;
        int lastPage = Integer.parseInt(this.allPagesSelectionLastPage.split(":")[0]) - 1;

        int selectionCount = lastPage - startPage + 1;
        if (selectionCount > 0) {

            /* alle bisher zugewiesenen Seiten entfernen */
            docStruct.getAllToReferences().clear();
            int zaehler = 0;
            while (zaehler < selectionCount) {
                docStruct.addReferenceTo(this.allPagesNew[startPage + zaehler].getMd().getDocStruct(),
                        "logical_physical");
                zaehler++;
            }
        } else {
            Helper.setErrorMessage("Last page before first page is not allowed");
        }
        determinePagesStructure(docStruct);

    }

    /**
     * die erste und die letzte Seite festlegen und alle dazwischen zuweisen.
     */
    public String takePagesFromChildren() {
        if (!updateBlocked()) {
            return BLOCK_EXPIRED;
        }

        // go through all the children of the current DocStruct
        this.docStruct.getAllReferences("to").removeAll(this.docStruct.getAllReferences("to"));
        if (this.docStruct.getAllChildren() != null) {
            for (LegacyDocStructHelperInterface child : this.docStruct.getAllChildren()) {
                List<LegacyReferenceHelper> childRefs = child.getAllReferences("to");
                for (LegacyReferenceHelper toAdd : childRefs) {
                    boolean match = isFoundMatchForReference(toAdd);
                    if (!match) {
                        this.docStruct.getAllReferences("to").add(toAdd);
                    }

                }
            }
        }
        determinePagesStructure(this.docStruct);
        return null;
    }

    private boolean isFoundMatchForReference(LegacyReferenceHelper toAdd) {
        for (LegacyReferenceHelper ref : this.docStruct.getAllReferences("to")) {
            if (ref.getTarget().equals(toAdd.getTarget())) {
                return true;
            }
        }
        return false;
    }

    /**
     * die erste und die letzte Seite festlegen und alle dazwischen zuweisen.
     */
    public String imageShowFirstPage() {
        image = null;
        identifyImage(1);
        return "";
    }

    /**
     * ausgewhlte Seiten dem aktuellen Strukturelement hinzufgen.
     */
    public String addPages() {
        // go through all selected pages
        for (String page : this.allPagesSelection) {
            int currentId = Integer.parseInt(page.split(":")[0]);
            boolean schonEnthalten = false;

            /*
             * wenn schon References vorhanden, prfen, ob schon enthalten, erst
             * dann zuweisen
             */
            if (this.docStruct.getAllToReferences("logical_physical") != null) {
                for (LegacyReferenceHelper reference : this.docStruct.getAllToReferences("logical_physical")) {
                    if (reference.getTarget() == this.allPagesNew[currentId - 1].getMd().getDocStruct()) {
                        schonEnthalten = true;
                        break;
                    }
                }
            }

            if (!schonEnthalten) {
                this.docStruct.addReferenceTo(this.allPagesNew[currentId - 1].getMd().getDocStruct(),
                        "logical_physical");
            }
        }
        determinePagesStructure(this.docStruct);
        this.allPagesSelection = null;
        if (!updateBlocked()) {
            return BLOCK_EXPIRED;
        }
        return null;
    }

    /**
     * ausgewhlte Seiten aus dem Strukturelement entfernen.
     */
    public String removePages() {
        for (String structurePage : this.structSeitenAuswahl) {
            int currentId = Integer.parseInt(structurePage);
            this.docStruct.removeReferenceTo(this.structurePageNew[currentId].getMd().getDocStruct());
        }
        determinePagesStructure(this.docStruct);
        this.structSeitenAuswahl = null;
        if (!updateBlocked()) {
            return BLOCK_EXPIRED;
        }
        return null;
    }

    /**
     * Get temporal type.
     *
     * @return String
     */
    public String getTempTyp() {
        if (this.selectedMetadata == null) {
            getAddableMetadataTypes();
            if (!this.tempMetadataList.isEmpty()) {
                this.selectedMetadata = this.tempMetadataList.get(0);
            } else {
                return "";
            }
        }
        return this.selectedMetadata.getMd().getMetadataType().getName();
    }

    /**
     * Set temporal type.
     *
     * @param tempTyp
     *            String
     */
    public void setTempTyp(String tempTyp) {
        this.tempTyp = tempTyp;
    }

    /**
     * Get metadata.
     *
     * @return selectedMetadataValue as java.lang.String
     */
    public String getSelectedMetadataValue() {
        return this.selectedMetadataValue;
    }

    /**
     * Set selectedMetadataValue.
     *
     * @param selectedMetadataValue as java.lang.String
     */
    public void setSelectedMetadataValue(String selectedMetadataValue) {
        this.selectedMetadataValue = selectedMetadataValue;
    }

    public String getTempPersonNachname() {
        return this.tempPersonNachname;
    }

    public void setTempPersonNachname(String tempPersonNachname) {
        this.tempPersonNachname = tempPersonNachname;
    }

    public String getTempPersonRecord() {
        return tempPersonRecord;
    }

    public void setTempPersonRecord(String tempPersonRecord) {
        this.tempPersonRecord = tempPersonRecord;
    }

    public String getTempPersonRolle() {
        return this.tempPersonRolle;
    }

    public void setTempPersonRolle(String tempPersonRolle) {
        this.tempPersonRolle = tempPersonRolle;
    }

    public String getTempPersonVorname() {
        return this.tempPersonVorname;
    }

    public void setTempPersonVorname(String tempPersonVorname) {
        this.tempPersonVorname = tempPersonVorname;
    }

    public String[] getAllPagesSelection() {
        return this.allPagesSelection;
    }

    public void setAllPagesSelection(String[] allPagesSelection) {
        this.allPagesSelection = allPagesSelection;
    }

    public String[] getAllPages() {
        return this.allPages;
    }

    /**
     * Get structure site.
     *
     * @return SelectItem object
     */
    public SelectItem[] getStructSeiten() {
        if (this.structSeiten.length > 0 && this.structSeiten[0] == null) {
            return new SelectItem[0];
        } else {
            return this.structSeiten;
        }
    }

    public String[] getStructSeitenAuswahl() {
        return this.structSeitenAuswahl;
    }

    public void setStructSeitenAuswahl(String[] structSeitenAuswahl) {
        this.structSeitenAuswahl = structSeitenAuswahl;
    }

    public Process getProcess() {
        return this.process;
    }

    public String getPaginationValue() {
        return this.paginationValue;
    }

    public void setPaginationValue(String paginationValue) {
        this.paginationValue = paginationValue;
    }

    public String getAllPagesSelectionFirstPage() {
        return this.allPagesSelectionFirstPage;
    }

    public void setAllPagesSelectionFirstPage(String allPagesSelectionFirstPage) {
        this.allPagesSelectionFirstPage = allPagesSelectionFirstPage;
    }

    public String getAllPagesSelectionLastPage() {
        return this.allPagesSelectionLastPage;
    }

    public void setAllPagesSelectionLastPage(String allPagesSelectionLastPage) {
        this.allPagesSelectionLastPage = allPagesSelectionLastPage;
    }

    /**
     * Returns the current selected TreeNode.
     *
     * @return The TreeNode.
     */
    public TreeNode getSelectedTreeNode() {
        return selectedTreeNode;
    }

    /**
     * Sets the selecetd TreeNode.
     *
     * @param selectedTreeNode
     *            The TreeNode.
     */
    public void setSelectedTreeNode(TreeNode selectedTreeNode) {
        this.selectedTreeNode = selectedTreeNode;
    }

    /**
     * Sets MyStrukturelement on selection of TreeNode.
     *
     * @param event
     *            The NoteSelectEvent.
     */
    public void onNodeSelect(NodeSelectEvent event) {
        setMyStrukturelement((LegacyDocStructHelperInterface) event.getTreeNode().getData());
    }

    /**
     * Gets logicalTopstruct of digital document as full expanded TreeNode
     * structure.
     *
     * @return The TreeNote.
     */
    public TreeNode getTreeNodes() {
        TreeNode root = new DefaultTreeNode("root", null);
        IncludedStructuralElement includedStructuralElement = this.gdzfile.getWorkpiece().getRootElement();
        List<IncludedStructuralElement> children = Objects.nonNull(includedStructuralElement)
                ? includedStructuralElement.getChildren()
                : null;
        TreeNode visibleRoot = new DefaultTreeNode(this.gdzfile.getWorkpiece().getRootElement(), root);
        if (this.selectedTreeNode == null) {
            visibleRoot.setSelected(true);
        } else {
            if (this.selectedTreeNode.equals(visibleRoot)) {
                visibleRoot.setSelected(true);
            }
        }

        if (children != null) {
            TreeNode primeFacesTreeNode = convertIncludedStructuralElementToPrimeFacesTreeNode(children,
                    visibleRoot);
            if (Objects.nonNull(primeFacesTreeNode)) {
                visibleRoot.getChildren().add(primeFacesTreeNode);
            }
        }
        return setExpandingAll(root, true);
    }

    private TreeNode convertIncludedStructuralElementToPrimeFacesTreeNode(List<IncludedStructuralElement> elements,
            TreeNode parentTreeNode) {
        TreeNode treeNode = null;

        for (IncludedStructuralElement element : elements) {

            treeNode = new DefaultTreeNode(element, parentTreeNode);
            if (this.selectedTreeNode != null && Objects.equals(this.selectedTreeNode.getData(), element)) {
                treeNode.setSelected(true);
            }
            List<IncludedStructuralElement> children = element.getChildren();
            Collection<View> pages = element.getViews();
            if (Objects.nonNull(children)) {
                convertIncludedStructuralElementToPrimeFacesTreeNode(children, treeNode);
            }
            if (Objects.nonNull(pages)) {
                convertViewToPrimeFacesTreeNode(pages, treeNode);
            }
        }
        return treeNode;
    }

    private void convertViewToPrimeFacesTreeNode(Collection<View> elements, TreeNode parentTreeNode) {
        TreeNode treeNode = null;

        for (View element : elements) {

            treeNode = new DefaultTreeNode(element.getMediaUnit(), parentTreeNode);
            if (this.selectedTreeNode != null
                    && Objects.equals(this.selectedTreeNode.getData(), element.getMediaUnit())) {
                treeNode.setSelected(true);
            }
        }
    }

    private TreeNode setExpandingAll(TreeNode node, boolean expanded) {
        for (TreeNode child : node.getChildren()) {
            setExpandingAll(child, expanded);
        }
        node.setExpanded(expanded);

        return node;
    }

    /**
     * Handles the TreeDragDropEvent of DocStruct tree.
     *
     * @param event
     *            The TreeDragDropEvent.
     */
    public void onNodeDragDrop(TreeDragDropEvent event) {
        int dropIndex = event.getDropIndex();
        if (event.getDropNode().getData().equals("root")) {
            Helper.setErrorMessage("Only one root element allowed");
        } else {
            LegacyDocStructHelperInterface dropDocStruct = (LegacyDocStructHelperInterface) event.getDropNode()
                    .getData();
            LegacyDocStructHelperInterface dragDocStruct = (LegacyDocStructHelperInterface) event.getDragNode()
                    .getData();

            if (Objects.equals(dragDocStruct.getDocStructType().getName(), "page")) {
                String pyhsicalPageNumber = String.valueOf(getPhysicalPageNumber(dragDocStruct));
                this.docStruct = dropDocStruct;
                this.allPagesSelection = new String[1];
                this.allPagesSelection[0] = pyhsicalPageNumber;
                addPages();
                // TODO We need to implement also the removing of the draged node from the old
                // parent
                return;
            }

            if (dropDocStruct.isDocStructTypeAllowedAsChild(dragDocStruct.getDocStructType())) {
                this.docStruct = dragDocStruct;
                this.docStruct.getParent().removeChild(dragDocStruct);

                addNewDocStructToExistingDocStruct(dropDocStruct, dragDocStruct, dropIndex);
            } else {
                Helper.setErrorMessage(dragDocStruct.getDocStructType() + " not allowed as child of "
                        + dropDocStruct.getDocStructType());
            }
        }
    }

    public List<MetadataImpl> getMyMetadaten() {
        return this.myMetadaten;
    }

    public void setMyMetadaten(List<MetadataImpl> myMetadaten) {
        this.myMetadaten = myMetadaten;
    }

    public List getMetaPersonList() {
        // Dead code pending removal. TODO remove
        return Collections.EMPTY_LIST;
    }

    public void setMetaPersonList(List<?> metaPersonList) {
        // Dead code pending removal. TODO remove
    }

    public Metadata getCurrentMetadata() {
        return this.currentMetadata;
    }

    public void setCurrentMetadata(Metadata currentMetadata) {
        this.currentMetadata = currentMetadata;
    }

    public Void getCurPerson() {
        // Dead code pending removal. TODO remove
        return null;
    }

    public void setCurPerson(Object curPerson) {
        // Dead code pending removal. TODO remove
    }

    public URI getCurrentTifFolder() {
        return this.currentTifFolder;
    }

    public void setCurrentTifFolder(URI currentTifFolder) {
        this.currentTifFolder = currentTifFolder;
    }

    public boolean getFictitious() {
        return fictitious;
    }

    public void setFictitious(boolean fictitious) {
        this.fictitious = fictitious;
    }

    /**
     * Delete selected pages.
     */
    void deleteSelectedPages() throws IOException {
        List<Integer> selectedPages = new ArrayList<>();
        List<String> pagesList = Arrays.asList(allPagesSelection);
        Collections.reverse(pagesList);
        for (String order : pagesList) {
            int currentPhysicalPageNo = Integer.parseInt(order);
            selectedPages.add(currentPhysicalPageNo);
        }

        if (selectedPages.isEmpty()) {
            return;
        }

        List<LegacyDocStructHelperInterface> allPages = digitalDocument.getPhysicalDocStruct().getAllChildren();

        removeReferenceToSelectedPages(selectedPages, allPages);

        allPagesSelection = null;

        allPages = digitalDocument.getPhysicalDocStruct().getAllChildren();

        int currentPhysicalOrder = 1;
        if (allPages != null) {
            LegacyMetadataTypeHelper mdt = this.myPrefs.getMetadataTypeByName("physPageNumber");
            for (LegacyDocStructHelperInterface page : allPages) {
                List<? extends LegacyMetadataHelper> pageNoMetadata = page.getAllMetadataByType(mdt);
                if (pageNoMetadata == null || pageNoMetadata.isEmpty()) {
                    currentPhysicalOrder++;
                    break;
                }
                for (LegacyMetadataHelper pageNo : pageNoMetadata) {
                    pageNo.setStringValue(String.valueOf(currentPhysicalOrder));
                }
                currentPhysicalOrder++;
            }
        }
        retrieveAllImages();

        // current image was deleted, load first image
        if (selectedPages.contains(imageNumber - 1)) {
            imageShowFirstPage();
        } else {
            identifyImage(1);
        }
    }

    private void removeReferenceToSelectedPages(List<Integer> selectedPages,
            List<LegacyDocStructHelperInterface> allPages) throws IOException {
        for (Integer pageIndex : selectedPages) {
            LegacyDocStructHelperInterface pageToRemove = allPages.get(pageIndex);
            String imageName = pageToRemove.getImageName();
            removeImage(imageName);
            throw new UnsupportedOperationException("Dead code pending removal");
        }
    }

    private void removeImage(String fileToDelete) throws IOException {
        // TODO check what happens with .tar.gz
        String fileToDeletePrefix = fileToDelete.substring(0, fileToDelete.lastIndexOf('.'));

        for (URI folder : allTifFolders) {
            removeFiles(fileService.getImagesDirectory(process).resolve(folder), fileToDeletePrefix);
        }

        URI ocr = fileService.getOcrDirectory(process);
        if (fileService.fileExist(ocr)) {
            List<URI> folder = fileService.getSubUris(ocr);
            for (URI dir : folder) {
                if (fileService.isDirectory(dir) && !fileService.getSubUris(dir).isEmpty()) {
                    removeFiles(dir, fileToDeletePrefix);
                }
            }
        }
    }

    private void removeFiles(URI directory, String fileToDeletePrefix) throws IOException {
        List<URI> filesInFolder = fileService.getSubUris(directory);
        for (URI currentFile : filesInFolder) {
            String fileName = fileService.getFileName(currentFile);
            if (fileName.equals(fileToDeletePrefix)) {
                fileService.delete(currentFile);
            }
        }
    }

    public LegacyMetsModsDigitalDocumentHelper getDigitalDocument() {
        return digitalDocument;
    }

    public void setDigitalDocument(LegacyMetsModsDigitalDocumentHelper digitalDocument) {
        this.digitalDocument = digitalDocument;
    }

    /**
     * Get file extension.
     *
     * @param filename
     *            String
     * @return String
     */
    public static String getFileExtension(String filename) {
        return FilenameUtils.getExtension(filename);
    }

    /**
     * Checks whether a given meta-data group type is available for adding. This
     * can be used by a RenderableMetadataGroup to getById out whether it can be
     * copied or not.
     *
     * @param type
     *            meta-data group type to look for
     * @return whether the type is available to add
     */
    public boolean canCreate(Object type) {
        throw new UnsupportedOperationException("Dead code pending removal");
    }

    /**
     * Deletes the metadata group.
     *
     * @param metadataGroup
     *            metadata group to delete.
     */
    public void removeMetadataGroupFromCurrentDocStruct(Object metadataGroup) {
        throw new UnsupportedOperationException("Dead code pending removal");
    }

    /**
     * Toggles the form to show the subpage to add a new metadata group. The
     * form is prepared with the values from the metadata group that the copy
     * mode was called from.
     *
     * @return "" to indicate JSF not to navigate anywhere or
     *         "SperrungAbgelaufen" to make JSF show the message that the lock
     *         time is up and the user must leave the editor and open it anew
     */
    public String showAddMetadataGroupAsCopy() {
        return !updateBlocked() ? BLOCK_EXPIRED : "";
    }

    /**
     * Gets all metadata elements which are added to new Docstruc elements.
     *
     * @return The all metadata elements which are added to new Docstruc
     *         elements.
     */
    public int getMetadataElementsToAdd() {
        return metadataElementsToAdd;
    }

    /**
     * Sets all metadata elements which are added to new Docstruc elements.
     *
     * @param metadataElementsToAdd
     *            The all metadata elements which are added to new Docstruc
     *            elements.
     */
    public void setMetadataElementsToAdd(int metadataElementsToAdd) {
        this.metadataElementsToAdd = metadataElementsToAdd;
    }

    /**
     * Gets the metadata typ which is added to new Docstruc elements.
     *
     * @return The metadata typ which is added to new Docstruc elements.
     */
    public String getAddMetaDataType() {
        return addMetaDataType;
    }

    /**
     * Sets the metadata typ which is added to new Docstruc elements.
     *
     * @param addMetaDataType
     *            The metadata typ which is added to new Docstruc elements.
     */
    public void setAddMetaDataType(String addMetaDataType) {
        this.addMetaDataType = addMetaDataType;
    }

    /**
     * Sets the metadata value which is added to new Docstruc elements.
     *
     * @return The metadata value which is added to new Docstruc elements.
     */
    public String getAddMetaDataValue() {
        return addMetaDataValue;
    }

    /**
     * Gets the metadata value which is added to new Docstruc elements.
     *
     * @param addMetaDataValue
     *            The metadata value which is added to new Docstruc elements.
     */
    public void setAddMetaDataValue(String addMetaDataValue) {
        this.addMetaDataValue = addMetaDataValue;
    }

    /**
     * Returns <code>true</code> if adding-serveral-DocStruc-elements-mode is
     * active.
     *
     * @return <code>true</code> if adding-serveral-DocStruc-elements-mode is
     *         active.
     */
    public boolean isAddServeralStructuralElementsMode() {
        return addServeralStructuralElementsMode;
    }

    /**
     * Sets the adding-serveral-DocStruc-elements-mode.
     *
     * @param addServeralStructuralElementsMode
     *            <code>true</code> if adding-serveral-DocStruc-elements-mode
     *            should be active.
     */
    public void setAddServeralStructuralElementsMode(boolean addServeralStructuralElementsMode) {
        this.addServeralStructuralElementsMode = addServeralStructuralElementsMode;
    }

    /**
     * Get selectedStructureType.
     *
     * @return value of selectedStructureType
     */
    public String getSelectedStructureType() {
        return selectedStructureType;
    }

    /**
     * Set selectedStructureType.
     *
     * @param selectedStructureType as java.lang.String
     */
    public void setSelectedStructureType(String selectedStructureType) {
        this.selectedStructureType = selectedStructureType;
    }

    /**
     * Get selectedMetadataType.
     *
     * @return value of selectedMetadataType
     */
    public String getSelectedMetadataType() {
        return selectedMetadataType;
    }

    /**
     * Set selectedMetadataType.
     *
     * @param selectedMetadataType as java.lang.String
     */
    public void setSelectedMetadataType(String selectedMetadataType) {
        this.selectedMetadataType = selectedMetadataType;
    }

    /**
     * Get showPagination.
     *
     * @return value of showPagination
     */
    public boolean isShowPagination() {
        return showPagination;
    }

    /**
     * Set showPagination.
     *
     * @param showPagination
     *            as boolean
     */
    public void setShowPagination(boolean showPagination) {
        this.showPagination = showPagination;
    }

    public boolean isShowNewComment() {
        return showNewComment;
    }

    public void setShowNewComment(boolean showNewComment) {
        this.showNewComment = showNewComment;
    }

    public String getViewMode() {
        return viewMode;
    }

    public void setViewMode(String viewMode) {
        this.viewMode = viewMode;
    }

    public String getCurrentImage() {
        return currentImage;
    }

    public void setCurrentImage(String currentImage) {
        this.currentImage = currentImage;
    }

    /**
     * Gets numberOfImagesToAdd.
     *
     * @return The numberOfImagesToAdd.
     */
    public int getNumberOfImagesToAdd() {
        return numberOfImagesToAdd;
    }

    /**
     * Sets numberOfImagesToAdd.
     *
     * @param numberOfImagesToAdd
     *            The numberOfImagesToAdd.
     */
    public void setNumberOfImagesToAdd(int numberOfImagesToAdd) {
        this.numberOfImagesToAdd = numberOfImagesToAdd;
    }

    /**
     * Return index of currently selected page.
     *
     * @return current page index
     */
    public int getPageIndex() {
        if (!this.getImages().isEmpty() && this.getImages().contains(this.currentImage)) {
            return this.getImages().indexOf(this.currentImage) + 1;
        } else {
            return 0;
        }
    }

    /**
     * Creates dummy images for current process and paginates by configured standard setting.
     */
    public void addNewImagesAndPaginate() {
        try {
            fileService.createDummyImagesForProcess(this.process, this.numberOfImagesToAdd);
            createPagination();
            this.digitalDocument = this.gdzfile.getDigitalDocument();
            this.digitalDocument.addAllContentFiles();
            readAllTifFolders();
        } catch (IOException | URISyntaxException e) {
            Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
        }
    }

    /**
     * Gets the logical page number from a paginated docstruct.
     *
     * @param docStruct
     *            The DocStruct opject.
     * @return The logical page number.
     */
    public String getLogicalPageNumber(LegacyDocStructHelperInterface docStruct) {
        for (String page : Arrays.stream(allPages).filter(Objects::nonNull).collect(Collectors.toList())) {
            int physicalPageNumber = getPhysicalPageNumber(docStruct);
            if (page.startsWith(String.valueOf(physicalPageNumber))) {
                return getLogicalPageNumberOfPaginatedImage(page);
            }
        }
        return "";
    }

    /**
     * Gets the logical page number from a paginated docstruct.
     *
     * @param mediaUnit
     *            the MediaUnit object
     * @return The logical page number.
     */
    public String getLogicalPageNumber(MediaUnit mediaUnit) {
        for (String page : Arrays.stream(allPages).filter(Objects::nonNull).collect(Collectors.toList())) {
            int physicalPageNumber = mediaUnit.getOrder();
            if (page.startsWith(String.valueOf(physicalPageNumber))) {
                return getLogicalPageNumberOfPaginatedImage(page);
            }
        }
        return "";
    }

    private String getLogicalPageNumberOfPaginatedImage(String paginationText) {
        paginationText = paginationText.replace(" ", "");
        return paginationText.split(":")[1];
    }

    /**
     * Convert the TIFF images of the current process to PNG images for the metadata web frontend and
     * copy them to them to the webapps/images/[processID]/fullsize/ folder.
     */
    private void convertImages() {
        if (Objects.nonNull(this.currentTifFolder)) {
            try {
                ensureDirectoryExists(Paths.get(fullsizePath));

                // first, convert tiff images to pngs
                for (URI tiffPath : this.imageHelper.getImageFiles(this.currentTifFolder)) {
                    String targetPath = fullsizePath + FilenameUtils.removeExtension(tiffPath.toString()) + ".png";

                    File fullsizeFile = new File(targetPath);
                    if (fullsizeFile.exists()) {
                        continue;
                    }

                    URI tiffURI = Paths
                            .get(ConfigCore.getKitodoDataDirectory() + this.currentTifFolder + tiffPath.toString())
                            .toUri();

                    logger.info("Reading {}", tiffURI);
                    BufferedImage inputImage = ImageIO.read(tiffURI.toURL());

                    logger.info("Writing {}", targetPath);
                    ImageIO.write(inputImage, "png", new File(targetPath));
                    numberOfConvertedImages++;
                    // FIXME: this call to the update function does not work!
                    updateComponent(metadataEditorComponents);
                }

                // then, create thumbnails from the converted images
                generateThumbnails();

                updateComponent(metadataEditorComponents);
            } catch (MalformedURLException e) {
                Helper.setErrorMessage("ERROR: URL malformed!", logger, e);
            } catch (IOException e) {
                Helper.setErrorMessage("ERROR: IOException!", logger, e);
            }
        }
    }

    private void generateThumbnails() {
        updateImagesFolder();
        if (!thumbnailsExist()) {
            URI fullsizeFolderURI = Paths.get(fullsizePath).toUri();
            try (Stream<Path> imagePaths = Files.list(Paths.get(fullsizeFolderURI))) {
                logger.info("Creating thumbnails from {} to {}", fullsizePath, thumbnailPath);
                Thumbnails.of(imagePaths.filter(path -> path.toFile().isFile())
                        .filter(path -> path.toFile().canRead()).filter(path -> path.toString().endsWith(".png"))
                        .map(Path::toFile).toArray(File[]::new)).size(60, 100).outputFormat("png")
                        .toFiles(new File(thumbnailPath), Rename.PREFIX_DOT_THUMBNAIL);
                logger.info("Thumbnails completed in {}", thumbnailPath);
            } catch (IOException e) {
                logger.error("ERROR: IOException thrown while creating thumbnails: " + e.getLocalizedMessage());
            } catch (IllegalArgumentException e) {
                logger.error("ERROR: IllegalArgumentException thrown while creating thumbnails: " + e.getMessage());
            }
        }
    }

    private boolean thumbnailsExist() {
        Path thumbnailDirectory = Paths.get(thumbnailPath);
        ensureDirectoryExists(thumbnailDirectory);
        File thumbnailFile;
        File imageFile;
        for (String image : getImages()) {
            imageFile = new File(image);
            String thumbnailFilepath = thumbnailPath + File.separator + imageFile.getName();
            thumbnailFile = new File(thumbnailFilepath);
            if (!thumbnailFile.exists()) {
                return false;
            }
        }
        return true;
    }

    private void ensureDirectoryExists(Path directory) {
        if (!directory.toFile().exists() || !directory.toFile().isDirectory()) {
            try {
                Files.createDirectories(directory);
            } catch (IOException e) {
                logger.error("ERROR: IOException thrown while trying to create directory '" + directory + ": "
                        + e.getLocalizedMessage());
            }
        }
    }

    /**
     * Return the path to the thumbnail for the page with the given path 'image'.
     *
     * @param image
     *            path to image file whose thumbnail is returned
     * @return thumbnail for given image
     */
    public String getThumbnail(String image) {
        File imageFile = new File(image);
        String filename = imageFile.getName();
        return image.replace(FULLSIZE_FOLDER_NAME, THUMBNAIL_FOLDER_NAME).replace(filename,
                "thumbnail." + filename);
    }

    private void resetTemporaryFolders() {

        try {
            FileUtils.deleteDirectory(new File(fullsizePath));
        } catch (IOException e) {
            logger.error("ERROR: unable to delete directory '" + fullsizePath
                    + "' containing fullsize PNG copies of TIFF images (" + "reason: " + e.getLocalizedMessage()
                    + ")");
        }

        try {
            FileUtils.deleteDirectory(new File(thumbnailPath));
        } catch (IOException e) {
            logger.error("ERROR: unable to delete directory '" + thumbnailPath
                    + "' containing PNG thumbnails of TIFF images (" + "reason: " + e.getLocalizedMessage() + ")");
        }
    }

    /**
     * Generate PNG copies and thumbnails of all images in currently selected TIF folder.
     */
    public void generatePNGs() {
        this.numberOfConvertedImages = 0;
        // resetTemporaryFolders();
        updateComponent(Collections.singletonList("convertTIFFDialog"));
        convertImages();
    }

    /**
     * Get number of TIFF images converted to PNG.
     *
     * @return number of converted TIFF images
     */
    public int getNumberOfConvertedImages() {
        return numberOfConvertedImages;
    }

    /**
     * Get number of all images in current TIFF folder.
     *
     * @return number of images in current TIFF folder
     */
    public int getNumberOfImagesInCurrentTifFolder() {
        return this.imageHelper.getImageFiles(this.currentTifFolder).size();
    }

    /**
     * Return all structure elements.
     *
     * @return list of all structure elements
     */
    public List<LegacyDocStructHelperInterface> getAllStructureElements() {
        return getStructureElements(this.logicalTopstruct);
    }

    private List<LegacyDocStructHelperInterface> getStructureElements(LegacyDocStructHelperInterface docStruct) {
        List<LegacyDocStructHelperInterface> docStructElements = new LinkedList<>();
        if (Objects.nonNull(docStruct)) {
            docStructElements.add(docStruct);
            if (Objects.nonNull(docStruct.getAllChildren())) {
                for (LegacyDocStructHelperInterface element : docStruct.getAllChildren()) {
                    if (Objects.nonNull(element)) {
                        if (Objects.isNull(element.getAllChildren()) || element.getAllChildren().isEmpty()) {
                            docStructElements.add(element);
                        } else {
                            docStructElements.addAll(getStructureElements(element));
                        }
                    }
                }
            }
        }
        return docStructElements;
    }

    /**
     * Event listener for drag drop event.
     *
     * @param dragDropEvent
     *            the event that triggers this listener
     */
    public void onPageDrop(DragDropEvent dragDropEvent) {
        String dragId = dragDropEvent.getDragId();
        String dropId = dragDropEvent.getDropId();

        String[] dragIDComponents = dragId.split(":");
        String[] dropIDComponents = dropId.split(":");

        int sourceStructureElementIndex;
        int pageIndex;

        int targetStructureElementIndex = Integer.parseInt(dropIDComponents[2]);
        LegacyDocStructHelperInterface targetDocStruct = getAllStructureElements().get(targetStructureElementIndex);

        if (dragIDComponents[1].equals("structuredPages")) {
            sourceStructureElementIndex = Integer.parseInt(dragIDComponents[2]);
            pageIndex = Integer.parseInt(dragIDComponents[4]);

            LegacyDocStructHelperInterface sourceDocStruct = getAllStructureElements()
                    .get(sourceStructureElementIndex);

            List<String> docStructPages = getPagesAssignedToDocStruct(sourceDocStruct);

            String pagePath = docStructPages.get(pageIndex);

            if (Objects.nonNull(sourceDocStruct.getAllToReferences("logical_physical"))) {
                for (LegacyReferenceHelper reference : sourceDocStruct.getAllToReferences("logical_physical")) {

                    if (FilenameUtils.getBaseName(pagePath)
                            .equals(FilenameUtils.removeExtension(reference.getTarget().getImageName()))) {
                        // Remove page reference from source doc struct
                        sourceDocStruct.removeReferenceTo(reference.getTarget());

                        // Add page reference to target doc struct
                        targetDocStruct.addReferenceTo(reference.getTarget(), "logical_physical");

                        determinePagesStructure(sourceDocStruct);
                        determinePagesStructure(targetDocStruct);

                        break;
                    }
                }
            }
        }
    }

    /**
     * Retrieve and return list of all DocStructInferface instances referencing the
     * given DocStructInterfaces 'docStruct'.
     *
     * @param docStruct
     *            the DocStructInterface for which the references are determined
     * @return list of DocStructInterface instances referencing the given
     *         DocStructInterface docStruct
     */
    public List<LegacyDocStructHelperInterface> getPageReferencesToDocStruct(
            LegacyDocStructHelperInterface docStruct) {
        List<LegacyDocStructHelperInterface> pageReferenceDocStructs = new LinkedList<>();
        List<LegacyReferenceHelper> pageReferences = docStruct.getAllReferences("to");

        for (LegacyReferenceHelper pageReferenceInterface : pageReferences) {
            pageReferenceDocStructs.add(pageReferenceInterface.getTarget());
        }

        return pageReferenceDocStructs;
    }

    /**
     * Retrieve and return list of file paths for pages assigned to given
     * DocStructInterface 'docStruct'.
     *
     * @param docStruct
     *            DocStructInterfaces for which page file paths are returned.
     * @return list of file paths of pages assigned to given DocStructInterface
     *         'docStruct'.
     */
    private List<String> getPagesAssignedToDocStruct(LegacyDocStructHelperInterface docStruct) {
        List<String> assignedPages = new LinkedList<>();
        List<LegacyReferenceHelper> pageReferences = docStruct.getAllReferences("to");
        LegacyPrefsHelper legacyPrefsHelper = this.metaHelper.getPrefs();
        LegacyMetadataTypeHelper mdt = legacyPrefsHelper.getMetadataTypeByName("physPageNumber");

        List<String> allImages = getImages();

        if (!allImages.isEmpty()) {
            for (LegacyReferenceHelper pageReferenceInterface : pageReferences) {
                LegacyDocStructHelperInterface legacyDocStructHelperInterface = pageReferenceInterface.getTarget();
                List<LegacyMetadataHelper> allMetadata = legacyDocStructHelperInterface.getAllMetadataByType(mdt);
                for (LegacyMetadataHelper legacyMetadataHelper : allMetadata) {
                    assignedPages.add(allImages.get(Integer.parseInt(legacyMetadataHelper.getValue()) - 1));
                }
            }
        } else {
            logger.error("ERROR: empty list of image file paths!");
        }
        return assignedPages;
    }

    /**
     * Retrieve file path to png image for given DocStructInterface 'pageDocStruct'
     * representing a single scanned page image.
     *
     * @param pageDocStruct
     *            DocStructInterface for which the corresponding file path to the
     *            png copy is returned.
     * @return file path to the png image of the given DocStructInterface
     *         'pageDoctStruct'.
     */
    public String getPageImageFilePath(LegacyDocStructHelperInterface pageDocStruct) {
        final String errorMessage = "IMAGE_PATH_NOT_FOUND";
        LegacyPrefsHelper legacyPrefsHelper = this.metaHelper.getPrefs();
        LegacyMetadataTypeHelper mdt = legacyPrefsHelper.getMetadataTypeByName("physPageNumber");
        List<String> allImages = getImages();
        List<? extends LegacyMetadataHelper> allMetadata = pageDocStruct.getAllMetadataByType(mdt);

        int imageIndex;

        switch (allMetadata.size()) {
        case 0:
            logger.error("ERROR: metadata of type 'physPageNumber' not found in given page doc struct!");
            return errorMessage;
        case 1:
            imageIndex = Integer.parseInt(allMetadata.get(0).getValue()) - 1;
            if (!allImages.isEmpty() && allImages.size() > imageIndex) {
                return allImages.get(imageIndex);
            } else {
                logger.error("ERROR: empty or broken list of image file paths!");
                return errorMessage;
            }
        default:
            logger.error("WARNING: number of 'physPageNumber' metadata values in given page doc struct is "
                    + allMetadata.size() + " (1 expected)!");
            return errorMessage;
        }
    }

    /**
     * Get list of image file paths for current process.
     *
     * @return List of images.
     */
    public List<String> getImages() {
        updateImagesFolder();
        List<String> imagePaths = new LinkedList<>();
        Path pngDir = Paths.get(fullsizePath);
        ensureDirectoryExists(pngDir);
        try (Stream<Path> streamPaths = Files.list(pngDir)) {
            imagePaths = streamPaths.filter(path -> path.toFile().isFile()).filter(path -> path.toFile().canRead())
                    .filter(path -> path.toString().endsWith(".png")).sorted().map(Path::getFileName)
                    .map(Path::toString).map(filename -> "/images/" + this.process.getId() + "/"
                            + this.subfolderName + "/" + FULLSIZE_FOLDER_NAME + "/" + filename)
                    .collect(Collectors.toList());
        } catch (IOException e) {
            Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
        }
        return imagePaths;
    }

    /**
     * Determines whether the list of images exists.
     *
     * @return boolean being true when the list contains at least one image
     */
    public boolean isImageListExistent() {
        return !getImages().isEmpty();
    }

    /**
     * Retrieve and return physical page number of given DocStructInterface
     * 'pageDocStruct'.
     *
     * @param pageDocStruct
     *            DocStructInterface whose physical page number is returned.
     * @return physical page number of given DocStructInterface pageDocStruct
     */
    public int getPhysicalPageNumber(LegacyDocStructHelperInterface pageDocStruct) {
        try {
            return Integer.parseInt(determineMetadata(pageDocStruct, "physPageNumber"));
        } catch (NullPointerException e) {
            return -1;
        }
    }

    /**
     * Checks and returns whether access is granted to the image with the given
     * filepath "imagePath".
     *
     * @param imagePath
     *            the filepath of the image for which access rights are checked
     * @return true if access is granted and false otherwise
     */
    public boolean isAccessGranted(String imagePath) {
        String imageName = FilenameUtils.getName(imagePath);
        String filePath;
        if (FilenameUtils.getPath(imagePath).endsWith(FULLSIZE_FOLDER_NAME + "/")) {
            filePath = fullsizePath + imageName;
        } else if (FilenameUtils.getPath(imagePath).endsWith(THUMBNAIL_FOLDER_NAME + "/")) {
            filePath = thumbnailPath + imageName;
        } else {
            logger.error("ERROR: Image path '" + imagePath + "' is invalid!");
            return false;
        }
        File image = new File(filePath);
        return image.canRead();
    }

    /**
     * Update the image folder.
     */
    public void updateImagesFolder() {
        String uriSeparator = "/";
        String[] pathParts = this.currentTifFolder.toString().split(uriSeparator);
        if (pathParts.length > 0) {
            subfolderName = pathParts[pathParts.length - 1];
            fullsizePath = imagesFolder + this.process.getId() + File.separator + subfolderName + File.separator
                    + FULLSIZE_FOLDER_NAME + File.separator;
            thumbnailPath = imagesFolder + this.process.getId() + File.separator + subfolderName + File.separator
                    + THUMBNAIL_FOLDER_NAME + File.separator;
        } else {
            logger.error("ERROR: splitting '" + this.currentTifFolder + "' at '" + File.separator
                    + "' resulted in an empty array!");
        }
    }

    private void updateComponent(List<String> componentIDs) {
        PrimeFaces primeFaces = PrimeFaces.current();
        if (primeFaces.isAjaxRequest()) {
            primeFaces.ajax().update(componentIDs);
        }
    }

    /**
     * Get current user.
     *
     * @return User
     */
    public User getCurrentUser() {
        if (this.user == null) {
            this.user = ServiceManager.getUserService().getAuthenticatedUser();
        }
        return this.user;
    }

    /**
     * Get wiki field.
     *
     * @return values for wiki field
     */
    public String[] getWikiField() {
        refreshProcess(this.process);
        String wiki = getProcess().getWikiField();
        if (!wiki.isEmpty()) {
            wiki = wiki.replace("</p>", "");
            String[] comments = wiki.split("<p>");
            if (comments[0].isEmpty()) {
                List<String> list = new ArrayList<>(Arrays.asList(comments));
                list.remove(list.get(0));
                comments = list.toArray(new String[0]);
            }
            for (int i = 0; i < comments.length; i++) {
                String[] parts = comments[i].split(":");
                if (parts.length < 2) {
                    continue;
                }
                String author = "<i>" + parts[0] + "</i>";
                String comment = String.join(":", Arrays.copyOfRange(parts, 1, parts.length));
                comments[i] = author + ":" + comment;
            }
            return comments;
        }
        return ArrayUtils.EMPTY_STRING_ARRAY;
    }

    /**
     * Get add to wiki field.
     *
     * @return values for add to wiki field
     */
    public String getAddToWikiField() {
        return this.addToWikiField;
    }

    /**
     * Set add to wiki field.
     *
     * @param addToWikiField
     *            String
     */
    public void setAddToWikiField(String addToWikiField) {
        this.addToWikiField = addToWikiField;
    }

    /**
     * Add to wiki field.
     */
    public void addToWikiField() {
        if (addToWikiField != null && addToWikiField.length() > 0) {
            String comment = ServiceManager.getUserService().getFullName(getCurrentUser()) + ": "
                    + this.addToWikiField;
            ServiceManager.getProcessService().addToWikiField(comment, this.process);
            this.addToWikiField = "";
            try {
                ServiceManager.getProcessService().save(process);
                refreshProcess(process);
            } catch (DataException e) {
                Helper.setErrorMessage("errorReloading", new Object[] { Helper.getTranslation("wikiField") },
                        logger, e);
            }
        }
        setShowNewComment(false);
    }

    /**
     * Get correction comment.
     *
     * @return value of correction comment
     */
    public boolean isCorrectionComment() {
        return correctionComment;
    }

    public void setProcess(Process process) {
        this.process = process;
    }

    /**
     * Set correction comment.
     *
     * @param correctionComment
     *            as boolean
     */
    public void setCorrectionComment(boolean correctionComment) {
        this.correctionComment = correctionComment;
    }

    /**
     * Get problem.
     *
     * @return Problem object
     */
    public Problem getProblem() {
        return problem;
    }

    /**
     * Set problem.
     *
     * @param problem
     *            object
     */
    public void setProblem(Problem problem) {
        this.problem = problem;
    }

    /**
     * Correction message to previous Tasks.
     */
    public List<Task> getPreviousStepsForProblemReporting() {
        refreshProcess(this.process);
        return ServiceManager.getTaskService().getPreviousTasksForProblemReporting(
                ServiceManager.getProcessService().getCurrentTask(this.process).getOrdering(),
                this.process.getId());
    }

    public int getSizeOfPreviousStepsForProblemReporting() {
        return getPreviousStepsForProblemReporting().size();
    }

    /**
     * Report the problem.
     *
     *
     */
    public void reportProblem() {
        List<Task> taskList = new ArrayList<>();
        taskList.add(ServiceManager.getProcessService().getCurrentTask(this.process));
        BatchTaskHelper batchStepHelper = new BatchTaskHelper(taskList);
        batchStepHelper.setProblem(getProblem());
        batchStepHelper.reportProblemForSingle();
        refreshProcess(this.process);
        setShowNewComment(false);
        setCorrectionComment(false);
        setProblem(new Problem());
    }

    /**
     * Solve the problem.
     */
    public void solveProblem(String comment) {
        BatchTaskHelper batchStepHelper = new BatchTaskHelper();
        batchStepHelper.solveProblemForSingle(ServiceManager.getProcessService().getCurrentTask(this.process));
        refreshProcess(this.process);
        String wikiField = getProcess().getWikiField();
        String updatedComment = comment.replace("<i>", "").replace("</i>", "").trim();
        wikiField = wikiField.replace(updatedComment, updatedComment.replace("Red K", "Orange K "));
        ServiceManager.getProcessService().setWikiField(wikiField, this.process);
        try {
            ServiceManager.getProcessService().save(process);
        } catch (DataException e) {
            Helper.setErrorMessage("correctionSolveProblem", logger, e);
        }
        refreshProcess(this.process);
    }

    /**
     * refresh the process in the session.
     *
     * @param process
     *            Object process to refresh
     */
    public void refreshProcess(Process process) {
        try {
            if (process.getId() != 0) {
                ServiceManager.getProcessService().refresh(process);
                setProcess(ServiceManager.getProcessService().getById(process.getId()));
            }

        } catch (DAOException e) {
            Helper.setErrorMessage("Unable to find process with ID " + process.getId(), logger, e);
        }
    }

    public void setReferringView(String referringView) {
        this.referringView = referringView;
    }

    public String getReferringView() {
        return this.referringView;
    }

    /**
     * Get translation for name of metadata.
     * @param inputString key of the metadata to translate
     * @return translated label as String
     */
    public String getMetadataTranslation(String inputString) {
        String language = ServiceManager.getUserService().getAuthenticatedUser().getMetadataLanguage();
        LegacyPrefsHelper legacyPrefsHelper = this.metaHelper.getPrefs();
        return legacyPrefsHelper.getMetadataTypeByName(inputString).getLanguage(language);
    }

    /**
     * Get translation for name of logical structure element.
     * @param inputString key of logical structure element to translate
     * @return translated label as String
     */
    public String getDocStructTranslation(String inputString) {
        String language = ServiceManager.getUserService().getAuthenticatedUser().getMetadataLanguage();
        LegacyPrefsHelper legacyPrefsHelper = this.metaHelper.getPrefs();
        return legacyPrefsHelper.getDocStrctTypeByName(inputString).getNameByLanguage(language);
    }

    /**
     * Get structure types that may be added at the selected position.
     * @return List of SelectItems
     */
    public List<SelectItem> getAllowedStructureTypes() {
        return getAllowedItems(STRUCTURE_ELEMENT);
    }

    /**
     * Get metadata types that may be added to the selected logical element.
     * @return List of SelectItems
     */
    public List<SelectItem> getAllowedMetadata() {
        return getAllowedItems(METADATA);
    }

    private List<SelectItem> getAllowedItems(String itemType) {
        List<Locale.LanguageRange> priorityList = Locale.LanguageRange
                .parse(ServiceManager.getUserService().getAuthenticatedUser().getMetadataLanguage());

        if (Objects.isNull(this.selectedTreeNode)
                || !(this.selectedTreeNode.getData() instanceof IncludedStructuralElement)) {
            Helper.setErrorMessage("TreeNode data does not contain structure element!");
            return Collections.emptyList();
        }

        IncludedStructuralElement includedStructuralElement = (IncludedStructuralElement) this.selectedTreeNode
                .getData();
        StructuralElementViewInterface structuralElementView = rulesetManagement
                .getStructuralElementView(includedStructuralElement.getType(), "", priorityList);

        if (itemType.equals(METADATA)) {
            Map<Metadata, String> metadataEntriesMappedToKeyNames = includedStructuralElement.getMetadata()
                    .parallelStream().collect(Collectors.toMap(Function.identity(), Metadata::getKey));
            return structuralElementView
                    .getAddableMetadata(metadataEntriesMappedToKeyNames, Collections.emptyList()).stream()
                    .map(e -> new SelectItem(e.getId(), e.getLabel())).collect(Collectors.toList());
        } else if (itemType.equals(STRUCTURE_ELEMENT)) {
            Map<String, String> structureElements = structuralElementView.getAllowedSubstructuralElements();
            return structureElements.entrySet().stream().map(e -> new SelectItem(e.getKey(), e.getValue()))
                    .collect(Collectors.toList());
        } else {
            return Collections.emptyList();
        }
    }
}