Coverage Summary for Class: GalleryPanel (org.kitodo.production.forms.dataeditor)

Class Class, % Method, % Line, %
GalleryPanel 100% (1/1) 4,3% (2/47) 4,3% (19/440)


 /*
  * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
  *
  * This file is part of the Kitodo project.
  *
  * It is licensed under GNU General Public License version 3 or later.
  *
  * For the full copyright and license information, please read the
  * GPL3-License.txt file that was distributed with this source code.
  */
 
 package org.kitodo.production.forms.dataeditor;
 
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale.LanguageRange;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import javax.faces.context.FacesContext;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface;
 import org.kitodo.api.dataformat.LogicalDivision;
 import org.kitodo.api.dataformat.MediaVariant;
 import org.kitodo.api.dataformat.PhysicalDivision;
 import org.kitodo.api.dataformat.View;
 import org.kitodo.data.database.beans.Folder;
 import org.kitodo.data.database.beans.Process;
 import org.kitodo.data.database.beans.Project;
 import org.kitodo.exceptions.InvalidMetadataValueException;
 import org.kitodo.exceptions.NoSuchMetadataFieldException;
 import org.kitodo.production.enums.MediaContentType;
 import org.kitodo.production.helper.Helper;
 import org.kitodo.production.model.Subfolder;
 import org.kitodo.production.services.ServiceManager;
 import org.kitodo.production.services.file.FileService;
 import org.primefaces.PrimeFaces;
 
 /**
  * Backing bean for the gallery panel of the metadata editor.
  */
 public class GalleryPanel {
     private static final Logger logger = LogManager.getLogger(GalleryPanel.class);
 
     private static final FileService fileService = ServiceManager.getFileService();
 
     // Structured media
     private static final Pattern DRAG_STRIPE_IMAGE = Pattern.compile(
             "imagePreviewForm:structuredPages:(\\d+):structureElementDataList:(\\d+):structuredPagePanel");
 
     private static final Pattern DROP_STRIPE = Pattern.compile(
             "imagePreviewForm:structuredPages:(\\d+):structureElementDataList");
 
     private static final Pattern DROP_MEDIA_AREA = Pattern.compile(
             "imagePreviewForm:structuredPages:(\\d+):structureElementDataList:(\\d+):structuredPageDropArea");
 
     private static final Pattern DROP_MEDIA_LAST_AREA = Pattern.compile(
             "imagePreviewForm:structuredPages:(\\d+):structureElementDataList:(\\d+):structuredPageLastDropArea");
 
     // Unstructured media
     private static final Pattern DRAG_UNSTRUCTURED_MEDIA = Pattern.compile(
             "imagePreviewForm:unstructuredMediaList:(\\d+):unstructuredMediaPanel");
 
     private static final Pattern DROP_UNSTRUCTURED_STRIPE = Pattern.compile("imagePreviewForm:unstructuredMediaList");
 
     private static final Pattern DROP_UNSTRUCTURED_MEDIA_AREA = Pattern.compile(
             "imagePreviewForm:unstructuredMediaList:(\\d+):unstructuredPageDropArea");
 
     private static final Pattern DROP_UNSTRUCTURED_MEDIA_LAST_AREA = Pattern.compile(
             "imagePreviewForm:unstructuredMediaList:(\\d+):unstructuredPageLastDropArea");
 
     private final DataEditorForm dataEditor;
     private GalleryViewMode galleryViewMode = GalleryViewMode.LIST;
     private List<GalleryMediaContent> medias = Collections.emptyList();
 
     private Map<String, GalleryMediaContent> previewImageResolver = new HashMap<>();
 
     private Map<MediaContentType, Map<GalleryViewMode, MediaVariant>> mediaContentTypeVariants = new HashMap<>();
 
     private Map<MediaContentType, Subfolder> mediaContentTypePreviewFolder = new HashMap<>();
 
     private List<GalleryStripe> stripes;
 
     private String cachingUUID = "";
 
     private static final Comparator<Pair<View, LogicalDivision>> IMAGE_ORDER_COMPARATOR = Comparator.comparing(
             pair -> pair.getLeft().getPhysicalDivision().getOrder());
 
     GalleryPanel(DataEditorForm dataEditor) {
         this.dataEditor = dataEditor;
     }
 
     String getAcquisitionStage() {
         return dataEditor.getAcquisitionStage();
     }
 
     /**
      * Get galleryViewMode.
      *
      * @return value of galleryViewMode
      */
     public String getGalleryViewMode() {
         return galleryViewMode.toString().toLowerCase();
     }
 
     /**
      * Get lastSelection.
      *
      * @return value of lastSelection
      */
     public Pair<PhysicalDivision, LogicalDivision> getLastSelection() {
         if (dataEditor.getSelectedMedia().size() > 0) {
             return dataEditor.getSelectedMedia().get(dataEditor.getSelectedMedia().size() - 1);
         } else if (dataEditor.getSelectedStructure().isPresent()
                 && !dataEditor.getSelectedStructure().get().getViews().isEmpty()) {
             return new ImmutablePair<>(
                     dataEditor.getSelectedStructure().get().getViews().getFirst().getPhysicalDivision(),
                     dataEditor.getSelectedStructure().get());
         } else {
             return null;
         }
     }
 
     /**
      * Check if passed galleryMediaContent is the last selection.
      * @param galleryMediaContent GalleryMediaContent to be checked
      * @return boolean
      */
     public boolean isLastSelection(GalleryMediaContent galleryMediaContent, GalleryStripe galleryStripe) {
         if (isSelected(galleryMediaContent, galleryStripe) && dataEditor.getSelectedMedia().size() > 0
                 && Objects.nonNull(galleryMediaContent)) {
             return Objects.equals(galleryMediaContent.getView().getPhysicalDivision(),
                     dataEditor.getSelectedMedia().get(dataEditor.getSelectedMedia().size() - 1).getKey());
         }
         return false;
     }
 
     /**
      * Get the list of image file paths for the current process.
      *
      * @return List of fullsize PNG images
      */
     public List<GalleryMediaContent> getMedias() {
         return medias;
     }
 
     List<LanguageRange> getPriorityList() {
         return dataEditor.getPriorityList();
     }
 
     RulesetManagementInterface getRuleset() {
         return dataEditor.getRulesetManagement();
     }
 
     /**
      * Get list of all logical structure elements for this process.
      *
      * @return List of logical elements
      */
     public List<GalleryStripe> getStripes() {
         return stripes;
     }
 
     /**
      * Handle event of page being dragged and dropped in gallery. Parameters are provided by
      * remoteCommand "triggerOnPageDrop", see gallery.xhtml.
      */
     public void onPageDrop() {
         FacesContext context = FacesContext.getCurrentInstance();
         Map<String,String> params = context.getExternalContext().getRequestParameterMap();
         String dragId = params.get("dragId");
         String dropId = params.get("dropId");
 
         int toStripeIndex = getDropStripeIndex(dropId);
         if (toStripeIndex == -1 || !dragStripeIndexMatches(dragId)) {
             logger.error("Unsupported drag'n'drop event from {} to {}", dragId, dropId);
             return;
         }
 
         GalleryStripe toStripe = stripes.get(toStripeIndex);
 
         // move views
         List<Pair<View, LogicalDivision>> viewsToBeMoved = new ArrayList<>();
         for (Pair<PhysicalDivision, LogicalDivision> selectedElement : dataEditor.getSelectedMedia()) {
             for (View view : selectedElement.getValue().getViews()) {
                 if (Objects.equals(view.getPhysicalDivision(), selectedElement.getKey())) {
                     viewsToBeMoved.add(new ImmutablePair<>(view, selectedElement.getValue()));
                 }
             }
         }
 
         viewsToBeMoved.sort(IMAGE_ORDER_COMPARATOR);
 
         int toMediaIndex = getMediaIndex(dropId);
         try {
             updateData(toStripe, viewsToBeMoved, toMediaIndex);
         } catch (Exception e) {
             PrimeFaces.current().executeScript("$('#loadingScreen').hide();");
             PrimeFaces.current().executeScript("PF('corruptDataWarning').show();");
         }
         dataEditor.getStructurePanel().show();
         dataEditor.getPaginationPanel().show();
         this.updateStripes();
         dataEditor.getSelectedMedia().clear();
 
         // mark previously selected thumbnail in new stripe as selected
         List<View> movedViews = viewsToBeMoved.stream().map(Pair::getKey).collect(Collectors.toList());
         for (GalleryMediaContent toStripeMedia : toStripe.getMedias()) {
             if (movedViews.contains(toStripeMedia.getView())) {
                 select(toStripeMedia, toStripe, "multi");
             }
         }
     }
 
     private boolean dragStripeIndexMatches(String dragId) {
         Matcher dragStripeImageMatcher = DRAG_STRIPE_IMAGE.matcher(dragId);
         Matcher dragUnstructuredMediaMatcher = DRAG_UNSTRUCTURED_MEDIA.matcher(dragId);
         return dragUnstructuredMediaMatcher.matches() || dragStripeImageMatcher.matches();
     }
 
     private int getDropStripeIndex(String dropId) {
         // empty stripe of structure element
         Matcher dropStripeMatcher = DROP_STRIPE.matcher(dropId);
         // between two pages of structure element
         Matcher dropMediaAreaMatcher = DROP_MEDIA_AREA.matcher(dropId);
         // after last page of structure element
         Matcher dropMediaLastAreaMatcher = DROP_MEDIA_LAST_AREA.matcher(dropId);
         // empty unstructured media stripe
         Matcher dropUnstructuredMediaStripeMatcher = DROP_UNSTRUCTURED_STRIPE.matcher(dropId);
         // between two pages of unstructured media stripe
         Matcher dropUnstructuredMediaAreaMatcher = DROP_UNSTRUCTURED_MEDIA_AREA.matcher(dropId);
         // after last page of unstructured media stripe
         Matcher dropUnstructuredMediaLastAreaMatcher = DROP_UNSTRUCTURED_MEDIA_LAST_AREA.matcher(dropId);
         if (dropStripeMatcher.matches()) {
             return Integer.parseInt(dropStripeMatcher.group(1));
         } else if (dropMediaAreaMatcher.matches()) {
             return Integer.parseInt(dropMediaAreaMatcher.group(1));
         } else if (dropMediaLastAreaMatcher.matches()) {
             return Integer.parseInt(dropMediaLastAreaMatcher.group(1));
         } else if (dropUnstructuredMediaStripeMatcher.matches()
                 || dropUnstructuredMediaLastAreaMatcher.matches()
                 || dropUnstructuredMediaAreaMatcher.matches()) {
             // First (0) stripe represents logical structure (unstructured media)
             return 0;
         } else {
             return -1;
         }
     }
 
     private int getMediaIndex(String dropId) {
         Matcher dropMediaAreaMatcher = DROP_MEDIA_AREA.matcher(dropId);
         Matcher dropMediaLastAreaMatcher = DROP_MEDIA_LAST_AREA.matcher(dropId);
         Matcher dropUnstructuredMediaAreaMatcher = DROP_UNSTRUCTURED_MEDIA_AREA.matcher(dropId);
         Matcher dropUnstructuredMediaLastAreaMatcher = DROP_UNSTRUCTURED_MEDIA_LAST_AREA.matcher(dropId);
         if (dropMediaAreaMatcher.matches()) {
             return Integer.parseInt(dropMediaAreaMatcher.group(2));
         } else if (dropMediaLastAreaMatcher.matches()) {
             return Integer.parseInt(dropMediaLastAreaMatcher.group(2)) + 1;
         } else if (dropUnstructuredMediaAreaMatcher.matches()) {
             return Integer.parseInt(dropUnstructuredMediaAreaMatcher.group(1));
         } else if (dropUnstructuredMediaLastAreaMatcher.matches()) {
             return Integer.parseInt(dropUnstructuredMediaLastAreaMatcher.group(1)) + 1;
         } else {
             return -1;
         }
     }
 
     private void updateData(GalleryStripe toStripe, List<Pair<View, LogicalDivision>> viewsToBeMoved, int toMediaIndex) {
         dataEditor.getStructurePanel().changeLogicalOrderFields(toStripe.getStructure(), viewsToBeMoved, toMediaIndex);
         dataEditor.getStructurePanel().reorderPhysicalDivisions(toStripe.getStructure(), viewsToBeMoved, toMediaIndex);
         dataEditor.getStructurePanel().moveViews(toStripe.getStructure(), viewsToBeMoved, toMediaIndex);
         dataEditor.getStructurePanel().changePhysicalOrderFields();
     }
 
     /**
      * Set galleryViewMode.
      *
      * @param galleryViewMode
      *            as java.lang.String
      */
     public void setGalleryViewMode(String galleryViewMode) {
         this.galleryViewMode = GalleryViewMode.valueOf(galleryViewMode.toUpperCase());
     }
 
     /**
      * Set galleryViewMode.
      * The new value can be passed from a {@code <p:remoteCommand/>} as request parameter.
      */
     public void setGalleryViewMode() {
         Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
         this.galleryViewMode = GalleryViewMode.valueOf(params.get("galleryViewMode").toUpperCase());
     }
 
     /**
      * Update the selected TreeNode in the physical structure tree.
      */
     private void updateStructure(GalleryMediaContent galleryMediaContent, LogicalDivision structure) {
         dataEditor.getStructurePanel().updateNodeSelection(galleryMediaContent, structure);
     }
 
     void updateSelection(PhysicalDivision physicalDivision, LogicalDivision structuralElement) {
         if (physicalDivision.getMediaFiles().size() > 0) {
 
             // Update structured view
             if (this.galleryViewMode.equals(GalleryViewMode.LIST)) {
                 for (GalleryStripe galleryStripe : getStripes()) {
                     if (Objects.isNull(structuralElement) || Objects.equals(structuralElement, galleryStripe.getStructure())) {
                         for (GalleryMediaContent galleryMediaContent : galleryStripe.getMedias()) {
                             if (Objects.equals(physicalDivision, galleryMediaContent.getView().getPhysicalDivision())) {
                                 dataEditor.getSelectedMedia().clear();
                                 dataEditor.getSelectedMedia().add(new ImmutablePair<>(physicalDivision, galleryStripe.getStructure()));
                                 break;
                             }
                         }
                     }
                 }
             }
             // Update unstructured view
             else {
                 for (GalleryMediaContent galleryMediaContent : getMedias()) {
                     if (Objects.equals(physicalDivision, galleryMediaContent.getView().getPhysicalDivision())) {
                         dataEditor.getSelectedMedia().clear();
                         dataEditor.getSelectedMedia().add(new ImmutablePair<>(
                                 physicalDivision, getLogicalStructureOfMedia(galleryMediaContent).getStructure()));
                         break;
                     }
                 }
             }
         }
     }
 
     void show() {
         Process process = dataEditor.getProcess();
         Project project = process.getProject();
         List<PhysicalDivision> physicalDivisions = dataEditor.getWorkpiece()
                 .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted();
 
         mediaContentTypeVariants.clear();
         mediaContentTypePreviewFolder.clear();
 
         initMediaContentType(physicalDivisions, project.getPreview(), project.getMediaView(), MediaContentType.IMAGE);
         initMediaContentType(physicalDivisions, project.getAudioPreview(), project.getAudioMediaView(),
                 MediaContentType.AUDIO);
         initMediaContentType(physicalDivisions, project.getVideoPreview(), project.getVideoMediaView(),
                 MediaContentType.VIDEO);
 
         medias = new ArrayList<>(physicalDivisions.size());
         stripes = new ArrayList<>();
         dataEditor.getMediaProvider().resetMediaResolverForProcess(process.getId());
         cachingUUID = UUID.randomUUID().toString();
 
         updateStripes();
 
         int imagesInStructuredView = stripes.parallelStream().mapToInt(stripe -> stripe.getMedias().size()).sum();
         if (imagesInStructuredView > 200) {
             logger.warn("Number of images in structured view: {}", imagesInStructuredView);
         }
     }
 
     private void initMediaContentType(List<PhysicalDivision> physicalDivisions, Folder previewSettings,
             Folder mediaViewSettings, MediaContentType mediaContentType) {
         Map<GalleryViewMode, MediaVariant> galleryViewModeMediaVariant = new HashMap<>();
         if (Objects.nonNull(previewSettings)) {
             galleryViewModeMediaVariant.put(GalleryViewMode.LIST, getMediaVariant(previewSettings, physicalDivisions));
             mediaContentTypePreviewFolder.put(mediaContentType,
                     new Subfolder(dataEditor.getProcess(), previewSettings));
         }
         if (Objects.nonNull(mediaViewSettings)) {
             galleryViewModeMediaVariant.put(GalleryViewMode.PREVIEW,
                     getMediaVariant(mediaViewSettings, physicalDivisions));
         }
         mediaContentTypeVariants.put(mediaContentType, galleryViewModeMediaVariant);
     }
 
     /**
      * Recreate media list from workpiece, which provides medias in correct order after drag and drop.
      */
     private void updateMedia() {
         List<PhysicalDivision> physicalDivisions = dataEditor.getWorkpiece()
                 .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted();
         medias = new ArrayList<>(physicalDivisions.size());
         dataEditor.getMediaProvider().resetMediaResolverForProcess(dataEditor.getProcess().getId());
         for (PhysicalDivision physicalDivision : physicalDivisions) {
             View wholeMediaUnitView = new View();
             wholeMediaUnitView.setPhysicalDivision(physicalDivision);
             GalleryMediaContent galleryMediaContent = createGalleryMediaContent(wholeMediaUnitView, null, null);
             medias.add(galleryMediaContent);
             dataEditor.getMediaProvider().addMediaContent(dataEditor.getProcess().getId(), galleryMediaContent);
         }
     }
 
     /**
      * Recreate gallery stripes, e.g., after drag and drop.
      *
      * <p>Always update media when recreating gallery stripes such that horizontal
      * gallery stripes and vertical thumbnail list of detail view are in sync.</p>
      */
     public void updateStripes() {
         updateMedia();
         stripes = new ArrayList<>();
         addStripesRecursive(dataEditor.getWorkpiece().getLogicalStructure());
     }
 
     private static MediaVariant getMediaVariant(Folder folderSettings, List<PhysicalDivision> physicalDivisions) {
         String use = folderSettings.getFileGroup();
         Optional<MediaVariant> optionalMediaVariant = physicalDivisions.parallelStream().map(PhysicalDivision::getMediaFiles)
                 .flatMap(mediaFiles -> mediaFiles.entrySet().parallelStream()).map(Entry::getKey)
                 .filter(mediaVariant -> use.equals(mediaVariant.getUse())).findAny();
         if (optionalMediaVariant.isPresent()) {
             return optionalMediaVariant.get();
         } else {
             MediaVariant mediaVariant = new MediaVariant();
             mediaVariant.setMimeType(folderSettings.getMimeType());
             mediaVariant.setUse(use);
             return mediaVariant;
         }
     }
 
     private void addStripesRecursive(LogicalDivision structure) {
         List<Integer> treeNodeIdList = new ArrayList<Integer>();
         Integer idx = 0;
         Process process = dataEditor.getProcess();
         if (Objects.nonNull(process) && Objects.nonNull(process.getParent())) {
             // determine how many additional tree nodes are added for parent processes
             // before the actual logical structure
             idx = dataEditor.getStructurePanel().getNumberOfParentLinkRootNodesAdded();
         }
         treeNodeIdList.add(idx);
         addStripesRecursive(structure, treeNodeIdList);
     }
 
     private void addStripesRecursive(LogicalDivision structure, List<Integer> treeNodeIdList) {
         String stripeTreeNodeId = treeNodeIdList.stream().map(String::valueOf).collect(Collectors.joining("_"));
         GalleryStripe galleryStripe = new GalleryStripe(this, structure, stripeTreeNodeId);
         stripes.add(galleryStripe);
 
         int siblingWithViewsIdx = 0;
         int siblingWithoutViewsIdx = 0;
         for (Pair<View, LogicalDivision> pair : StructurePanel.mergeLogicalStructureViewsAndChildren(structure)) {
             View view = pair.getLeft();
             LogicalDivision child = pair.getRight();
             if (Objects.nonNull(child)) {
                 // add child
                 if (Objects.isNull(child.getLink())) {
                     List<Integer> childTreeNodeIdList = new ArrayList<>(treeNodeIdList);
                     if (!dataEditor.getStructurePanel().logicalStructureTreeContainsMedia()) {
                         childTreeNodeIdList.add(siblingWithoutViewsIdx);
                     } else {
                         childTreeNodeIdList.add(siblingWithViewsIdx);
                     }
                     addStripesRecursive(child, childTreeNodeIdList);
                 }
                 siblingWithViewsIdx += 1;
                 siblingWithoutViewsIdx += 1;
             } else {
                 // add view
                 for (GalleryMediaContent galleryMediaContent : medias) {
                     if (Objects.equals(view.getPhysicalDivision(), galleryMediaContent.getView().getPhysicalDivision())) {
                         galleryStripe.getMedias().add(galleryMediaContent);
                         List<Integer> viewTreeNodeIdList = new ArrayList<>(treeNodeIdList);
                         viewTreeNodeIdList.add(siblingWithViewsIdx);
                         String viewTreeNodeId = viewTreeNodeIdList.stream().map(String::valueOf).collect(Collectors.joining("_"));
                         galleryMediaContent.setLogicalTreeNodeId(viewTreeNodeId);
                         dataEditor.getMediaProvider().addMediaContent(dataEditor.getProcess().getId(), galleryMediaContent);
                         siblingWithViewsIdx += 1;
                         break;
                     }
                 }
             }
         }
     }
 
     private GalleryMediaContent createGalleryMediaContent(View view, String stripeTreeNodeId, Integer index) {
         PhysicalDivision physicalDivision = view.getPhysicalDivision();
 
         MediaContentType mediaContentType = MediaContentType.IMAGE;
         if (physicalDivision.getMediaFiles().keySet().stream()
                 .anyMatch(mediaFile -> mediaFile.getMimeType().startsWith("video"))) {
             mediaContentType = MediaContentType.VIDEO;
         } else if (physicalDivision.getMediaFiles().keySet().stream()
                 .anyMatch(mediaFile -> mediaFile.getMimeType().startsWith("audio"))) {
             mediaContentType = MediaContentType.AUDIO;
         }
 
         return buildGalleryMediaContent(view, stripeTreeNodeId, index, mediaContentType, physicalDivision);
     }
 
     private GalleryMediaContent buildGalleryMediaContent(View view, String stripeTreeNodeId, Integer index,
             MediaContentType mediaContentType, PhysicalDivision physicalDivision) {
 
         MediaVariant previewMediaVariant = mediaContentTypeVariants.get(mediaContentType).get(GalleryViewMode.LIST);
         URI previewUri = physicalDivision.getMediaFiles()
                 .get(previewMediaVariant);
 
         URI resourceListUri = null;
         if (Objects.nonNull(previewUri)) {
             resourceListUri = previewUri.isAbsolute()
                     ? previewUri
                     : fileService.getResourceUriForProcessRelativeUri(dataEditor.getProcess(), previewUri);
         }
 
         // prefer canonical of preview folder
         MediaVariant mediaViewMediaVariant = mediaContentTypeVariants.get(mediaContentType)
                 .get(GalleryViewMode.PREVIEW);
         URI mediaViewUri = physicalDivision.getMediaFiles().get(mediaViewMediaVariant);
         URI resourceMediaViewUri = null;
         if (Objects.nonNull(mediaViewUri)) {
             resourceMediaViewUri = mediaViewUri.isAbsolute()
                     ? mediaViewUri
                     : fileService.getResourceUriForProcessRelativeUri(dataEditor.getProcess(), mediaViewUri);
         }
 
         // prefer canonical of preview folder
         String canonical = Objects.nonNull(resourceListUri) && mediaContentTypePreviewFolder.containsKey(
                 mediaContentType) && Objects.nonNull(mediaContentTypePreviewFolder.get(mediaContentType))
                 ? mediaContentTypePreviewFolder.get(mediaContentType).getCanonical(resourceListUri)
                 : null;
         if (Objects.isNull(canonical)) {
             // if preview media not available, load canonical id from other folders
             canonical = dataEditor.getStructurePanel().findCanonicalIdForView(view);
         }
 
         String treeNodeId = "unknown";
         if (Objects.nonNull(stripeTreeNodeId) && Objects.nonNull(index)) {
             treeNodeId = stripeTreeNodeId + "_" + index;
         }
 
         return new GalleryMediaContent(mediaContentType, view, canonical,
                 Objects.nonNull(previewMediaVariant) ? previewMediaVariant.getMimeType() : null,
                 resourceListUri,
                 Objects.nonNull(mediaViewMediaVariant) ? mediaViewMediaVariant.getMimeType() : null,
                 resourceMediaViewUri, treeNodeId);
     }
 
     /**
      * Return the GalleryStripe instance representing the logical structure element to which the Media represented
      * by the given GalleryMediaContent instance is assigned. Return null, if Media is not assigned to any logical
      * structure element.
      *
      * @param galleryMediaContent
      *          Media
      * @return GalleryStripe representing the logical structure element to which the Media is assigned
      */
     GalleryStripe getLogicalStructureOfMedia(GalleryMediaContent galleryMediaContent) {
         for (GalleryStripe galleryStripe : stripes) {
             for (GalleryMediaContent mediaContent : galleryStripe.getMedias()) {
                 if (galleryMediaContent.getId().equals(mediaContent.getId())) {
                     return galleryStripe;
                 }
             }
         }
         return null;
     }
 
     GalleryMediaContent getGalleryMediaContent(View view) {
         if (Objects.nonNull(view)) {
             for (GalleryMediaContent galleryMediaContent : this.medias) {
                 if (galleryMediaContent.getView().getPhysicalDivision().equals(view.getPhysicalDivision())) {
                     return galleryMediaContent;
                 }
             }
         }
         return null;
     }
 
     /**
      * Get the GalleryMediaContent object of the passed PhysicalDivision.
      * @param physicalDivision Object to find the GalleryMediaContent for
      * @return GalleryMediaContent
      */
     public GalleryMediaContent getGalleryMediaContent(PhysicalDivision physicalDivision) {
         for (GalleryStripe galleryStripe : stripes) {
             for (GalleryMediaContent media : galleryStripe.getMedias()) {
                 if (Objects.equals(media.getView().getPhysicalDivision(), physicalDivision)) {
                     return media;
                 }
             }
         }
         return null;
     }
 
     /**
      * Get a List of all PhysicalDivisions and the LogicalDivisions they are
      * assigned to which are displayed between two selected PhysicalDivisions.
      * This method selects the Stripes that are affected by the selection and
      * delegates the selection of the contained PhysicalDivisions.
      *
      * @param first
      *            First selected PhysicalDivision. A Pair of the
      *            PhysicalDivision and the LogicalDivision to which the
      *            PhysicalDivision is assigned.
      * @param last
      *            Last selected PhysicalDivision. A Pair of the PhysicalDivision
      *            and the Logical Division to which the PhysicalDivision is
      *            assigned.
      * @return A List of all selected PhysicalDivisions
      */
     private List<Pair<PhysicalDivision, LogicalDivision>> getMediaWithinRange(Pair<PhysicalDivision, LogicalDivision> first,
                                                           Pair<PhysicalDivision, LogicalDivision> last) {
         // Pairs of stripe index and media index
         Pair<Integer, Integer> firstIndices = getIndices(first.getKey(), first.getValue());
         Pair<Integer, Integer> lastIndices = getIndices(last.getKey(), last.getValue());
 
         if (!Objects.nonNull(firstIndices) || !Objects.nonNull(lastIndices)) {
             return new LinkedList<>();
         }
 
         List<GalleryStripe> stripesWithinRange = new LinkedList<>();
 
         /* Stripe with index 0 represents "unstructured media".
            This stripe is displayed last, but is actually the logical structure (first stripe). */
         if (Objects.equals(firstIndices.getKey(), lastIndices.getKey())) {
             stripesWithinRange.add(stripes.get(firstIndices.getKey()));
         } else if (lastIndices.getKey() == 0) {
             // count up, last stripe is "unstructured media"
             for (int i = firstIndices.getKey(); i <= stripes.size() - 1; i++) {
                 stripesWithinRange.add(stripes.get(i));
             }
             stripesWithinRange.add(stripes.get(0));
         } else if (firstIndices.getKey() != 0 && firstIndices.getKey() < lastIndices.getKey()) {
             // count up first stripe and last stripe are not "unstructured media"
             for (int i = firstIndices.getKey(); i <= lastIndices.getKey(); i++) {
                 stripesWithinRange.add(stripes.get(i));
             }
         } else if (firstIndices.getKey() == 0) {
             // count down, first stripe is "unstructured media"
             stripesWithinRange.add(stripes.get(0));
             for (int i = stripes.size() - 1; i >= lastIndices.getKey(); i--) {
                 stripesWithinRange.add(stripes.get(i));
             }
         } else {
             // count down
             for (int i = firstIndices.getKey(); i >= lastIndices.getKey(); i--) {
                 stripesWithinRange.add(stripes.get(i));
             }
         }
 
         return getMediaWithinRangeFromSelectedStripes(firstIndices, lastIndices, stripesWithinRange);
     }
 
     /**
      * Get a List of all PhysicalDivisions and the LogicalDivisions they are
      * assigned to which are displayed between two selected PhysicalDivisions.
      * This method selected the PhysicalDivisions between and including the two
      * indices.
      *
      * @param firstIndices
      *            First selected PhysicalDivision. A Pair of indices of the
      *            PhysicalDivision and the LogicalDivision to which the
      *            PhysicalDivision is assigned.
      * @param lastIndices
      *            Last selected PhysicalDivision. A Pair of indices of the
      *            PhysicalDivision and the Logical Division to which the
      *            PhysicalDivision is assigned.
      * @param galleryStripes
      *            A List of GalleryStripes which contain the two selected
      *            PhysicalDivisions and all in between
      * @return A List of all selected PhysicalDivisions
      */
     private List<Pair<PhysicalDivision, LogicalDivision>> getMediaWithinRangeFromSelectedStripes(
             Pair<Integer, Integer> firstIndices, Pair<Integer, Integer> lastIndices, List<GalleryStripe> galleryStripes) {
         boolean countDown = false;
 
         /* Count down if firstIndices are larger than lastIndices.
            (Each Pair represents a stripe index and the index of the selected media within this stripe.)
          */
         if (firstIndices.getKey() > lastIndices.getKey() && lastIndices.getKey() != 0
                 || Objects.equals(firstIndices.getKey(), lastIndices.getKey()) && firstIndices.getValue() > lastIndices.getValue()
                 || !Objects.equals(firstIndices.getKey(), lastIndices.getKey()) && firstIndices.getKey() == 0) {
             countDown = true;
         }
 
         if (galleryStripes.size() == 0) {
             return new LinkedList<>();
         }
 
         if (countDown) {
             return getMediaBackwards(firstIndices.getValue(), lastIndices.getValue(), galleryStripes);
         } else {
             return getMediaForwards(firstIndices.getValue(), lastIndices.getValue(), galleryStripes);
         }
     }
 
     private List<Pair<PhysicalDivision, LogicalDivision>> getMediaForwards(Integer firstIndex, Integer lastIndex,
                                                                               List<GalleryStripe> galleryStripes) {
         List<Pair<PhysicalDivision, LogicalDivision>> mediaWithinRange = new LinkedList<>();
         GalleryStripe firstStripe = galleryStripes.get(0);
 
         if (galleryStripes.size() == 1) {
             for (int i = firstIndex; i <= lastIndex; i++) {
                 mediaWithinRange.add(
                         new ImmutablePair<>(firstStripe.getMedias().get(i).getView().getPhysicalDivision(), firstStripe.getStructure()));
             }
         } else {
 
             for (int i = firstIndex; i <= firstStripe.getMedias().size() - 1; i++) {
                 mediaWithinRange.add(
                         new ImmutablePair<>(firstStripe.getMedias().get(i).getView().getPhysicalDivision(), firstStripe.getStructure()));
             }
 
             for (int i = 1; i <= galleryStripes.size() - 2; i++) {
                 GalleryStripe galleryStripe = galleryStripes.get(i);
                 for (GalleryMediaContent media : galleryStripe.getMedias()) {
                     mediaWithinRange.add(new ImmutablePair<>(media.getView().getPhysicalDivision(), galleryStripe.getStructure()));
                 }
             }
 
             GalleryStripe lastStripe = galleryStripes.get(galleryStripes.size() - 1);
             for (int i = 0; i <= lastIndex; i++) {
                 mediaWithinRange.add(
                         new ImmutablePair<>(lastStripe.getMedias().get(i).getView().getPhysicalDivision(), lastStripe.getStructure()));
             }
         }
         return mediaWithinRange;
     }
 
     private List<Pair<PhysicalDivision, LogicalDivision>> getMediaBackwards(Integer firstIndex, Integer lastIndex,
                                                                                List<GalleryStripe> galleryStripes) {
         List<Pair<PhysicalDivision, LogicalDivision>> mediaWithinRange = new LinkedList<>();
         GalleryStripe firstStripe = galleryStripes.get(0);
 
         if (galleryStripes.size() == 1) {
             for (int i = firstIndex; i >= lastIndex; i--) {
                 mediaWithinRange.add(
                         new ImmutablePair<>(firstStripe.getMedias().get(i).getView().getPhysicalDivision(), firstStripe.getStructure()));
             }
         } else {
             for (int i = firstIndex; i >= 0; i--) {
                 mediaWithinRange.add(
                         new ImmutablePair<>(firstStripe.getMedias().get(i).getView().getPhysicalDivision(), firstStripe.getStructure()));
             }
 
             for (int i = 1; i <= galleryStripes.size() - 2; i++) {
                 GalleryStripe galleryStripe = galleryStripes.get(i);
                 for (int j = galleryStripe.getMedias().size() - 1; j >= 0; j--) {
                     mediaWithinRange.add(new ImmutablePair<>(
                             galleryStripe.getMedias().get(j).getView().getPhysicalDivision(), galleryStripe.getStructure()));
                 }
             }
 
             GalleryStripe lastStripe = galleryStripes.get(galleryStripes.size() - 1);
             for (int i = lastStripe.getMedias().size() - 1; i >= lastIndex; i--) {
                 mediaWithinRange.add(
                         new ImmutablePair<>(lastStripe.getMedias().get(i).getView().getPhysicalDivision(), lastStripe.getStructure()));
             }
         }
         return mediaWithinRange;
     }
 
     private Pair<Integer, Integer> getIndices(PhysicalDivision physicalDivision, LogicalDivision structuralElement) {
         for (GalleryStripe galleryStripe : stripes) {
             if (Objects.equals(galleryStripe.getStructure(), structuralElement)) {
                 for (GalleryMediaContent media : galleryStripe.getMedias()) {
                     if (Objects.equals(media.getView().getPhysicalDivision(), physicalDivision)) {
                         return new ImmutablePair<>(stripes.indexOf(galleryStripe), galleryStripe.getMedias().indexOf(media));
                     }
                 }
             }
         }
         return null;
     }
 
     /**
      * Check whether the passed GalleryMediaContent is selected.
      * @param galleryMediaContent the GalleryMediaContent to be checked
      * @param galleryStripe the GalleryStripe where the GalleryMediaContent is located
      * @return Boolean whether passed GalleryMediaContent is selected
      */
     public boolean isSelected(GalleryMediaContent galleryMediaContent, GalleryStripe galleryStripe) {
         if (Objects.nonNull(galleryMediaContent) && Objects.nonNull(galleryMediaContent.getView())) {
             PhysicalDivision physicalDivision = galleryMediaContent.getView().getPhysicalDivision();
             if (Objects.nonNull(physicalDivision)) {
                 if (Objects.nonNull(galleryStripe)) {
                     return dataEditor.isSelected(physicalDivision, galleryStripe.getStructure());
                 } else {
                     return dataEditor.isSelected(physicalDivision, getLogicalStructureOfMedia(galleryMediaContent).getStructure());
                 }
             }
         }
         return false;
     }
 
     private void selectMedia(String physicalDivisionOrder, String stripeIndex, String selectionType) {
         PhysicalDivision selectedPhysicalDivision = null;
         for (PhysicalDivision physicalDivision : this.dataEditor.getWorkpiece()
                 .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted()) {
             if (Objects.equals(physicalDivision.getOrder(), Integer.parseInt(physicalDivisionOrder))) {
                 selectedPhysicalDivision = physicalDivision;
                 break;
             }
         }
 
         try {
             this.dataEditor.getMetadataPanel().preserveLogical();
         } catch (InvalidMetadataValueException | NoSuchMetadataFieldException e) {
             Helper.setErrorMessage(e.getLocalizedMessage(), logger, e);
         }
         if (Objects.nonNull(selectedPhysicalDivision)) {
             this.dataEditor.getMetadataPanel().showPageInLogical(selectedPhysicalDivision);
         } else {
             Helper.setErrorMessage("Selected PhysicalDivision is null!");
         }
 
         GalleryStripe parentStripe;
         try {
             parentStripe = stripes.get(Integer.parseInt(stripeIndex));
         } catch (NumberFormatException e) {
             parentStripe = null;
         }
 
         GalleryMediaContent currentSelection = getGalleryMediaContent(selectedPhysicalDivision);
         select(currentSelection, parentStripe, selectionType);
 
         String scrollScripts = "scrollToSelectedTreeNode();scrollToSelectedPaginationRow();";
         if (GalleryViewMode.PREVIEW.equals(galleryViewMode)) {
             PrimeFaces.current().executeScript("checkScrollPosition();initializeImage();" + scrollScripts);
         } else {
             PrimeFaces.current().executeScript(scrollScripts);
         }
     }
 
     private void selectStructure(String stripeIndex) {
         LogicalDivision logicalDivision = stripes.get(Integer.parseInt(stripeIndex)).getStructure();
         dataEditor.getSelectedMedia().clear();
         dataEditor.getStructurePanel().updateLogicalNodeSelection(logicalDivision);
         PrimeFaces.current().executeScript("scrollToSelectedTreeNode();scrollToSelectedPaginationRow();");
     }
 
     /**
      * Update the media selection based on the page index, stripe index and pressed modifier keys passed as request parameter.
      * This method should be called via remoteCommand.
      */
     public void select() {
         Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
         String physicalDivisionOrder = params.get("page");
         String stripeIndex = params.get("stripe");
         String selectionType = params.get("selectionType");
         boolean triggerContextMenu = Boolean.parseBoolean(params.get("triggerContextMenu"));
         String createEvent = "";
         if (triggerContextMenu) {
             int pageX = Integer.parseInt(params.get("pageX"));
             int pageY = Integer.parseInt(params.get("pageY"));
             createEvent = "(function(){let e=new Event('mousedown');e.pageX=" + pageX + ";e.pageY=" + pageY + ";return e})()";
         }
 
         if (StringUtils.isNotBlank(physicalDivisionOrder)) {
             selectMedia(physicalDivisionOrder, stripeIndex, selectionType);
             if (triggerContextMenu) {
                 PrimeFaces.current().executeScript("PF('mediaContextMenu').show(" + createEvent + ")");
             }
         } else if (StringUtils.isNotBlank(stripeIndex)) {
             try {
                 selectStructure(stripeIndex);
                 if (triggerContextMenu) {
                     PrimeFaces.current().executeScript("PF('stripeContextMenu').show(" + createEvent + ")");
                 }
             } catch (NumberFormatException e) {
                 Helper.setErrorMessage("Could not select stripe: Stripe index \"" + stripeIndex + "\" could not be parsed.");
             }
         }
     }
 
     /**
      * Update selection based on the passed GalleryMediaContent and selectionType.
      *
      * @param currentSelection the GalleryMediaContent that was clicked
      * @param parentStripe the GalleryStripe the clicked GalleryMediaContent is part of
      * @param selectionType the type of selection based on the pressed modifier key
      */
     private void select(GalleryMediaContent currentSelection, GalleryStripe parentStripe, String selectionType) {
         if (Objects.isNull(parentStripe)) {
             parentStripe = getLogicalStructureOfMedia(currentSelection);
         }
 
         if (Objects.isNull(currentSelection)) {
             Helper.setErrorMessage("Passed GalleryMediaContent must not be null.");
             return;
         }
 
         switch (selectionType) {
             case "multi":
                 multiSelect(currentSelection, parentStripe);
                 break;
             case "range":
                 rangeSelect(currentSelection, parentStripe);
                 break;
             default:
                 defaultSelect(currentSelection, parentStripe);
                 break;
         }
 
         updateStructure(currentSelection, parentStripe.getStructure());
         dataEditor.getPaginationPanel().preparePaginationSelectionSelectedItems();
     }
 
     private void defaultSelect(GalleryMediaContent currentSelection, GalleryStripe parentStripe) {
         if (Objects.isNull(currentSelection)) {
             return;
         }
 
         dataEditor.getSelectedMedia().clear();
         dataEditor.getSelectedMedia().add(
             new ImmutablePair<>(currentSelection.getView().getPhysicalDivision(), parentStripe.getStructure()));
     }
 
     private void rangeSelect(GalleryMediaContent currentSelection, GalleryStripe parentStripe) {
         if (Objects.isNull(currentSelection)) {
             return;
         }
 
         if (dataEditor.getSelectedMedia().isEmpty()) {
             dataEditor.getSelectedMedia().add(
                 new ImmutablePair<>(currentSelection.getView().getPhysicalDivision(), parentStripe.getStructure()));
             return;
         }
 
         Pair<PhysicalDivision, LogicalDivision> firstSelectedMediaPair = dataEditor.getSelectedMedia().get(0);
         Pair<PhysicalDivision, LogicalDivision> lastSelectedMediaPair =
                 new ImmutablePair<>(currentSelection.getView().getPhysicalDivision(), parentStripe.getStructure());
 
         dataEditor.getSelectedMedia().clear();
         dataEditor.getSelectedMedia().addAll(getMediaWithinRange(firstSelectedMediaPair, lastSelectedMediaPair));
     }
 
     private void multiSelect(GalleryMediaContent currentSelection, GalleryStripe parentStripe) {
         Pair<PhysicalDivision, LogicalDivision> selectedMediaPair = new ImmutablePair<>(
                 currentSelection.getView().getPhysicalDivision(), parentStripe.getStructure());
 
         if (dataEditor.getSelectedMedia().contains(selectedMediaPair)) {
             if (dataEditor.getSelectedMedia().size() > 1) {
                 dataEditor.getSelectedMedia().remove(selectedMediaPair);
             }
         } else {
             dataEditor.getSelectedMedia().add(selectedMediaPair);
         }
     }
 
     /**
      * Get the index of this GalleryMediaContent's PhysicalDivision out of all
      * PhysicalDivisions which are assigned to more than one LogicalDivision.
      *
      * @param galleryMediaContent
      *            object to find the index for
      * @return index of the GalleryMediaContent's PhysicalDivision if present in
      *         the List of several assignments, or -1 if not present in the
      *         list.
      */
     public int getSeveralAssignmentsIndex(GalleryMediaContent galleryMediaContent) {
         if (Objects.nonNull(galleryMediaContent.getView()) && Objects.nonNull(galleryMediaContent.getView().getPhysicalDivision())) {
             return dataEditor.getStructurePanel().getSeveralAssignments().indexOf(galleryMediaContent.getView().getPhysicalDivision());
         }
         return -1;
     }
 
     /**
      * Get cachingUUID.
      *
      * @return value of cachingUUID
      */
     public String getCachingUUID() {
         return cachingUUID;
     }
 }