Coverage Summary for Class: ProcessFieldedMetadata (org.kitodo.production.forms.createprocess)

Class Method, % Line, %
ProcessFieldedMetadata 60% (27/45) 53,5% (154/288)
ProcessFieldedMetadata$1 100% (1/1) 100% (1/1)
Total 60,9% (28/46) 53,6% (155/289)


 /*
  * (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.createprocess;
 
 import java.io.Serializable;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 import org.apache.commons.collections4.list.UnmodifiableList;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.kitodo.api.Metadata;
 import org.kitodo.api.MetadataEntry;
 import org.kitodo.api.MetadataGroup;
 import org.kitodo.api.dataeditor.rulesetmanagement.ComplexMetadataViewInterface;
 import org.kitodo.api.dataeditor.rulesetmanagement.Domain;
 import org.kitodo.api.dataeditor.rulesetmanagement.InputType;
 import org.kitodo.api.dataeditor.rulesetmanagement.MetadataViewInterface;
 import org.kitodo.api.dataeditor.rulesetmanagement.MetadataViewWithValuesInterface;
 import org.kitodo.api.dataeditor.rulesetmanagement.SimpleMetadataViewInterface;
 import org.kitodo.api.dataeditor.rulesetmanagement.StructuralElementViewInterface;
 import org.kitodo.api.dataformat.Division;
 import org.kitodo.exceptions.InvalidMetadataValueException;
 import org.kitodo.exceptions.NoSuchMetadataFieldException;
 import org.kitodo.production.services.dataeditor.DataEditorService;
 import org.primefaces.model.DefaultTreeNode;
 import org.primefaces.model.TreeNode;
 
 public class ProcessFieldedMetadata extends ProcessDetail implements Serializable {
     private static final Logger logger = LogManager.getLogger(ProcessFieldedMetadata.class);
 
     /**
      * An empty metadata group for the empty metadata panel showing. The empty metadata panel can be displayed if the
      * element selected in the structure window isn’t a structure (has no metadata).
      */
     public static final ProcessFieldedMetadata EMPTY = new ProcessFieldedMetadata();
     public static final String METADATA_KEY_LABEL = "LABEL";
     public static final String METADATA_KEY_ORDERLABEL = "ORDERLABEL";
 
     /**
      * Fields the user has selected to show in addition, with no data yet.
      */
     private final Collection<String> additionallySelectedFields = new ArrayList<>();
 
     private boolean copy;
 
     /**
      * The division this panel is related to, if it isn’t a sub-panel.
      */
     private Division<?> division;
 
     /**
      * Metadata which is excluded by the rule set.
      */
     private Collection<Metadata> hiddenMetadata;
 
     /**
      * The metadata object with the content of this panel.
      */
     private final HashSet<Metadata> metadata;
 
     /**
      * The key of the metadata group displaying here.
      */
     private String metadataKey;
 
     /**
      * The definition of this panel in the rule set.
      */
     private ComplexMetadataViewInterface metadataView;
 
     /**
      * The tree node that JSF has to display.
      */
     protected TreeNode treeNode;
 
     /**
      * Creates an empty metadata group.
      */
     public ProcessFieldedMetadata() {
         super(null, null);
         this.treeNode = new DefaultTreeNode();
         treeNode.setExpanded(true);
         this.metadata = new HashSet<>();
         this.hiddenMetadata = Collections.emptyList();
     }
 
     /**
      * Creates a new root metadata group representing the metadata table
      * content in the processMetadata.
      *
      * @param structure
      *            structure selected by the user
      * @param divisionView
      *            information about that structure from the rule set
      */
     public ProcessFieldedMetadata(Division<?> structure, StructuralElementViewInterface divisionView) {
         this(null, structure, divisionView, null, null, structure.getMetadata());
         buildTreeNodeAndCreateMetadataTable();
     }
 
     /**
      * Add the metadata of this fielded metadata.
      *
      * @param potentialMetadataItems
      *            metadata to add if not exist
      * @return returns count of added metadata
      */
     public int addMetadataIfNotExists(Collection<Metadata> potentialMetadataItems) {
         Collection<Metadata> metadataToAdd = new ArrayList<>();
         potentialMetadataItems.forEach( potentialMetadataItem -> {
             if (metadata.stream().noneMatch(item -> item.getKey().equals(potentialMetadataItem.getKey()))) {
                 if (!(METADATA_KEY_LABEL.equals(potentialMetadataItem.getKey()) && StringUtils.isNotEmpty(
                         division.getLabel()) || METADATA_KEY_ORDERLABEL.equals(
                         potentialMetadataItem.getKey()) && StringUtils.isNotEmpty(division.getOrderlabel()))) {
                     metadataToAdd.add(potentialMetadataItem);
                 }
 
             }
         });
         metadata.addAll(metadataToAdd);
 
         TreeNode editedTreeNode = treeNode;
 
         buildTreeNodeAndCreateMetadataTable();
 
         overwriteTreeNodes(editedTreeNode.getChildren(), treeNode.getChildren());
 
         return metadataToAdd.size();
     }
 
     private void buildTreeNodeAndCreateMetadataTable() {
         treeNode = new DefaultTreeNode();
         treeNode.setExpanded(true);
         createMetadataTable();
     }
 
     /**
      * Overwrites the target list with source list of tree nodes based on the
      * metadata id.
      *
      * @param source
      *            The list of source tree nodes
      * @param target
      *            The list of target tree nodes
      */
     private static void overwriteTreeNodes(List<TreeNode> source, List<TreeNode> target) {
         int index = 0;
         for (TreeNode targetNode : target) {
             ProcessDetail row = (ProcessDetail) targetNode.getData();
             Optional<TreeNode> treeNodeOptional = source.stream().filter(
                 sourceNode -> ((ProcessDetail) sourceNode.getData()).getMetadataID().equals(row.getMetadataID()))
                     .findFirst();
             if (treeNodeOptional.isPresent()) {
                 target.set(index, treeNodeOptional.get());
             }
             index++;
         }
     }
 
     /**
      * Creates a sub-panel for a metadata group.
      *
      * @param metadataView
      *            information about that group from the rule set
      * @param metadata
      *            data of the group, may be empty but must be modifiable
      */
     private ProcessFieldedMetadata(ProcessFieldedMetadata parent, ComplexMetadataViewInterface metadataView,
             HashSet<Metadata> metadata) {
         this(parent, null, metadataView, metadataView.getLabel(), metadataView.getId(), metadata);
     }
 
     /**
      * Creates a new fielded metadata. This constructor is called from one of
      * the above ones and does the work.
      *
      * @param structure
      *            structure selected by the user, null in case of a sub-panel
      * @param metadataView
      *            information about that structure or group from the rule set
      * @param metadata
      *            metadata, may be empty but must be modifiable
      */
     private ProcessFieldedMetadata(ProcessFieldedMetadata parent, Division<?> structure,
             ComplexMetadataViewInterface metadataView, String label, String metadataKey,
                                    HashSet<Metadata> metadata) {
         super(parent, label);
         this.division = structure;
         this.metadata = metadata;
         this.metadataView = metadataView;
         this.metadataKey = metadataKey;
     }
 
     private ProcessFieldedMetadata(ProcessFieldedMetadata parent, MetadataGroup group) {
         this(parent, null, null, group.getKey(), group.getKey(), group.getMetadata());
     }
 
     private ProcessFieldedMetadata(ProcessFieldedMetadata template) {
         this(template.container, null, template.metadataView, template.label, template.metadataKey,
                 new HashSet<>(template.metadata));
         copy = true;
         hiddenMetadata = template.hiddenMetadata;
         treeNode = new DefaultTreeNode(this, template.getTreeNode().getParent());
         createMetadataTable();
         treeNode.setExpanded(true);
     }
 
     /**
      * The method for building the metadata table.
      */
     private void createMetadataTable() {
         // the existing metadata is passed to the rule set, which sorts it
         Collection<Metadata> entered = addLabels(new HashSet<>(metadata));
         if (Objects.nonNull(treeNode) && !treeNode.getChildren().isEmpty()) {
             try {
                 entered = entered.stream().filter(metadataElem -> !(metadataElem instanceof MetadataGroup))
                         .collect(Collectors.toSet());
                 entered.addAll(DataEditorService.getExistingMetadataRows(treeNode.getChildren()));
             } catch (InvalidMetadataValueException e) {
                 logger.error(e.getLocalizedMessage());
             }
         }
         List<MetadataViewWithValuesInterface> tableData = metadataView.getSortedVisibleMetadata(entered, additionallySelectedFields);
         treeNode.getChildren().clear();
         hiddenMetadata = Collections.emptyList();
         for (MetadataViewWithValuesInterface rowData : tableData) {
             Optional<MetadataViewInterface> optionalMetadataView = rowData.getMetadata();
             Collection<Metadata> values = rowData.getValues();
             if (optionalMetadataView.isPresent()) {
                 MetadataViewInterface metadataView = optionalMetadataView.get();
                 if (metadataView.isComplex()) {
                     createMetadataGroupPanel((ComplexMetadataViewInterface) metadataView, values);
                 } else {
                     if (!createMetadataEntryEdit((SimpleMetadataViewInterface) metadataView, values)) {
                         /*
                          * If a conditional metadata was set automatically,
                          * start over. This is necessary to update dependent
                          * fields above or below.
                          */
                         logger.debug("Metadata was changed. Restarting.");
                         createMetadataTable();
                         break;
                     }
                 }
             } else {
                 hiddenMetadata = values;
             }
         }
     }
 
     /**
      * The method for building the metadata table if the group and thus
      * everything in it is undefined in the ruleset.
      */
     private void createUndefinedMetadataTable() {
         treeNode.getChildren().clear();
         hiddenMetadata = Collections.emptyList();
         for (Metadata entry : metadata) {
             createMetadataEntryEdit(null, Collections.singletonList(entry));
         }
     }
 
     /**
      * Reads the labels from the structure (if any) and adds them to the
      * returned metadata collection.
      *
      * @param metadata
      *            available metadata
      * @return metadata with labels, if any
      */
     private Collection<Metadata> addLabels(Collection<Metadata> metadata) {
         Collection<Metadata> displayMetadata = metadata;
         if (Objects.nonNull(division)) {
             displayMetadata = new HashSet<>(metadata);
             for (URI contentId : division.getContentIds()) {
                 MetadataEntry contentIdEntry = new MetadataEntry();
                 contentIdEntry.setKey("CONTENTIDS");
                 contentIdEntry.setValue(contentId.toString());
                 displayMetadata.add(contentIdEntry);
             }
             if (Objects.nonNull(division.getLabel())) {
                 MetadataEntry label = new MetadataEntry();
                 label.setKey(METADATA_KEY_LABEL);
                 label.setValue(division.getLabel());
                 displayMetadata.add(label);
             }
             if (Objects.nonNull(division.getOrderlabel())) {
                 MetadataEntry label = new MetadataEntry();
                 label.setKey(METADATA_KEY_ORDERLABEL);
                 label.setValue(division.getOrderlabel());
                 displayMetadata.add(label);
             }
         }
         return displayMetadata;
     }
 
     /**
      * Creates an object to represent a metadata group. This is done by
      * creating a {@code FieldedMetadataGroup} recursively.
      *
      * @param complexMetadataView
      *            information about that group from the rule set
      * @param values
      *            data for that group, must contain at most one element
      */
     public void createMetadataGroupPanel(ComplexMetadataViewInterface complexMetadataView,
                                                            Collection<Metadata> values) {
         HashSet<Metadata> value;
 
         switch (values.size()) {
             case 0:
                 value = new HashSet<>();
                 break;
             case 1:
                 Metadata nextMetadata = values.iterator().next();
                 if (nextMetadata instanceof MetadataGroup) {
                     MetadataGroup metadataGroup = (MetadataGroup) nextMetadata;
                     value = metadataGroup.getMetadata();
                 } else {
                     throw new IllegalStateException("Got simple metadata entry with key \"" + nextMetadata.getKey()
                             + "\" which is declared as substructured key in the rule set.");
                 }
                 break;
             default:
                 throw new IllegalStateException("Too many (" + values.size() + ") complex metadata of type \""
                         + metadataKey + "\" in a single row. Must be 0 or 1 per row.");
         }
         ProcessFieldedMetadata metadata = new ProcessFieldedMetadata(this, complexMetadataView, value);
         metadata.treeNode = new DefaultTreeNode(metadata, treeNode);
         metadata.createMetadataTable();
         metadata.treeNode.setExpanded(true);
     }
 
     /**
      * Creates an object to represent a single-row metadata input.
      *
      * @param simpleMetadataView
      *            presentation information about the metadata entry from the
      *            ruleset
      * @param values
      *            the value(s) to be displayed
      * @return whether the input field could be generated. If an automatic
      *         preset has been added to the metadata, false is returned. In that
      *         case, the rule set must be invoked again to update the options
      *         for dependent fields.
      */
     public boolean createMetadataEntryEdit(SimpleMetadataViewInterface simpleMetadataView,
                                                  Collection<Metadata> values) {
 
         ProcessDetail data;
         try {
             InputType inputType = Objects.isNull(simpleMetadataView) ? InputType.ONE_LINE_TEXT
                     : simpleMetadataView.getInputType();
             switch (inputType) {
                 case MULTIPLE_SELECTION:
                 case MULTI_LINE_SINGLE_SELECTION:
                 case ONE_LINE_SINGLE_SELECTION:
                     List<Map<MetadataEntry, Boolean>> leadingFields = getListForLeadingMetadataFields();
                     Map<String, String> options = simpleMetadataView.getSelectItems(leadingFields);
                     boolean dependent = leadingFields.parallelStream().flatMap(map -> map.entrySet().parallelStream())
                             .anyMatch(entry -> Boolean.TRUE.equals(entry.getValue()));
                     if (dependent && !options.isEmpty() && addAutoPresetForConditionalMetadata(simpleMetadataView, options, values)) {
                         return false;
                     }
                     data = new ProcessSelectMetadata(this, simpleMetadataView, simpleValues(values), dependent);
                     break;
                 case BOOLEAN:
                     data = new ProcessBooleanMetadata(this, simpleMetadataView, oneValue(values, MetadataEntry.class));
                     break;
                 case DATE:
                     data = new ProcessDateMetadata(this, simpleMetadataView, oneValue(values, MetadataEntry.class));
                     break;
                 case INTEGER:
                 case MULTI_LINE_TEXT:
                 case ONE_LINE_TEXT:
                     data = new ProcessTextMetadata(this, simpleMetadataView, oneValue(values, MetadataEntry.class));
                     break;
                 default:
                     throw new IllegalStateException("complete switch");
             }
             new DefaultTreeNode(data, treeNode).setExpanded(true);
         } catch (IllegalStateException e) {
             logger.catching(Level.WARN, e);
             ProcessFieldedMetadata metadata = new ProcessFieldedMetadata(this, oneValue(values, MetadataGroup.class));
             metadata.treeNode = new DefaultTreeNode(metadata, treeNode);
             metadata.createUndefinedMetadataTable();
             metadata.treeNode.setExpanded(true);
         }
         return true;
     }
 
     /**
      * A value must be set for conditional metadata, as there is no no-selection
      * option. In this case, the first possible value is already displayed as
      * selected, even if it is not yet reflected in the metadata before saving.
      * In this case, no options are offered in selection fields that depend on
      * this field, because their options are based on the metadata. Therefore,
      * in this case, the metadata must be set explicitly. Then, a termination
      * must take place and the rule set must be invoked again in order to
      * correctly populate the dependent fields with options.
      *
      * @param view
      *            view that gives the access the select items
      * @param metadataForInput
      *            list of metadata, to which a preset will be added if necessary
      * @return true, if the current tree building process must be restarted,
      *         because the metadata was changed
      */
     private boolean addAutoPresetForConditionalMetadata(SimpleMetadataViewInterface view, Map<String, String> options,
             Collection<Metadata> metadataForInput) {
 
         if (metadataForInput.isEmpty()) {
             MetadataEntry autoPreset = new MetadataEntry();
             autoPreset.setKey(view.getId());
             if (Objects.isNull(container)) {
                 autoPreset.setDomain(DOMAIN_TO_MDSEC.get(view.getDomain().orElse(Domain.DESCRIPTION)));
             }
             autoPreset.setValue(options.entrySet().iterator().next().getKey());
             metadata.add(autoPreset);
 
             logger.debug("Added metadata {} to {}", autoPreset, metadataKey);
             return true;
         }
         return false;
     }
 
     /**
      * Returns the collection of simple metadata entries. Throws an
      * IllegalStateException if a cannot be casted.
      *
      * @param values
      *            values obtained
      * @return a collection of simple metadata entries
      */
     @SuppressWarnings({"unchecked", "rawtypes" })
     private Collection<MetadataEntry> simpleValues(Collection<Metadata> values) {
         Optional<Metadata> fault = values.parallelStream().filter(entry -> !(entry instanceof MetadataEntry)).findAny();
         if (fault.isPresent()) {
             throw new IllegalStateException("Got complex metadata entry with key \"" + fault.get().getKey()
                     + "\" which isn't declared as substructured key in the rule set.");
         }
         return (Collection) values;
     }
 
     /**
      * Returns the only metadata entry or null. Throws an IllegalStateException
      * if the value is ambiguous or cannot be cast.
      *
      * @param <T>
      *
      * @param values
      *            values obtained
      * @return the only entry or null
      */
     @SuppressWarnings("unchecked")
     private <T extends Metadata> T oneValue(Collection<Metadata> values, Class<T> subclass) {
         switch (values.size()) {
             case 0:
                 return null;
             case 1:
                 Metadata nextMetadata = values.iterator().next();
                 if (subclass.isAssignableFrom(nextMetadata.getClass())) {
                     return (T) nextMetadata;
                 } else {
                     throw new IllegalStateException("Got complex metadata entry with key \"" + nextMetadata.getKey()
                             + "\" which isn't declared as substructured key in the rule set.");
                 }
             default:
                 throw new IllegalStateException("Too many (" + values.size() + ") metadata of type \""
                         + values.iterator().next().getKey() + "\" in a single row. Must be 0 or 1 per row.");
         }
     }
 
     /**
      * Adds an additionally selected field.
      *
      * @param additionallySelectedField
      *            additionally selected field to add
      * @throws NoSuchMetadataFieldException
      *             if the method has to save the entries in order to rebuild the
      *             display with the new field, but when saving an attempt is
      *             made to write a non-existing &lt;mets:div> attribute
      */
     public void addAdditionallySelectedField(String additionallySelectedField) throws NoSuchMetadataFieldException {
         additionallySelectedFields.add(additionallySelectedField);
         try {
             preserve();
         } catch (InvalidMetadataValueException e) {
             logger.info(e.getLocalizedMessage(), e);
         }
         createMetadataTable();
     }
 
     /**
      * Duplicates a process detail.
      *
      * @param processDetail
      *            process detail to copy
      */
     public void copy(ProcessDetail processDetail) {
         if (Objects.isNull(division)) {
             container.copy(processDetail);
         } else {
             searchRecursiveAndCopy(treeNode, processDetail);
         }
     }
 
     private static boolean searchRecursiveAndCopy(TreeNode treeNode, ProcessDetail processDetail) {
         List<TreeNode> children = treeNode.getChildren();
         for (int index = 0; index < children.size(); index++) {
             TreeNode child = children.get(index);
             Object childData = child.getData();
             if (Objects.equals(childData, processDetail)) {
                 TreeNode copy = null;
                 if (childData instanceof ProcessSimpleMetadata) {
                     ProcessSimpleMetadata copyData = ((ProcessSimpleMetadata) childData).getClone();
                     copy = new DefaultTreeNode(copyData, treeNode);
                     copy.setExpanded(child.isExpanded());
                 } else if (childData instanceof ProcessFieldedMetadata) {
                     ProcessFieldedMetadata copyData = new ProcessFieldedMetadata((ProcessFieldedMetadata) childData);
                     copy = copyData.treeNode;
                 }
                 treeNode.getChildren().add(index + 1, copy);
             } else if (searchRecursiveAndCopy(child, processDetail)) {
                 return true;
             }
         }
         return false;
     }
 
     /**
      * Returns the division.
      * @return the division
      */
     public Division<?> getDivision() {
         return division;
     }
 
     @Override
     public String getMetadataID() {
         return metadataKey;
     }
 
     @Override
     public String getInput() {
         return "dataTable";
     }
 
     List<Map<MetadataEntry, Boolean>> getListForLeadingMetadataFields() {
         List<Map<MetadataEntry, Boolean>> result = Objects.isNull(container) ? new ArrayList<>()
                 : container.getListForLeadingMetadataFields();
         Map<String, MetadataEntry> metadataEntryMap = new HashMap<>();
         treeNode.getChildren().stream().map(TreeNode::getData).filter(ProcessSimpleMetadata.class::isInstance)
                 .map(ProcessSimpleMetadata.class::cast).map(ProcessDetail::getMetadataID).forEachOrdered(key -> {
                     MetadataEntry metadataEntry = new MetadataEntry();
                     metadataEntry.setKey(key);
                     metadataEntryMap.put(key, metadataEntry);
                 });
         metadata.stream().filter(MetadataEntry.class::isInstance).map(MetadataEntry.class::cast)
                 .forEachOrdered(key -> metadataEntryMap.put(key.getKey(), key));
         result.add(metadataEntryMap.entrySet().stream()
             .collect(Collectors.toMap(Entry::getValue, all -> Boolean.FALSE)));
         return result;
     }
 
     /**
      * Returns the metadata of a metadata group, when used recursively.
      *
      * @return the metadata of the metadata group
      * @throws InvalidMetadataValueException
      *             if some value is invalid
      */
     @Override
     public Collection<Metadata> getMetadataWithFilledValues() throws InvalidMetadataValueException {
         return getMetadata(true);
     }
 
     @Override
     public Collection<Metadata> getMetadata(boolean skipEmpty) throws InvalidMetadataValueException {
         assert division == null;
         MetadataGroup result = new MetadataGroup();
         result.setKey(metadataKey);
         result.setDomain(DOMAIN_TO_MDSEC.get(metadataView.getDomain().orElse(Domain.DESCRIPTION)));
         try {
             this.preserve();
         } catch (NoSuchMetadataFieldException e) {
             throw new IllegalStateException("never happening exception");
         }
         if (skipEmpty) {
             result.setMetadata(metadata instanceof List ? metadata : new HashSet<>(metadata));
         } else {
             result.setMetadata(new HashSet<>(DataEditorService.getExistingMetadataRows(treeNode.getChildren())));
         }
         return Collections.singletonList(result);
     }
 
     /**
      * Return this ProcessFieldedMetadata as MetadataGroup.
      *
      * @return MetadataGroup representing this ProcessFieldedMetadata
      */
     public Collection<Metadata> getChildMetadata() {
         return metadata;
     }
 
     /**
      * Returns the rows that JSF has to display.
      *
      * @return the rows that JSF has to display
      */
     public List<ProcessDetail> getRows() {
         List<ProcessDetail> rows = new ArrayList<>();
         for (TreeNode child : treeNode.getChildren()) {
             rows.add((ProcessDetail) child.getData());
         }
         return new UnmodifiableList<>(rows);
     }
 
     @Override
     Pair<BiConsumer<Division<?>, String>, String> getStructureFieldValue() {
         return null;
     }
 
     public TreeNode getTreeNode() {
         return treeNode;
     }
 
     @Override
     public boolean isUndefined() {
         return Objects.isNull(metadataView) || metadataView.isUndefined();
     }
 
     @Override
     public boolean isRequired() {
         return Objects.nonNull(metadataView) && metadataView.getMinOccurs() > 0;
     }
 
     @Override
     public boolean isValid() {
         for (ProcessDetail row : getRows()) {
             if (!row.isValid()) {
                 return false;
             }
         }
         return true;
     }
 
     void markLeadingMetadataFields(List<Map<MetadataEntry, Boolean>> leadingMetadataFields) {
         int lastIndex = leadingMetadataFields.size() - 1;
         if (lastIndex > 0) {
             container.markLeadingMetadataFields(leadingMetadataFields.subList(0, lastIndex));
         }
         final List<String> leadingMetadataKeys = leadingMetadataFields.get(lastIndex).entrySet().parallelStream()
                 .filter(entry -> Boolean.TRUE.equals(entry.getValue())).map(entry -> entry.getKey().getKey())
                 .collect(Collectors.toList());
         treeNode.getChildren().parallelStream().map(TreeNode::getData).map(ProcessDetail.class::cast)
                 .filter(processDetail -> leadingMetadataKeys.contains(processDetail.getMetadataID()))
                 .forEach(ProcessDetail::setLeading);
     }
 
     /**
      * Reads the contents of the processMetadata and stores the values in the
      * appropriate place. If the line is used to edit a field of the METS
      * structure, this field is set, otherwise the metadata will be stored in
      * the list. The hidden metadata is also written back there again.
      *
      * @throws InvalidMetadataValueException
      *             if the content of a metadata input field is syntactically
      *             wrong
      * @throws NoSuchMetadataFieldException
      *             if an input shall be saved to a field of the structure, but
      *             there is no setter corresponding to the name configured in
      *             the rule set
      */
     public void preserve() throws InvalidMetadataValueException, NoSuchMetadataFieldException {
         try {
             if (Objects.nonNull(division)) {
                 division.getContentIds().clear();
                 division.setOrderlabel(null);
                 division.setLabel(null);
             }
             metadata.clear();
             for (TreeNode child : treeNode.getChildren()) {
                 ProcessDetail row = (ProcessDetail) child.getData();
                 Pair<BiConsumer<Division<?>, String>, String> metsFieldValue = row.getStructureFieldValue();
                 if (Objects.nonNull(metsFieldValue)) {
                     metsFieldValue.getKey().accept(division, metsFieldValue.getValue());
                 } else {
                     metadata.addAll(row.getMetadataWithFilledValues());
                 }
             }
             if (Objects.nonNull(hiddenMetadata)) {
                 metadata.addAll(hiddenMetadata);
             }
         } catch (InvalidMetadataValueException invalidValueException) {
             if (Objects.isNull(division)) {
                 invalidValueException.addParent(metadataKey);
             }
             throw invalidValueException;
         }
         if (copy) {
             MetadataGroup metadataGroup = new MetadataGroup();
             metadataGroup.setKey(metadataKey);
             Optional<Domain> optionalDomain = metadataView.getDomain();
             optionalDomain.ifPresent(domain -> metadataGroup.setDomain(DOMAIN_TO_MDSEC.get(domain)));
             metadataGroup.setMetadata(metadata);
             container.metadata.add(metadataGroup);
             copy = false;
         }
     }
 
     /**
      * Removes a process detail.
      *
      * @param toDelete
      *            process detail to delete
      */
     public void remove(ProcessDetail toDelete) throws InvalidMetadataValueException, NoSuchMetadataFieldException {
         Iterator<TreeNode> treeNodesIterator = treeNode.getChildren().iterator();
         while (treeNodesIterator.hasNext()) {
             TreeNode treeNode = treeNodesIterator.next();
             if (treeNode.getData().equals(toDelete)) {
                 treeNodesIterator.remove();
                 preserve();
                 break;
             }
         }
     }
 
     /**
      * Overwrites the metadata of this process fielded metadata.
      *
      * @param metadata
      *            metadata to overwrite with
      */
     public void setMetadata(Collection<Metadata> metadata) {
         this.metadata.clear();
         this.metadata.addAll(metadata);
         buildTreeNodeAndCreateMetadataTable();
     }
 
     /**
      * Get metadataView.
      *
      * @return value of metadataView
      */
     public ComplexMetadataViewInterface getMetadataView() {
         return metadataView;
     }
 
     /**
      * Get additionallySelectedFields.
      *
      * @return value of additionallySelectedFields
      */
     public Collection<String> getAdditionallySelectedFields() {
         return additionallySelectedFields;
     }
 
     /**
      * Get occurrences of a metadata in the treeNode.
      * @param metadataKey as String
      * @return occurrences
      */
     public int getOccurrences(String metadataKey) {
         int occ = 0;
         for (TreeNode treeNode : treeNode.getChildren()) {
             if (((ProcessDetail) treeNode.getData()).getMetadataID().equals(metadataKey)) {
                 occ++;
             }
         }
         return occ;
     }
 
     @Override
     public int getMinOccurs() {
         return metadataView.getMinOccurs();
     }
 }