diff --git a/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java b/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java
index adcaac575d..8f842ced0e 100644
--- a/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java
+++ b/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java
@@ -1025,7 +1025,7 @@ public Source resolve(String href, String base) {
}
} catch (Exception ex) {
LOGGER.info("MCRNotNullResolver caught exception: {}", ex.getLocalizedMessage());
- LOGGER.debug(ex.getStackTrace());
+ LOGGER.debug(ex);
LOGGER.debug("MCRNotNullResolver returning empty xml");
return new JDOMSource(new Element("null"));
}
diff --git a/mycore-mods/src/main/java/org/mycore/mods/merger/MCRAbstractRedundantModsEventHandler.java b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRAbstractRedundantModsEventHandler.java
new file mode 100644
index 0000000000..e5ffb6ccc2
--- /dev/null
+++ b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRAbstractRedundantModsEventHandler.java
@@ -0,0 +1,169 @@
+package org.mycore.mods.merger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdom2.Element;
+import org.mycore.common.MCRConstants;
+import org.mycore.common.events.MCREvent;
+import org.mycore.common.events.MCREventHandlerBase;
+import org.mycore.datamodel.classifications2.MCRCategoryID;
+import org.mycore.datamodel.metadata.MCRObject;
+import org.mycore.mods.MCRMODSWrapper;
+import org.mycore.mods.classification.MCRClassMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Checks for and removes redundant classifications in Mods-Documents. If a classification category and
+ * the classification's child category are both present in the document, the parent classification will
+ * be removed.
+ * This abstract class can be extended by overriding the following abstract methods:
+ *
+ *
{@link MCRAbstractRedundantModsEventHandler#isConsistent(Element, Element)}: custom checks for
+ * the two classifications' consistency.
+ *
{@link MCRAbstractRedundantModsEventHandler#getClassificationElementName()}: the name of
+ * the mods-element that the EventHandler is checking duplicates for.
+ *
+ */
+public abstract class MCRAbstractRedundantModsEventHandler extends MCREventHandlerBase {
+
+ private final Logger logger = LogManager.getLogger(getClass());
+
+ @Override
+ protected void handleObjectCreated(MCREvent evt, MCRObject obj) {
+ mergeCategories(obj);
+ }
+
+ @Override
+ protected void handleObjectUpdated(MCREvent evt, MCRObject obj) {
+ mergeCategories(obj);
+ }
+
+ @Override
+ protected void handleObjectRepaired(MCREvent evt, MCRObject obj) {
+ mergeCategories(obj);
+ }
+
+ /**
+ * Merging classifications by detaching parent-categories inside an {@link MCRObject}.
+ * Mods-element is traversed for classifications, found relatedItems are processed separately from
+ * the rest of the document.
+ * @param obj the handled object
+ */
+ protected void mergeCategories(MCRObject obj) {
+ MCRMODSWrapper mcrmodsWrapper = new MCRMODSWrapper(obj);
+ if (mcrmodsWrapper.getMODS() == null) {
+ return;
+ }
+ logger.info("merge redundant " + getClassificationElementName() + " categories for {}", obj.getId());
+
+ Element mods = mcrmodsWrapper.getMODS();
+ List supportedElements = getAllDescendants(mods).stream()
+ .filter(element -> element.getName().equals(getClassificationElementName()))
+ .filter(element -> MCRClassMapper.getCategoryID(element) != null).toList();
+ dropRedundantCategories(supportedElements);
+
+ List relatedItems = getAllRelatedItems(mods);
+ for (Element relatedItem : relatedItems) {
+ if (relatedItem.getAttribute("href", MCRConstants.XLINK_NAMESPACE) == null) {
+ dropRedundantCategories(getAllDescendants(relatedItem));
+ }
+ }
+ }
+
+ /**
+ * Recursively writes all child-elements of a given element into a list and returns the list once completed.
+ * @param element the parent element for which all children should be listed
+ * @return a list with all child-elements
+ */
+ protected static List getAllDescendants(Element element) {
+ List descendants = new ArrayList<>();
+
+ for (Element child : element.getChildren()) {
+ if (!child.getName().equals("relatedItem")) {
+ descendants.add(child);
+ descendants.addAll(getAllDescendants(child));
+ }
+ }
+ return descendants;
+ }
+
+ /**
+ * Returns all relatedItem-Elements from a mods-Element. Assumes that relatedItems are only used at top-level.
+ * @param mods The mods-Element to be searched for relatedItems
+ * @return a List of all Elements with the name "relatedItem"
+ */
+ protected static List getAllRelatedItems(Element mods) {
+ return mods.getChildren().stream().filter(child -> "relatedItem".equals(child.getName())).toList();
+ }
+
+ /**
+ * Iterates through a list of classification elements and for each element pair checks if one of the element
+ * is a parent category of the other. Calls
+ * {@link MCRAbstractRedundantModsEventHandler#isConsistent(Element, Element)} and only detaches parent element
+ * if the method returns true.
+ * @param elements a list of classification elements that are all compared to each other in pairs
+ */
+ protected void dropRedundantCategories(List elements) {
+ for (int i = 0; i < elements.size(); i++) {
+ for (int j = i + 1; j < elements.size(); j++) {
+
+ Element element1 = elements.get(i);
+ Element element2 = elements.get(j);
+ Element parentElement = MCRCategoryMerger.getElementWithParentCategory(element1, element2);
+ if (parentElement != null && isConsistent(element1, element2)) {
+ parentElement.detach();
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses the authority name from an element.
+ * @param element the element using an authority
+ * @return the String of the authority
+ */
+ protected String getAuthority(Element element) {
+ return element.getAttributeValue("authorityURI") != null ?
+ element.getAttributeValue("authorityURI") : element.getAttributeValue("authority");
+ }
+
+ /**
+ * Compares two classification elements for the same authority
+ * @param el1 first element to be compared
+ * @param el2 second element to be compared
+ * @return true if both have the same authority, or if none of them has an authority
+ */
+ protected boolean hasSameAuthority(Element el1, Element el2) {
+ return Objects.equals(el1.getAttributeValue("authorityURI"),
+ el2.getAttributeValue("authorityURI")) &&
+ Objects.equals(el1.getAttributeValue("authority"), el2.getAttributeValue("authority"));
+ }
+
+ /**
+ * Get the name of an element's classification or return value "unknown".
+ * @param element the element that contains the classification
+ * @return the name of the classification or the string "unknown"
+ */
+ protected String getClassificationName(Element element) {
+ return Optional.ofNullable(MCRClassMapper.getCategoryID(element)).map(MCRCategoryID::toString)
+ .orElse("unknown");
+ }
+
+ /**
+ * Method can be overridden to implement custom checks to two categories' consistency regarding attributes.
+ * @param element1 the first element to be compared
+ * @param element2 the first element to be compared
+ * @return will always return true
+ */
+ protected abstract boolean isConsistent(Element element1, Element element2);
+
+ /**
+ * Returns the name of the classification element that the specific EventHandler is handling.
+ * @return name of the classification element
+ */
+ protected abstract String getClassificationElementName();
+}
diff --git a/mycore-mods/src/main/java/org/mycore/mods/merger/MCRCategoryMerger.java b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRCategoryMerger.java
index 03c6bac639..a08e6307b2 100644
--- a/mycore-mods/src/main/java/org/mycore/mods/merger/MCRCategoryMerger.java
+++ b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRCategoryMerger.java
@@ -19,8 +19,11 @@
package org.mycore.mods.merger;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Optional;
+import org.jdom2.Element;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.datamodel.classifications2.MCRCategory;
import org.mycore.datamodel.classifications2.MCRCategoryDAO;
@@ -88,12 +91,39 @@ static boolean oneIsDescendantOfTheOther(MCRCategoryID idThis, MCRCategoryID idO
}
private static List getAncestorsAndSelf(MCRCategoryID categoryID) {
- List ancestorsAndSelf = new ArrayList<>(DAO.getParents(categoryID));
+ List ancestorsAndSelf = new ArrayList<>(Optional.ofNullable(DAO.getParents(categoryID)).orElse(
+ Collections.emptyList()));
ancestorsAndSelf.remove(DAO.getRootCategory(categoryID, 0));
ancestorsAndSelf.add(DAO.getCategory(categoryID, 0));
return ancestorsAndSelf;
}
+ /**
+ * Compares two {@link Element Elements} that are assumed to be categories.
+ * If it is determined that one Element is a parent category of the other, return the parent, else return null.
+ * @param element1 first Element to compare
+ * @param element2 second Element to compare
+ * @return the parent Element or null
+ */
+ public static Element getElementWithParentCategory(Element element1, Element element2) {
+ MCRCategoryID idThis = MCRClassMapper.getCategoryID(element1);
+ MCRCategoryID idOther = MCRClassMapper.getCategoryID(element2);
+ if (idThis == null || idOther == null) {
+ return null;
+ }
+
+ final String p = CONFIG_PREFIX + idThis.getRootID();
+ if (idThis.getRootID().equals(idOther.getRootID()) && !MCRConfiguration2.getBoolean(p).orElse(true)) {
+ return null;
+ }
+
+ if (idThis.equals(idOther) || !oneIsDescendantOfTheOther(idThis, idOther)) {
+ return null;
+ }
+
+ return getAncestorsAndSelf(idThis).containsAll(getAncestorsAndSelf(idOther)) ? element2 : element1;
+ }
+
@Override
public void mergeFrom(MCRMerger other) {
MCRCategoryMerger cmo = (MCRCategoryMerger) other;
diff --git a/mycore-mods/src/main/java/org/mycore/mods/merger/MCRRedundantModsClassificationEventHandler.java b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRRedundantModsClassificationEventHandler.java
new file mode 100644
index 0000000000..f4f8348d25
--- /dev/null
+++ b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRRedundantModsClassificationEventHandler.java
@@ -0,0 +1,63 @@
+package org.mycore.mods.merger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdom2.Element;
+
+import java.util.Objects;
+
+/**
+ * Checks for and removes redundant classifications in Mods-Documents. If a classification category and
+ * the classification's child category are both present in the document, the parent classification will
+ * be removed.
+ */
+public class MCRRedundantModsClassificationEventHandler extends MCRAbstractRedundantModsEventHandler {
+
+ private static final Logger LOGGER = LogManager.getLogger(MCRRedundantModsClassificationEventHandler.class);
+
+ protected static final String CLASSIFICATION_ELEMENT_NAME = "classification";
+
+ @Override
+ protected String getClassificationElementName() {
+ return CLASSIFICATION_ELEMENT_NAME;
+ }
+
+ /**
+ * Returns false if the authorities of the two classification elements are the same, but
+ * the displayLabels differ from each other.
+ * @param el1 the first element to be compared
+ * @param el2 the first element to be compared
+ * @return false if inconsistent
+ */
+ @Override
+ protected boolean isConsistent(Element el1, Element el2) {
+ return !hasSameAuthority(el1, el2) || checkDisplayLabelConsistence(el1, el2);
+ }
+
+ /**
+ * Checks if both elements have the same displayLabel. Logs a warning if not.
+ * @param el1 first element to check
+ * @param el2 second element to check
+ * @return true, if both elements have the same displayLabel (or both have none)
+ */
+ private boolean checkDisplayLabelConsistence(Element el1, Element el2) {
+ final String displayLabel1 = el1.getAttributeValue("displayLabel");
+ final String displayLabel2 = el2.getAttributeValue("displayLabel");
+
+ final String classificationName1 = getClassificationName(el1);
+ final String classificationName2 = getClassificationName(el2);
+
+ if (!Objects.equals(displayLabel1, displayLabel2)) {
+
+ String logMessage = """
+ There are inconsistencies found between the classifications {} and {}.
+ They have the same authority "{}" but {} has the displayLabel "{}" and {} has the displayLabel "{}".""";
+
+ LOGGER.warn(logMessage, classificationName1, classificationName2, getAuthority(el1),
+ classificationName1, displayLabel1, classificationName2, displayLabel2);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/mycore-mods/src/main/java/org/mycore/mods/merger/MCRRedundantModsGenreEventHandler.java b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRRedundantModsGenreEventHandler.java
new file mode 100644
index 0000000000..0d786b15f9
--- /dev/null
+++ b/mycore-mods/src/main/java/org/mycore/mods/merger/MCRRedundantModsGenreEventHandler.java
@@ -0,0 +1,90 @@
+package org.mycore.mods.merger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdom2.Element;
+
+import java.util.Objects;
+
+/**
+ * Checks for and removes redundant genres in Mods-Documents. If a genre category and
+ * the genre's child category are both present in the document, the parent genre will
+ * be removed.
+ */
+public class MCRRedundantModsGenreEventHandler extends MCRAbstractRedundantModsEventHandler {
+
+ private static final Logger LOGGER = LogManager.getLogger(MCRRedundantModsGenreEventHandler.class);
+
+ protected static final String CLASSIFICATION_ELEMENT_NAME = "genre";
+
+ @Override
+ protected String getClassificationElementName() {
+ return CLASSIFICATION_ELEMENT_NAME;
+ }
+
+ /**
+ * Returns false if the authorities of the two genre elements are the same, but
+ * the displayLabels or the types differ from each other.
+ * @param el1 the first element to be compared
+ * @param el2 the first element to be compared
+ * @return false if inconsistent
+ */
+ @Override
+ protected boolean isConsistent(Element el1, Element el2) {
+ return !hasSameAuthority(el1, el2) ||
+ (checkDisplayLabelConsistence(el1, el2) && checkTypeConsistence(el1, el2));
+ }
+
+ /**
+ * Checks if both elements have the same displayLabel. Logs a warning if not.
+ * @param el1 first element to check
+ * @param el2 second element to check
+ * @return true, if both elements have the same displayLabel (or both have none)
+ */
+ private boolean checkDisplayLabelConsistence(Element el1, Element el2) {
+ final String displayLabel1 = el1.getAttributeValue("displayLabel");
+ final String displayLabel2 = el2.getAttributeValue("displayLabel");
+
+ final String classificationName1 = getClassificationName(el1);
+ final String classificationName2 = getClassificationName(el2);
+
+ if (!Objects.equals(displayLabel1, displayLabel2)) {
+
+ String logMessage = """
+ There are inconsistencies found between the classifications {} and {}.
+ They have the same authority "{}" but {} has the displayLabel "{}" and {} has the displayLabel "{}".""";
+
+ LOGGER.warn(logMessage, classificationName1, classificationName2, getAuthority(el1),
+ classificationName1, displayLabel1, classificationName2, displayLabel2);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks if both elements have the same type. Logs a warning if not.
+ * @param el1 first element to check
+ * @param el2 second element to check
+ * @return true, if both elements have the same type (or both have none)
+ */
+ private boolean checkTypeConsistence(Element el1, Element el2) {
+ final String type1 = el1.getAttributeValue("type");
+ final String type2 = el2.getAttributeValue("type");
+
+ final String classificationName1 = getClassificationName(el1);
+ final String classificationName2 = getClassificationName(el2);
+
+ if (!Objects.equals(type1, type2)) {
+ String logMessage = """
+ There are inconsistencies found between the classifications {} and {}.
+ They have the same authority "{}" but {} has the type "{}" and {} has the type "{}".""";
+
+ LOGGER.warn(logMessage, classificationName1, classificationName2, getAuthority(el1),
+ classificationName1, type1, classificationName2, type2);
+
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/mycore-mods/src/main/resources/components/mods/config/mycore.properties b/mycore-mods/src/main/resources/components/mods/config/mycore.properties
index b95409807f..31b57f4691 100644
--- a/mycore-mods/src/main/resources/components/mods/config/mycore.properties
+++ b/mycore-mods/src/main/resources/components/mods/config/mycore.properties
@@ -1,6 +1,8 @@
MCR.Metadata.Type.mods=true
MCR.Metadata.ShareAgent.mods=org.mycore.mods.MCRMODSMetadataShareAgent
MCR.EventHandler.MCRObject.040.Class=org.mycore.mods.MCRMODSLinksEventHandler
+# MCR.EventHandler.MCRObject.016a.Class=org.mycore.mods.merger.MCRRedundantModsClassificationEventHandler
+# MCR.EventHandler.MCRObject.016b.Class=org.mycore.mods.merger.MCRRedundantModsGenreEventHandler
MCR.MODS.NewObjectType=mods
MCR.MODS.Types=mods
diff --git a/mycore-mods/src/test/java/org/mycore/mods/merger/MCRRedundantModsClassificationEventHandlerTest.java b/mycore-mods/src/test/java/org/mycore/mods/merger/MCRRedundantModsClassificationEventHandlerTest.java
new file mode 100644
index 0000000000..6e6ff0389b
--- /dev/null
+++ b/mycore-mods/src/test/java/org/mycore/mods/merger/MCRRedundantModsClassificationEventHandlerTest.java
@@ -0,0 +1,147 @@
+package org.mycore.mods.merger;
+
+import static org.junit.Assert.assertEquals;
+import static org.mycore.common.MCRConstants.MODS_NAMESPACE;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.junit.Test;
+import org.mycore.common.MCRJPATestCase;
+import org.mycore.common.content.MCRJDOMContent;
+import org.mycore.datamodel.classifications2.MCRCategoryDAO;
+import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
+import org.mycore.datamodel.classifications2.utils.MCRXMLTransformer;
+import org.mycore.datamodel.metadata.MCRObject;
+import org.mycore.mods.MCRMODSWrapper;
+import org.mycore.resource.MCRResourceHelper;
+
+import java.io.IOException;
+import java.util.List;
+
+public class MCRRedundantModsClassificationEventHandlerTest extends MCRJPATestCase {
+
+ public static final String TEST_DIRECTORY = "MCRRedundantModsClassificationEventHandlerTest/";
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ MCRCategoryDAO categoryDao = MCRCategoryDAOFactory.getInstance();
+ categoryDao.addCategory(null, MCRXMLTransformer.getCategory(loadXml("sdnb.xml")));
+ categoryDao.addCategory(null, MCRXMLTransformer.getCategory(loadXml("sdnb2.xml")));
+ categoryDao.addCategory(null, MCRXMLTransformer.getCategory(loadXml("sdnb3.xml")));
+ }
+
+ private Element loadMods(String fileName) throws Exception {
+
+ MCRObject object = new MCRObject();
+ Document modsDocument = loadXml(fileName);
+
+ MCRMODSWrapper modsWrapper = new MCRMODSWrapper(object);
+ modsWrapper.setMODS(modsDocument.getRootElement().detach());
+ modsWrapper.setID("junit", 1);
+
+ MCRRedundantModsClassificationEventHandler mergeEventHandler = new MCRRedundantModsClassificationEventHandler();
+ mergeEventHandler.handleObjectCreated(null, object);
+
+ LOGGER.info(new MCRJDOMContent(modsWrapper.getMODS()).asString());
+
+ return modsWrapper.getMODS();
+
+ }
+
+ private static Document loadXml(String fileName) throws JDOMException, IOException {
+ return new SAXBuilder().build(MCRResourceHelper.getResourceAsStream(TEST_DIRECTORY + fileName));
+ }
+
+ @Test
+ public void redundantClassificationsWithSameAuthorityAndLabelAreRemoved() throws Exception {
+ Element mods = loadMods("modsClassificationSameAuthoritySameLabel.xml");
+ List classifications = mods.getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(2, classifications.size());
+ assertEquals("foo:x-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(0)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(1)));
+ }
+
+ @Test
+ public void redundantClassificationsWithSameAuthorityAndDifferingLabelAreKept() throws Exception {
+ Element mods = loadMods("modsClassificationSameAuthorityDifferingLabel.xml");
+ List classifications = mods.getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(4, classifications.size());
+ assertEquals("foo:x", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(0)));
+ assertEquals("bar:x-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(1)));
+ assertEquals("baz:x-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(2)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(3)));
+ }
+
+ @Test
+ public void redundantClassificationsWithDifferingAuthorityAreKept() throws Exception {
+ Element mods = loadMods("modsClassificationDifferingAuthority.xml");
+ List classifications = mods.getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(4, classifications.size());
+ assertEquals("foo:x", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(0)));
+ assertEquals("foo:x-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(1)));
+ assertEquals("foo:x-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(2)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(3)));
+ }
+
+ @Test
+ public void redundantClassificationsInRelatedItemAreKept() throws Exception {
+ Element mods = loadMods("modsClassificationsInRelatedItem.xml");
+ Element relatedItem = mods.getChildren("relatedItem", MODS_NAMESPACE).getFirst();
+ List classifications = relatedItem.getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(4, classifications.size());
+ assertEquals("foo:x", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(0)));
+ assertEquals("foo:x-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(1)));
+ assertEquals("foo:x-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(2)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(3)));
+ }
+
+ @Test
+ public void redundantClassificationsInRelatedItemWithoutXlinkAreRemoved() throws Exception {
+ Element mods = loadMods("modsClassificationsInRelatedItemNoXlink.xml");
+ Element relatedItem = mods.getChildren("relatedItem", MODS_NAMESPACE).getFirst();
+ List classifications = relatedItem.getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(2, classifications.size());
+ assertEquals("foo:x-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(0)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromTextValue(classifications.get(1)));
+ }
+
+ @Test
+ public void redundantClassificationsInModsAndRelatedItemNoInteraction() throws Exception {
+ Element mods = loadMods("modsClassificationsInAndOutsideRelatedItem.xml");
+ List classificationsOutside = mods.getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(1, classificationsOutside.size());
+ assertEquals("foo:x-1", getLabelFromAttributeAndCategoryIdFromTextValue(classificationsOutside.getFirst()));
+
+ List classificationsInside = mods.getChild("relatedItem", MODS_NAMESPACE)
+ .getChildren("classification", MODS_NAMESPACE);
+
+ assertEquals(3, classificationsInside.size());
+ assertEquals("foo:x-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classificationsInside.get(0)));
+ assertEquals("foo:x-1-1-1", getLabelFromAttributeAndCategoryIdFromTextValue(classificationsInside.get(1)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromTextValue(classificationsInside.get(2)));
+ }
+
+ private String getLabelFromAttributeAndCategoryIdFromTextValue(Element element) {
+ return element.getAttributeValue("displayLabel") + ":" + element.getText().trim();
+ }
+
+ private String getCategoryIdFromValueUri(Element element) {
+ String uri = element.getAttribute("valueURI").getValue();
+ return uri.substring(uri.indexOf('#') + 1);
+ }
+
+}
diff --git a/mycore-mods/src/test/java/org/mycore/mods/merger/MCRRedundantModsGenreEventHandlerTest.java b/mycore-mods/src/test/java/org/mycore/mods/merger/MCRRedundantModsGenreEventHandlerTest.java
new file mode 100644
index 0000000000..35bf91428b
--- /dev/null
+++ b/mycore-mods/src/test/java/org/mycore/mods/merger/MCRRedundantModsGenreEventHandlerTest.java
@@ -0,0 +1,171 @@
+package org.mycore.mods.merger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.junit.Test;
+import org.mycore.common.MCRJPATestCase;
+import org.mycore.common.content.MCRJDOMContent;
+import org.mycore.datamodel.classifications2.MCRCategoryDAO;
+import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
+import org.mycore.datamodel.classifications2.utils.MCRXMLTransformer;
+import org.mycore.datamodel.metadata.MCRObject;
+import org.mycore.mods.MCRMODSWrapper;
+import org.mycore.resource.MCRResourceHelper;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mycore.common.MCRConstants.MODS_NAMESPACE;
+
+public class MCRRedundantModsGenreEventHandlerTest extends MCRJPATestCase {
+
+ public static final String TEST_DIRECTORY = "MCRRedundantModsGenreEventHandlerTest/";
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ MCRCategoryDAO categoryDao = MCRCategoryDAOFactory.getInstance();
+ categoryDao.addCategory(null, MCRXMLTransformer.getCategory(loadXml("genre.xml")));
+ categoryDao.addCategory(null, MCRXMLTransformer.getCategory(loadXml("genre2.xml")));
+ }
+
+ private Element loadMods(String fileName) throws Exception {
+
+ MCRObject object = new MCRObject();
+ Document modsDocument = loadXml(fileName);
+
+ MCRMODSWrapper modsWrapper = new MCRMODSWrapper(object);
+ modsWrapper.setMODS(modsDocument.getRootElement().detach());
+ modsWrapper.setID("junit", 1);
+
+ MCRRedundantModsGenreEventHandler mergeEventHandler = new MCRRedundantModsGenreEventHandler();
+ mergeEventHandler.handleObjectCreated(null, object);
+
+ LOGGER.info(new MCRJDOMContent(modsWrapper.getMODS()).asString());
+
+ return modsWrapper.getMODS();
+
+ }
+
+ private static Document loadXml(String fileName) throws JDOMException, IOException {
+ return new SAXBuilder().build(MCRResourceHelper.getResourceAsStream(TEST_DIRECTORY + fileName));
+ }
+
+ @Test
+ public void redundantGenresAreRemovedAscending() throws Exception {
+ Element mods = loadMods("modsGenresAscending.xml");
+ List genres = mods.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(2, genres.size());
+ assertEquals("x-1-1", getCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("y", getCategoryIdFromValueUri(genres.get(1)));
+ }
+
+ @Test
+ public void redundantGenresAreRemovedDescending() throws Exception {
+ Element mods = loadMods("modsGenresDescending.xml");
+ List genres = mods.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(2, genres.size());
+ assertEquals("y", getCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("x-1-1", getCategoryIdFromValueUri(genres.get(1)));
+ }
+
+ @Test
+ public void redundantGenresInRelatedItemAreKept() throws Exception {
+ Element mods = loadMods("modsGenresInRelatedItem.xml");
+ Element relatedItem = mods.getChildren("relatedItem", MODS_NAMESPACE).getFirst();
+ List genres = relatedItem.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(4, genres.size());
+ assertEquals("x", getCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("x-1", getCategoryIdFromValueUri(genres.get(1)));
+ assertEquals("x-1-1", getCategoryIdFromValueUri(genres.get(2)));
+ assertEquals("y", getCategoryIdFromValueUri(genres.get(3)));
+ }
+
+ @Test
+ public void redundantGenresInRelatedItemWithoutXlinkAreRemoved() throws Exception {
+ Element mods = loadMods("modsGenresInRelatedItemNoXlink.xml");
+ Element relatedItem = mods.getChildren("relatedItem", MODS_NAMESPACE).getFirst();
+ List genres = relatedItem.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(2, genres.size());
+ assertEquals("x-1-1", getCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("y", getCategoryIdFromValueUri(genres.get(1)));
+ }
+
+ @Test
+ public void redundantGenresWithSameAuthorityAndDifferingLabelAreKept() throws Exception {
+ Element mods = loadMods("modsGenreSameAuthorityDifferingLabel.xml");
+ List genres = mods.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(4, genres.size());
+ assertEquals("foo:x", getLabelFromAttributeAndCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("bar:x-1", getLabelFromAttributeAndCategoryIdFromValueUri(genres.get(1)));
+ assertEquals("baz:x-1-1", getLabelFromAttributeAndCategoryIdFromValueUri(genres.get(2)));
+ assertEquals("foo:y", getLabelFromAttributeAndCategoryIdFromValueUri(genres.get(3)));
+ }
+
+ @Test
+ public void redundantGenresWithSameAuthorityAndDifferingTypeAreKept() throws Exception {
+ Element mods = loadMods("modsGenreSameAuthorityDifferingType.xml");
+ List genres = mods.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(4, genres.size());
+ assertEquals("foo:x", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("bar:x-1", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(1)));
+ assertEquals("baz:x-1-1", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(2)));
+ assertEquals("foo:y", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(3)));
+ }
+
+ @Test
+ public void redundantGenresWithDifferingAuthorityAreKept() throws Exception {
+ Element mods = loadMods("modsGenreDifferingAuthority.xml");
+ List genres = mods.getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(3, genres.size());
+ assertEquals("intern:y", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(0)));
+ assertEquals("intern:x-1-1", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(1)));
+ assertEquals("intern:x-1", getTypeFromAttributeAndCategoryIdFromValueUri(genres.get(2)));
+ }
+
+ @Test
+ public void redundantGenresInModsAndRelatedItemNoInteraction() throws Exception {
+ Element mods = loadMods("modsGenresInAndOutsideRelatedItem.xml");
+ List genresOutside = mods.getChildren("genre", MODS_NAMESPACE);
+
+
+ assertEquals(1, genresOutside.size());
+ assertEquals("intern:x-1", getTypeFromAttributeAndCategoryIdFromValueUri(genresOutside.getFirst()));
+
+ List genresInside = mods.getChild("relatedItem", MODS_NAMESPACE)
+ .getChildren("genre", MODS_NAMESPACE);
+
+ assertEquals(2, genresInside.size());
+ assertEquals("intern:x-1-1-1", getTypeFromAttributeAndCategoryIdFromValueUri(genresInside.get(0)));
+ assertEquals("intern:y", getTypeFromAttributeAndCategoryIdFromValueUri(genresInside.get(1)));
+ }
+
+ private String getCategoryIdFromValueUri(Element element) {
+ String uri = element.getAttribute("valueURI").getValue();
+ return uri.substring(uri.indexOf('#') + 1);
+ }
+
+ private String getLabelFromAttributeAndCategoryIdFromValueUri(Element element) {
+ return element.getAttributeValue("displayLabel") + ":" + getCategoryIdFromValueUri(element);
+ }
+
+ private String getTypeFromAttributeAndCategoryIdFromValueUri(Element element) {
+ return element.getAttributeValue("type") + ":" + getCategoryIdFromValueUri(element);
+ }
+
+}
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationDifferingAuthority.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationDifferingAuthority.xml
new file mode 100644
index 0000000000..ec469e0378
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationDifferingAuthority.xml
@@ -0,0 +1,7 @@
+
+
+ x
+ x-1
+ x-1-1
+ y
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationSameAuthorityDifferingLabel.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationSameAuthorityDifferingLabel.xml
new file mode 100644
index 0000000000..3731a8bf18
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationSameAuthorityDifferingLabel.xml
@@ -0,0 +1,7 @@
+
+
+ x
+ x-1
+ x-1-1
+ y
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationSameAuthoritySameLabel.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationSameAuthoritySameLabel.xml
new file mode 100644
index 0000000000..2b3bfb605e
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationSameAuthoritySameLabel.xml
@@ -0,0 +1,7 @@
+
+
+ x
+ x-1
+ x-1-1
+ y
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInAndOutsideRelatedItem.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInAndOutsideRelatedItem.xml
new file mode 100644
index 0000000000..39837132f2
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInAndOutsideRelatedItem.xml
@@ -0,0 +1,10 @@
+
+
+ x
+ x-1
+
+ x-1-1
+ x-1-1-1
+ y
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInRelatedItem.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInRelatedItem.xml
new file mode 100644
index 0000000000..f128a0226d
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInRelatedItem.xml
@@ -0,0 +1,9 @@
+
+
+
+ x
+ x-1
+ x-1-1
+ y
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInRelatedItemNoXlink.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInRelatedItemNoXlink.xml
new file mode 100644
index 0000000000..ed201e6e56
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/modsClassificationsInRelatedItemNoXlink.xml
@@ -0,0 +1,9 @@
+
+
+
+ x
+ x-1
+ x-1-1
+ y
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb.xml
new file mode 100644
index 0000000000..fd44f35901
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb2.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb2.xml
new file mode 100644
index 0000000000..f1390452f1
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb2.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb3.xml b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb3.xml
new file mode 100644
index 0000000000..f9a34dff83
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsClassificationEventHandlerTest/sdnb3.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/genre.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/genre.xml
new file mode 100644
index 0000000000..1521c7d81a
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/genre.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/genre2.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/genre2.xml
new file mode 100644
index 0000000000..caff642253
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/genre2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreDifferingAuthority.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreDifferingAuthority.xml
new file mode 100644
index 0000000000..1096b1d8c8
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreDifferingAuthority.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreSameAuthorityDifferingLabel.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreSameAuthorityDifferingLabel.xml
new file mode 100644
index 0000000000..010b53dbfe
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreSameAuthorityDifferingLabel.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreSameAuthorityDifferingType.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreSameAuthorityDifferingType.xml
new file mode 100644
index 0000000000..1c9068cc8b
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenreSameAuthorityDifferingType.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresAscending.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresAscending.xml
new file mode 100644
index 0000000000..757d558ea9
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresAscending.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresDescending.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresDescending.xml
new file mode 100644
index 0000000000..c5bc0041b0
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresDescending.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInAndOutsideRelatedItem.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInAndOutsideRelatedItem.xml
new file mode 100644
index 0000000000..b252825783
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInAndOutsideRelatedItem.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInRelatedItem.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInRelatedItem.xml
new file mode 100644
index 0000000000..98b4fe9149
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInRelatedItem.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInRelatedItemNoXlink.xml b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInRelatedItemNoXlink.xml
new file mode 100644
index 0000000000..1179600ed6
--- /dev/null
+++ b/mycore-mods/src/test/resources/MCRRedundantModsGenreEventHandlerTest/modsGenresInRelatedItemNoXlink.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+