diff --git a/Kitodo-API/src/main/java/org/kitodo/api/dataeditor/rulesetmanagement/RulesetManagementInterface.java b/Kitodo-API/src/main/java/org/kitodo/api/dataeditor/rulesetmanagement/RulesetManagementInterface.java index 3b33dc39a49..64c7415977e 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/dataeditor/rulesetmanagement/RulesetManagementInterface.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/dataeditor/rulesetmanagement/RulesetManagementInterface.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.Optional; +import org.kitodo.api.Metadata; + /** * Interface for a service that provides access to the ruleset. * @@ -143,4 +145,20 @@ StructuralElementViewInterface getStructuralElementView(String structuralElement * @return the “always showing” value or its default value */ boolean isAlwaysShowingForKey(String keyId); + + /** + * Updates metadata during a repeated catalog import, depending on the + * reimport settings specified in the ruleset. + * + * @param division + * current division + * @param metadata + * current metadata + * @param acquisitionStage + * current acquisition stage + * @param updateItems + * items obtained from import + * @return number of added metadata items + */ + int updateMetadata(String division, Collection metadata, String acquisitionStage, Collection updateItems); } diff --git a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/ReimportMetadata.java b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/ReimportMetadata.java new file mode 100644 index 00000000000..e1599c843c3 --- /dev/null +++ b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/ReimportMetadata.java @@ -0,0 +1,133 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.dataeditor.ruleset; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.function.Supplier; + +import org.apache.commons.lang3.ArrayUtils; +import org.kitodo.api.Metadata; +import org.kitodo.dataeditor.ruleset.xml.Reimport; + +/** + * Determines the result of re-importing metadata for a single metadata key. + */ +class ReimportMetadata implements Supplier> { + + // Metadata key for which the result of the reimport is determined. + private final String key; + + // existing metadata for this key before reimport + private final Collection currentEntries; + + // configured reimport behavior + private Reimport reimport; + + // metadata fetched on reimport + private final Collection updateEntries; + + // the maximum amount of metadata applicable here for this key + private int maxOccurs; + + /** + * Constructor. Generates a data set for the re-import of + * metadata for a specific key. + * + * @param key + * metadata key for which the result of the reimport is + * determined + */ + ReimportMetadata(String key) { + this.currentEntries = new ArrayList<>(); + this.updateEntries = new ArrayList<>(); + this.key = key; + this.maxOccurs = Integer.MAX_VALUE; + } + + /** + * Sets the repeated import behavior model for this metadata key. Must be a + * value from {@link Reimport}. + * + * @param reimport + * configured reimport behavior to set + */ + void setReimport(Reimport reimport) { + this.reimport = reimport; + } + + /** + * Sets the maximum number of metadata values allowed for this key. The + * setting only affects newly added metadata. + * + * @param maxOccurs + * maximum amount of metadata to set + */ + void setMaxOccurs(int maxOccurs) { + this.maxOccurs = maxOccurs; + } + + /** + * Returns the metadata key. The key is that of the contained metadata. + * + * @return the metadata key + */ + String getKey() { + return key; + } + + /** + * Adds a metadata entry to the current metadata entries. + * + * @param metadata + * metadata to add + */ + void addToCurrentEntries(Metadata metadata) { + assert Objects.equals(metadata.getKey(), key) : "keys should match"; + currentEntries.add(metadata); + } + + /** + * Adds a metadata entry to the update metadata entries. + * + * @param metadata + * metadata to add + */ + void addToUpdateEntries(Metadata metadata) { + assert Objects.equals(metadata.getKey(), key) : "keys should match"; + updateEntries.add(metadata); + } + + /** + * Merges the metadata of this key in the given behavior, respecting the + * maximum number. + * + * @return the metadata remaining after the repeated import + */ + @Override + public Collection get() { + if (!ArrayUtils.contains(Reimport.values(), reimport)) { + throw new IllegalStateException("Used not supported reimport case ".concat(Objects.toString(reimport))); + } + Collection result = reimport.equals(Reimport.REPLACE) && !updateEntries.isEmpty() ? new ArrayList<>() + : new ArrayList<>(currentEntries); + if (!reimport.equals(Reimport.KEEP) || result.isEmpty()) { + for (Metadata metadata : updateEntries) { + if (!result.contains(metadata) && result.size() < maxOccurs) { + result.add(metadata); + } + } + } + return result; + } +} diff --git a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/RulesetManagement.java b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/RulesetManagement.java index 90c799167ae..31afb58a4a3 100644 --- a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/RulesetManagement.java +++ b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/RulesetManagement.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale.LanguageRange; import java.util.Map; @@ -27,8 +28,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.kitodo.api.Metadata; import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalDivision; import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalMetadata; +import org.kitodo.api.dataeditor.rulesetmanagement.MetadataViewInterface; import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface; import org.kitodo.api.dataeditor.rulesetmanagement.StructuralElementViewInterface; import org.kitodo.dataeditor.ruleset.xml.AcquisitionStage; @@ -48,6 +51,14 @@ public class RulesetManagement implements RulesetManagementInterface { */ private static final Logger logger = LogManager.getLogger(RulesetManagement.class); + /** + * English, the only language understood by the System user. This value is + * passed when a method requests a language of the user in order to display + * labels in this language, but the labels are not required from the result + * and the language passed is therefore irrelevant at this point. + */ + private static final List ENGLISH = LanguageRange.parse("en"); + /** * The ruleset. */ @@ -321,5 +332,43 @@ public boolean isAlwaysShowingForKey(String keyId) { return false; } + @Override + public int updateMetadata(String division, Collection currentMetadata, String acquisitionStage, + Collection updateMetadata) { + + Settings settings = ruleset.getSettings(acquisitionStage); + Collection allowedMetadata = getStructuralElementView(division, acquisitionStage, + ENGLISH).getAllowedMetadata(); + Collection metadataForReimport = createListOfMetadataToMerge(currentMetadata, settings, + allowedMetadata, updateMetadata); + + int sizeBefore = currentMetadata.size(); + currentMetadata.clear(); + for (ReimportMetadata metadataInReimport : metadataForReimport) { + currentMetadata.addAll(metadataInReimport.get()); + } + return currentMetadata.size() - sizeBefore; + } + private Collection createListOfMetadataToMerge(Collection currentMetadata, + Settings settings, Collection allowedMetadata, Collection updateMetadata) { + HashMap unifying = new HashMap<>(); + for (Metadata metadata : currentMetadata) { + unifying.computeIfAbsent(metadata.getKey(), ReimportMetadata::new).addToCurrentEntries(metadata); + } + for (Metadata metadata : updateMetadata) { + unifying.computeIfAbsent(metadata.getKey(), ReimportMetadata::new).addToUpdateEntries(metadata); + } + Collection result = unifying.values(); + for (ReimportMetadata entry : result) { + entry.setReimport(settings.getReimport(entry.getKey())); + } + for (MetadataViewInterface metadataView : allowedMetadata) { + ReimportMetadata metadataToMerge = unifying.get(metadataView.getId()); + if (Objects.nonNull(metadataToMerge)) { + metadataToMerge.setMaxOccurs(metadataView.getMaxOccurs()); + } + } + return result; + } } diff --git a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/Settings.java b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/Settings.java index 9d13437de52..461682863b2 100644 --- a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/Settings.java +++ b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/Settings.java @@ -17,10 +17,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import org.kitodo.dataeditor.ruleset.xml.Reimport; import org.kitodo.dataeditor.ruleset.xml.Setting; /** @@ -44,6 +46,24 @@ public Settings(Collection baseSettings) { .collect(Collectors.toMap(Setting::getKey, Function.identity())); } + /** + * Returns the reimport setting for a key. + * + * @param keyId + * key for which the query is + * @return reimport setting + */ + Reimport getReimport(String keyId) { + Setting settingForKey = currentSettings.get(keyId); + if (Objects.nonNull(settingForKey)) { + Reimport reimport = settingForKey.getReimport(); + if (Objects.nonNull(reimport)) { + return reimport; + } + } + return Reimport.REPLACE; + } + /** * Returns the settings for a key. * @@ -157,6 +177,7 @@ private List merge(Collection currentSettings, Collection + * + * 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.dataeditor.ruleset.xml; + +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlEnumValue; + +import org.apache.commons.lang3.ArrayUtils; + +/** + * This class is a backing bean for the XML attribute reimport in the ruleset. + * With it, JAXB can map the attribute to an enum. + */ +@XmlEnum() +public enum Reimport { + /** + * The metadata should be added. + */ + @XmlEnumValue("add") + ADD, + + /** + * The existing metadata should be kept. + */ + @XmlEnumValue("keep") + KEEP, + + /** + * The existing metadata should be replaced. + */ + @XmlEnumValue("replace") + REPLACE; +} diff --git a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/xml/Setting.java b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/xml/Setting.java index e7cdd33aa6e..3d2874c7177 100644 --- a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/xml/Setting.java +++ b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/ruleset/xml/Setting.java @@ -73,6 +73,12 @@ public class Setting { @XmlAttribute private Boolean multiline; + /** + * This will specify how to update metadata in repeated imports. + */ + @XmlAttribute + private Reimport reimport; + /** * The settings for sub-keys. */ @@ -136,6 +142,17 @@ public Boolean getMultiline() { return multiline; } + /** + * Returns the value “reimport” if one is set. This getter returns + * {@code null} if the attribute was not entered. This is needed, for + * example, when merging attributes. + * + * @return the value “excluded”, if set, else {@code null} + */ + public Reimport getReimport() { + return reimport; + } + /** * Returns the editor settings. * @@ -239,6 +256,17 @@ public void setMultiline(Boolean multiline) { this.multiline = multiline; } + /** + * This sets the “reimport” value. If you set the value to {@code null}, no + * attribute is written. + * + * @param reimport + * “reimport” value to set + */ + public void setReimport(Reimport reimport) { + this.reimport = reimport; + } + /** * Sets the settings for nested keys. * diff --git a/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/ruleset/RulesetManagementIT.java b/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/ruleset/RulesetManagementIT.java index b1779318fd5..fd7402da1ef 100644 --- a/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/ruleset/RulesetManagementIT.java +++ b/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/ruleset/RulesetManagementIT.java @@ -12,6 +12,7 @@ package org.kitodo.dataeditor.ruleset; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; @@ -31,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale.LanguageRange; @@ -40,6 +42,7 @@ import org.apache.commons.lang3.StringUtils; import org.junit.Test; +import org.kitodo.api.MdSec; import org.kitodo.api.Metadata; import org.kitodo.api.MetadataEntry; import org.kitodo.api.dataeditor.rulesetmanagement.ComplexMetadataViewInterface; @@ -1066,6 +1069,114 @@ public void testValidationByCodomain() throws IOException { Collections.emptyList())); } + @Test + public void testReimportOfMetadataModesCreate() throws Exception { + RulesetManagement underTest = new RulesetManagement(); + underTest.load(new File("src/test/resources/testReimportOfMetadataModes.xml")); + + Collection metadata = new HashSet<>(); + metadata.add(newMetadataEntry("metadataToReplaceByDefault", "value to replace")); + metadata.add(newMetadataEntry("metadataToReplaceExplicitly", "value to replace")); + metadata.add(newMetadataEntry("metadataToAdd", "value 1")); + metadata.add(newMetadataEntry("metadataToAddWithLimit", "value 1")); + metadata.add(newMetadataEntry("metadataToAddDuringCreationAndKeepLater", "value 1")); + metadata.add(newMetadataEntry("metadataToKeep", "value not to replace")); + metadata.add(newMetadataEntry("metadataToKeepExceptInEditing", "value not to replace")); + + Collection imported = new HashSet<>(); + imported.add(newMetadataEntry("metadataToReplaceByDefault", "replaced value")); // 0 + imported.add(newMetadataEntry("metadataToReplaceExplicitly", "replaced value")); // 0 + imported.add(newMetadataEntry("metadataToAdd", "value 1")); // 0 + imported.add(newMetadataEntry("metadataToAdd", "value 2")); // 1 + imported.add(newMetadataEntry("metadataToAdd", "value 3")); // 1 + imported.add(newMetadataEntry("metadataToAddWithLimit", "value 1")); // 0 + imported.add(newMetadataEntry("metadataToAddWithLimit", "value 2")); // 0.5 + imported.add(newMetadataEntry("metadataToAddWithLimit", "value 3")); // 0.5 + imported.add(newMetadataEntry("metadataToAddDuringCreationAndKeepLater", "value 2")); // 1 + imported.add(newMetadataEntry("metadataToKeep", "value must not appear in result")); // 0 + imported.add(newMetadataEntry("metadataToKeepExceptInEditing", "value not to replace")); // 0 + imported.add(newMetadataEntry("metadataThatIsNew", "new value")); // 1 + + int numAdded = underTest.updateMetadata("division", metadata, "create", imported); + assertEquals(5, numAdded); + + List defaultReplace = metadata.stream() + .filter(element -> element.getKey().equals("metadataToReplaceByDefault")).collect(Collectors.toList()); + assertEquals(1, defaultReplace.size()); + assertEquals("replaced value", ((MetadataEntry) defaultReplace.get(0)).getValue()); + + List explicitReplace = metadata.stream() + .filter(element -> element.getKey().equals("metadataToReplaceExplicitly")).collect(Collectors.toList()); + assertEquals(1, explicitReplace.size()); + assertEquals("replaced value", ((MetadataEntry) explicitReplace.get(0)).getValue()); + + List add = metadata.stream().filter(element -> element.getKey().equals("metadataToAdd")) + .collect(Collectors.toList()); + assertEquals(3, add.size()); + assertThat( + add.stream().map(MetadataEntry.class::cast).map(MetadataEntry::getValue).collect(Collectors.toList()), + containsInAnyOrder("value 1", "value 2", "value 3")); + + List limitedAdd = metadata.stream().filter(element -> element.getKey().equals("metadataToAddWithLimit")) + .collect(Collectors.toList()); + assertEquals(2, limitedAdd.size()); + assertThat( + limitedAdd.stream().map(MetadataEntry.class::cast).map(MetadataEntry::getValue).collect(Collectors.toList()), + anyOf(containsInAnyOrder("value 1", "value 2"), containsInAnyOrder("value 1", "value 3"))); + + List addCreate = metadata.stream() + .filter(element -> element.getKey().equals("metadataToAddDuringCreationAndKeepLater")) + .collect(Collectors.toList()); + assertEquals(2, addCreate.size()); + assertThat( + addCreate.stream().map(MetadataEntry.class::cast).map(MetadataEntry::getValue).collect(Collectors.toList()), + containsInAnyOrder("value 1", "value 2")); + + List keep = metadata.stream().filter(element -> element.getKey().equals("metadataToKeep")) + .collect(Collectors.toList()); + assertEquals(1, keep.size()); + assertEquals("value not to replace", ((MetadataEntry) keep.get(0)).getValue()); + + List keepNoEdit = metadata.stream() + .filter(element -> element.getKey().equals("metadataToKeepExceptInEditing")) + .collect(Collectors.toList()); + assertEquals(1, keepNoEdit.size()); + assertEquals("value not to replace", ((MetadataEntry) keepNoEdit.get(0)).getValue()); + } + + @Test + public void testReimportOfMetadataModesEdit() throws Exception { + RulesetManagement underTest = new RulesetManagement(); + underTest.load(new File("src/test/resources/testReimportOfMetadataModes.xml")); + + Collection metadata = new HashSet<>(); + metadata.add(newMetadataEntry("metadataToAddDuringCreationAndKeepLater", "value 1")); + metadata.add(newMetadataEntry("metadataToAddDuringCreationAndKeepLater", "value 2")); + metadata.add(newMetadataEntry("metadataToKeepExceptInEditing", "value 1")); + metadata.add(newMetadataEntry("metadataToKeepExceptInEditing", "value 2")); + + Collection imported = new HashSet<>(); + imported.add(newMetadataEntry("metadataToAddDuringCreationAndKeepLater", "value not to replace")); // 0 + imported.add(newMetadataEntry("metadataToKeepExceptInEditing", "replaced value")); // -1 + + int numAdded = underTest.updateMetadata("division", metadata, "edit", imported); + assertEquals(-1, numAdded); + + List keepLater = metadata.stream() + .filter(element -> element.getKey().equals("metadataToAddDuringCreationAndKeepLater")) + .collect(Collectors.toList()); + assertEquals(2, keepLater.size()); + assertThat( + keepLater.stream().map(MetadataEntry.class::cast).map(MetadataEntry::getValue).collect(Collectors.toList()), + containsInAnyOrder("value 1", "value 2")); + + List replaceInEdit = metadata.stream() + .filter(element -> element.getKey().equals("metadataToKeepExceptInEditing")) + .collect(Collectors.toList()); + assertEquals(1, replaceInEdit.size()); + assertEquals("replaced value", ((MetadataEntry) replaceInEdit.get(0)).getValue()); + } + /** * The method provides a simple access to a metadata key in a list of * MetadataViewWithValuesInterface. @@ -1128,4 +1239,12 @@ private List ids(List metadataViewWithV .map(metadataViewWithValuesInterface -> metadataViewWithValuesInterface.getMetadata().get().getId()) .collect(Collectors.toList()); } + + private MetadataEntry newMetadataEntry(String key, String value) { + MetadataEntry newMetadataEntry = new MetadataEntry(); + newMetadataEntry.setKey(key); + newMetadataEntry.setValue(value); + newMetadataEntry.setDomain(MdSec.DMD_SEC); + return newMetadataEntry; + } } diff --git a/Kitodo-DataEditor/src/test/resources/ruleset.xsd b/Kitodo-DataEditor/src/test/resources/ruleset.xsd index b102eb5eba7..fa6c923eddf 100644 --- a/Kitodo-DataEditor/src/test/resources/ruleset.xsd +++ b/Kitodo-DataEditor/src/test/resources/ruleset.xsd @@ -49,9 +49,13 @@ URI; if the type is specified, this applies. If a namespace has been specified, Production looks for an XML file in the same directory whose file name is the same as the last segment of the namespace URI and, if it finds it, makes the namespace elements available as a selection list. + + The attribute "minDigits" can be used with type="integer" to define a minimum number of digits, which + will save the value with leading zeroes, if it has less digits. + @@ -265,6 +269,22 @@ + + + + The attribute "reimport" indicates how the metadata entry should behave when imported repeatedly: + 'add' adds the newly imported metadata entry to the existing entries. + 'replace' replaces an existing metadata entry with the new value. (This is the default case.) + 'keep' keeps the first existing value and discards the additionally imported value. + + + + + + + + + @@ -297,7 +317,8 @@ A <ruleset> technically describes the description of digital objects in structure and the assignment of metadata. The assumed default language of <label>s without the "lang" attribute is - English, unless another language is specified as default with the "lang" attribute here. Section + English, unless another language is specified as default with the "lang" attribute here. + <include>s allow to preload embedded ruleset files. Section <declaration> defines the possible <division>s and metadata <key>s. In section <restriction>, the possible combinations of the former can be restricted. In section <editing>, settings for displaying the input fields in the metadata editor can be made. @@ -305,6 +326,7 @@ + @@ -341,6 +363,7 @@ + @@ -399,7 +422,7 @@ - The attribute "unspecified" inicates whether the divisions, keys or options not mentioned in a + The attribute "unspecified" indicates whether the divisions, keys or options not mentioned in a <restriction> or <permit> are 'forbidden' and may not be offered, or whether they are 'unrestricted', and are to be arranged afterwards to explicitly named entries. The latter allows the few regularly used entries of a large set of values, for example a namespace, to be placed at the top @@ -420,6 +443,8 @@ 'authorLastName' The value is used as the author's last name to form the author-title key. + 'childCount' will set the (1-based) child number, when creating child documents. + 'dataSource' Internal identifier of the data source in order to be able to update the imported metadata entries from the data source later. @@ -442,6 +467,7 @@ + diff --git a/Kitodo-DataEditor/src/test/resources/testReimportOfMetadataModes.xml b/Kitodo-DataEditor/src/test/resources/testReimportOfMetadataModes.xml new file mode 100644 index 00000000000..a7d7e4f0691 --- /dev/null +++ b/Kitodo-DataEditor/src/test/resources/testReimportOfMetadataModes.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java b/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java index 12749bfa3ea..735ce9695d7 100644 --- a/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java +++ b/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -350,7 +351,7 @@ private List createMetadataElements(Namespace xmlns, DocketData docketD HashMap names = getNamespacesFromConfig(); Namespace[] namespaces = new Namespace[names.size()]; int index = 0; - for (var entries = names.entrySet().iterator(); entries.hasNext(); index++) { + for (Iterator> entries = names.entrySet().iterator(); entries.hasNext(); index++) { Entry entry = entries.next(); namespaces[index] = Namespace.getNamespace(entry.getKey(), entry.getValue()); } diff --git a/Kitodo/rulesets/ruleset.xsd b/Kitodo/rulesets/ruleset.xsd index 173239dccf7..1105604adc0 100644 --- a/Kitodo/rulesets/ruleset.xsd +++ b/Kitodo/rulesets/ruleset.xsd @@ -269,6 +269,26 @@ + + + + The attribute "reimport" indicates how the metadata entry should behave when imported repeatedly: + + 'add' adds the newly imported metadata entry to the existing entries. + + 'replace' replaces an existing metadata entry with the new value. (This is the default case.) + Note that metadata, for which there is no value, is kept and not deleted. + + 'keep' keeps the first existing value and discards the additionally imported value. + + + + + + + + + @@ -347,6 +367,7 @@ + @@ -405,7 +426,7 @@ - The attribute "unspecified" inicates whether the divisions, keys or options not mentioned in a + The attribute "unspecified" indicates whether the divisions, keys or options not mentioned in a <restriction> or <permit> are 'forbidden' and may not be offered, or whether they are 'unrestricted', and are to be arranged afterwards to explicitly named entries. The latter allows the few regularly used entries of a large set of values, for example a namespace, to be placed at the top diff --git a/Kitodo/rulesets/subhh.xml b/Kitodo/rulesets/subhh.xml index 154bdd4c551..7eb5ce2694d 100644 --- a/Kitodo/rulesets/subhh.xml +++ b/Kitodo/rulesets/subhh.xml @@ -1170,6 +1170,25 @@ selection list is displayed, otherwise a multi-line text field. + - optional attribute 'reimport': + Defines the behaviour on repeated imports. Defaults to + 'replace'. Possible values: + + add Add the additionally imported + metadata to the existing ones. + + keep Only import a new value if there + isn’t an existing value. If there + is, keep the existing value(s) and + doesn’t change it. + + replace Remove any existing values, and + replace them with the newly + imported values. + + Note that 'reimport' settings only affect the top level + metadata and have no effect on sub-keys. + - optional member tag: setting (optional, repeatable) To define settings on sub-keys of @@ -1178,6 +1197,10 @@ At least one of the optional attributes should be assigned or at least one child setting tag should be present. --> + + + + diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/MetadataImportDialog.java b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/MetadataImportDialog.java index a2fdcd91234..aeacaa04a2b 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/MetadataImportDialog.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/MetadataImportDialog.java @@ -122,7 +122,9 @@ public List getImportConfigurations() { * @param processes * The linked list of TempProcess instances */ - void extendsMetadataTableOfMetadataTab(LinkedList processes) { + void extendsMetadataTableOfMetadataTab(LinkedList processes) + throws InvalidMetadataValueException, NoSuchMetadataFieldException { + int countOfAddedMetadata = 0; if (processes.size() > 0) { TempProcess process = processes.getFirst(); diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/ProcessFieldedMetadata.java b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/ProcessFieldedMetadata.java index 4f7ab861461..09cff4f5468 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/ProcessFieldedMetadata.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/ProcessFieldedMetadata.java @@ -41,6 +41,7 @@ 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.RulesetManagementInterface; import org.kitodo.api.dataeditor.rulesetmanagement.SimpleMetadataViewInterface; import org.kitodo.api.dataeditor.rulesetmanagement.StructuralElementViewInterface; import org.kitodo.api.dataformat.Division; @@ -93,6 +94,11 @@ public class ProcessFieldedMetadata extends ProcessDetail implements Serializabl */ private ComplexMetadataViewInterface metadataView; + /** + * To access the ruleset functions. + */ + private RulesetManagementInterface rulesetService; + /** * The tree node that JSF has to display. */ @@ -110,16 +116,21 @@ public ProcessFieldedMetadata() { } /** - * Creates a new root metadata group representing the metadata table - * content in the processMetadata. + * 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 + * @param rulesetService + * the ruleset used for displaying */ - public ProcessFieldedMetadata(Division structure, StructuralElementViewInterface divisionView) { + public ProcessFieldedMetadata(Division structure, StructuralElementViewInterface divisionView, + RulesetManagementInterface rulesetService) { + this(null, structure, divisionView, null, null, structure.getMetadata()); + this.rulesetService = rulesetService; buildTreeNodeAndCreateMetadataTable(); } @@ -130,27 +141,13 @@ public ProcessFieldedMetadata(Division structure, StructuralElementViewInterf * metadata to add if not exist * @return returns count of added metadata */ - public int addMetadataIfNotExists(Collection potentialMetadataItems) { - Collection 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; + public int addMetadataIfNotExists(Collection potentialMetadataItems) + throws InvalidMetadataValueException, NoSuchMetadataFieldException { + preserve(); + int count = rulesetService.updateMetadata(division.getType(), metadata, "create", potentialMetadataItems); buildTreeNodeAndCreateMetadataTable(); - - overwriteTreeNodes(editedTreeNode.getChildren(), treeNode.getChildren()); - - return metadataToAdd.size(); + return count; } private void buildTreeNodeAndCreateMetadataTable() { @@ -159,29 +156,6 @@ private void buildTreeNodeAndCreateMetadataTable() { 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 source, List target) { - int index = 0; - for (TreeNode targetNode : target) { - ProcessDetail row = (ProcessDetail) targetNode.getData(); - Optional 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. * @@ -616,7 +590,7 @@ public Collection getMetadata(boolean skipEmpty) throws InvalidMetadat } else { result.setMetadata(new HashSet<>(DataEditorService.getExistingMetadataRows(treeNode.getChildren()))); } - return Collections.singletonList(result); + return result.getMetadata().isEmpty() ? Collections.emptyList() : Collections.singletonList(result); } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/MetadataPanel.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/MetadataPanel.java index b6c2f371bdf..b9bbf5b2214 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/MetadataPanel.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/MetadataPanel.java @@ -178,7 +178,7 @@ void showPhysical(Optional optionalPhysicalDivision) { private ProcessFieldedMetadata createProcessFieldedMetadata(Division structure) { StructuralElementViewInterface divisionView = dataEditorForm.getRulesetManagement().getStructuralElementView( structure.getType(), dataEditorForm.getAcquisitionStage(), dataEditorForm.getPriorityList()); - return new ProcessFieldedMetadata(structure, divisionView); + return new ProcessFieldedMetadata(structure, divisionView, dataEditorForm.getRulesetManagement()); } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/ProcessHelper.java b/Kitodo/src/main/java/org/kitodo/production/helper/ProcessHelper.java index f30b3f1a3de..4359f604171 100644 --- a/Kitodo/src/main/java/org/kitodo/production/helper/ProcessHelper.java +++ b/Kitodo/src/main/java/org/kitodo/production/helper/ProcessHelper.java @@ -99,7 +99,7 @@ public static ProcessFieldedMetadata initializeProcessDetails(LogicalDivision st RulesetManagementInterface managementInterface, String stage, List priorityList) { StructuralElementViewInterface divisionView = managementInterface.getStructuralElementView(structure.getType(), stage, priorityList); - return new ProcessFieldedMetadata(structure, divisionView); + return new ProcessFieldedMetadata(structure, divisionView, managementInterface); } /** diff --git a/Kitodo/src/test/java/org/kitodo/DummyRulesetManagement.java b/Kitodo/src/test/java/org/kitodo/DummyRulesetManagement.java index fd4cdce3ac2..87f6003eb9d 100644 --- a/Kitodo/src/test/java/org/kitodo/DummyRulesetManagement.java +++ b/Kitodo/src/test/java/org/kitodo/DummyRulesetManagement.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; +import org.kitodo.api.Metadata; import org.kitodo.api.dataeditor.rulesetmanagement.ComplexMetadataViewInterface; import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalDivision; import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalMetadata; @@ -77,4 +78,10 @@ public void load(File rulesetFile) throws IOException { public boolean isAlwaysShowingForKey(String keyId) { throw new UnsupportedOperationException(); } + + @Override + public int updateMetadata(String division, Collection metadata, String acquisitionStage, + Collection updateItems) { + throw new UnsupportedOperationException(); + } } diff --git a/Kitodo/src/test/java/org/kitodo/production/forms/createprocess/ProcessDetailIT.java b/Kitodo/src/test/java/org/kitodo/production/forms/createprocess/ProcessDetailIT.java index 2a176dfeb1d..179e528d633 100644 --- a/Kitodo/src/test/java/org/kitodo/production/forms/createprocess/ProcessDetailIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/forms/createprocess/ProcessDetailIT.java @@ -39,7 +39,7 @@ public void shouldCopyProcessDetail() throws Exception { titleDocMain.setKey("TitleDocMain"); titleDocMain.setValue("Lorem Ipsum"); division.getMetadata().add(titleDocMain); - ProcessFieldedMetadata processFieldedMetadata = new ProcessFieldedMetadata(division, divisionView); + ProcessFieldedMetadata processFieldedMetadata = new ProcessFieldedMetadata(division, divisionView, ruleset); TreeNode treeNode = processFieldedMetadata.getTreeNode(); ProcessDetail processDetail = (ProcessDetail) treeNode.getChildren().get(0).getData(); int beforeCopying = treeNode.getChildCount(); diff --git a/Kitodo/src/test/java/org/kitodo/production/process/ProcessValidatorIT.java b/Kitodo/src/test/java/org/kitodo/production/process/ProcessValidatorIT.java index 147ec62d0c1..c114f30d2b2 100644 --- a/Kitodo/src/test/java/org/kitodo/production/process/ProcessValidatorIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/process/ProcessValidatorIT.java @@ -114,7 +114,8 @@ private List createProcessDetailsList() throws IOException { rulesetManagementInterface.load(new File("src/test/resources/rulesets/monograph.xml")); StructuralElementViewInterface monograph = rulesetManagementInterface.getStructuralElementView( "Monograph", "", Locale.LanguageRange.parse("en")); - ProcessFieldedMetadata processDetails = new ProcessFieldedMetadata(workpiece.getLogicalStructure(), monograph); + ProcessFieldedMetadata processDetails = new ProcessFieldedMetadata(workpiece.getLogicalStructure(), monograph, + rulesetManagementInterface); for (ProcessDetail detail : processDetails.getRows()) { switch (detail.getMetadataID()) { case "TitleDocMain": diff --git a/Kitodo/src/test/java/org/kitodo/production/process/TitleGeneratorTest.java b/Kitodo/src/test/java/org/kitodo/production/process/TitleGeneratorTest.java index d5db365ed82..c0257f7d82c 100644 --- a/Kitodo/src/test/java/org/kitodo/production/process/TitleGeneratorTest.java +++ b/Kitodo/src/test/java/org/kitodo/production/process/TitleGeneratorTest.java @@ -106,7 +106,8 @@ static List createProcessDetailsList() throws IOException { rulesetManagementInterface.load(new File("src/test/resources/rulesets/monograph.xml")); StructuralElementViewInterface monograph = rulesetManagementInterface.getStructuralElementView( "Monograph", "", Locale.LanguageRange.parse("en")); - ProcessFieldedMetadata processDetails = new ProcessFieldedMetadata(workpiece.getLogicalStructure(), monograph); + ProcessFieldedMetadata processDetails = new ProcessFieldedMetadata(workpiece.getLogicalStructure(), monograph, + rulesetManagementInterface); for (ProcessDetail detail : processDetails.getRows()) { switch (detail.getMetadataID()) { case "TitleDocMain":