Die Komponente mycore-dedup erkennt mögliche Dubletten.
Eine Anwendung kann fĂĽr jeden MyCoRe-Objekttyp eigene Kriterien konfigurieren und einen eigenen
Anzeigetitel bereitstellen. Die Verwendung ist nicht auf MODS-Dokumente beschränkt -
mycore-mods bringt lediglich eine fertige Konfiguration fĂĽr MODS mit.
Die Erkennung liefert bewusst nur mögliche Dubletten. Zwei Objekte gelten als mögliche Dubletten, sobald sie mindestens ein Kriterium mit gleichem Typ und gleichem, normalisiertem Wert besitzen. Ein fachlicher Prozess oder ein Benutzer muss anschließend entscheiden, ob es sich tatsächlich um Dubletten handelt.
Die Komponente speichert die drei folgenden Angaben in der MyCoRe-Datenbank:
Beim Anlegen, Ändern und Reparieren eines MCRObject führt der MCRDeDupEventHandler alle für dessen Objekttyp
konfigurierten MCRDeDupCriterionBuilder aus. Er ersetzt die bisher gespeicherten Keys und aktualisiert den
Anzeigetitel. Beim Löschen eines Objekts entfernt er dessen Keys, Titel und alle zugehörigen
Keine-Dublette-Markierungen. Wenn fĂĽr den Objekttyp kein Builder konfiguriert ist, werden keine Keys erzeugt.
Eine Dubletten-Check vergleicht also nicht bei jeder Anfrage erneut die vollständigen Metadaten, sondern nur die vorberechneten Keys. Das macht die Abfrage schnell und erlaubt es, unterschiedliche Metadatenmodelle über dieselbe API anzusprechen. Eine mögliche Dublette wird durch jeden gemeinsamen Key einzeln ausgegeben. Teilen zwei Objekte beispielsweise sowohl einen Identifier als auch ein Titel-Autor-Kriterium, enthält das Ergebnis zwei Einträge für dieses Paar.
Nach dem erstmaligen Aktivieren der Komponente besitzen bereits vorhandene Objekte noch keine Keys. Sie mĂĽssen einmal repariert oder erneut gespeichert werden, damit der Event-Handler die Keys und Titel aufbaut.
Eine Anwendung übernimmt zunächst die Komponente als Maven-Dependency. Die Version stammt üblicherweise über die MyCoRe-BOM:
<dependency>
<groupId>org.mycore</groupId>
<artifactId>mycore-dedup</artifactId>
</dependency>
Dadurch werden die Datenbank-Konfiguration (JPA-Mapping), die Event-Handler, der URI-Resolver und die REST-Anwendung konfiguriert. Die dafĂĽr mitgelieferten Standard-Properties lauten:
MCR.DeDup.CriteriaProvider.Class=org.mycore.dedup.MCRDeDupCriteriaProvider
MCR.DeDup.KeyManager.Class=org.mycore.dedup.MCRDeDupKeyManager
MCR.DeDup.TitleProvider.Class=org.mycore.dedup.MCRDeDupTitleProvider
MCR.JPA.MappingFileNames=%MCR.JPA.MappingFileNames%,META-INF/mycore-dedup-mappings.xml
MCR.EventHandler.MCRObject.015D.Class=org.mycore.dedup.MCRDeDupNoDuplicateFlagEventHandler
MCR.EventHandler.MCRObject.085.Class=org.mycore.dedup.MCRDeDupEventHandler
MCR.DeDup.NoDuplicateFlagType=dedupNoDuplicate
MCR.URIResolver.ModuleResolver.dedup.Class=org.mycore.dedup.MCRDeDupURIResolver
MCR.URIResolver.ModuleResolver.dedup.SessionKeyPrefixes=editor_
MCR.DeDup.API.Resource.Packages=org.mycore.dedup.resources
Die Reihenfolge der Event-Handler ist relevant: 015D wertet temporäre Keine-Dublette-Flags aus, bevor der reguläre
Handler 020 das Objekt schreibt. 085 baut anschlieĂźend die Such-Keys auf.
Für eine optionale Administrationsoberfläche kann zusätzlich die Maven-Dependency org.mycore:mycore-dedup-gui eingebunden werden. Sie ist
unter /dedup/ erreichbar und zeigt mögliche Dubletten sowie Keine-Dublette-Markierungen an.
FĂĽr einen eigenen Objekttyp sind mindestens ein MCRDeDupCriterionBuilder und dessen Konfiguration notwendig. Ein
MCRDeDupTitleResolver ist optional, aber für verständliche REST- und GUI-Ausgaben empfehlenswert.
Angenommen, die Anwendung verwendet den MyCoRe-Objekt-Typ book und speichert ISBNs in Elementen namens isbn.
Dann kann ein einfacher Builder so aussehen:
package org.example.book.dedup;
import java.util.LinkedHashSet;
import java.util.Set;
import org.jdom2.Element;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathFactory;
import org.mycore.datamodel.metadata.MCRObject;
import org.mycore.dedup.MCRDeDupCriterion;
import org.mycore.dedup.MCRDeDupCriterionBuilder;
public class BookISBNCriterionBuilder implements MCRDeDupCriterionBuilder {
@Override
public Set<MCRDeDupCriterion> build(MCRObject object) {
if (!"book".equals(object.getId().getTypeId())) {
return Set.of();
}
Set<MCRDeDupCriterion> result = new LinkedHashSet<>();
Element metadata = object.getMetadata().createXML();
var isbnElements = XPathFactory.instance()
.compile(".//isbn", Filters.element())
.evaluate(metadata);
for (Element isbnElement : isbnElements) {
String isbn = isbnElement.getTextTrim().replaceAll("[-\\s]", "");
if (!isbn.isEmpty()) {
result.add(MCRDeDupCriterion.of("identifier", "isbn:" + isbn));
}
}
return result;
}
}
Der Builder wird unter der tatsächlichen Typ-ID (hier: book) aus der MCRObjectID registriert:
MCR.DeDup.CriterionBuilder.book.isbn.Class=org.example.book.dedup.BookISBNCriterionBuilder
Das Segment isbn ist nur ein frei wählbarer, innerhalb des Objekttyps eindeutiger Name. Mehrere Builder lassen sich
parallel konfigurieren:
MCR.DeDup.CriterionBuilder.book.isbn.Class=org.example.book.dedup.BookISBNCriterionBuilder
MCR.DeDup.CriterionBuilder.book.titleAuthor.Class=org.example.book.dedup.BookTitleAuthorCriterionBuilder
MCR.DeDup.CriterionBuilder.book.shelfmark.Class=org.example.book.dedup.BookShelfmarkCriterionBuilder
Alle zurĂĽckgegebenen Ergebnisse werden vereinigt. Ein einzelner gemeinsamer Wert genĂĽgt fĂĽr einen Treffer; die verschiedenen
Builder bilden also standardmäßig eine ODER-Verknüpfung. Anwendungen, die nur eine bestimmte Kombination vergleichen
wollen, können diese Felder in einem normalisierten Kriterium zusammenfassen, beispielsweise
MCRDeDupCriterion.of("title-author", normalizedTitle + ':' + normalizedAuthor).
Bei selbst erstellten Kriterien sollte Folgendes beachtet werden:
identifier und shelfmark erzeugt keinen Treffer.Der Titel wird zusammen mit den Keys gespeichert und muss daher nicht fĂĽr jede Zeile einer Trefferliste erneut aus den
Metadaten geladen werden. Eine Implementierung gibt Optional.empty() zurĂĽck, wenn sie keinen Titel ermitteln kann:
package org.example.book.dedup;
import java.util.Optional;
import org.jdom2.Element;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathFactory;
import org.mycore.datamodel.metadata.MCRObject;
import org.mycore.dedup.MCRDeDupTitleResolver;
public class BookTitleResolver implements MCRDeDupTitleResolver {
@Override
public Optional<String> resolveTitle(MCRObject object) {
if (!"book".equals(object.getId().getTypeId())) {
return Optional.empty();
}
Element metadata = object.getMetadata().createXML();
Element title = XPathFactory.instance()
.compile(".//title", Filters.element())
.evaluateFirst(metadata);
return title == null || title.getTextTrim().isEmpty()
? Optional.empty()
: Optional.of(title.getTextTrim());
}
}
MCR.DeDup.TitleResolver.book.Class=org.example.book.dedup.BookTitleResolver
Alternativ kann org.mycore.dedup.MCRDeDupLabelTitleResolver verwendet werden, wenn das Label des MCRObject als
Anzeige ausreicht. Titel werden auf 1024 Zeichen gekürzt. Änderungen am Resolver werden für vorhandene Objekte erst
nach einer Reparatur oder dem nächsten Speichern sichtbar.
mycore-mods registriert drei Builder fĂĽr Identifier, die Kombination aus Titel und Autor sowie Signaturen:
MCR.DeDup.CriterionBuilder.mods.identifier.Class=org.mycore.mods.dedup.MCRMODSIdentifierDeDupCriterionBuilder
MCR.DeDup.CriterionBuilder.mods.titleAuthor.Class=org.mycore.mods.dedup.MCRMODSTitleAuthorDeDupCriterionBuilder
MCR.DeDup.CriterionBuilder.mods.shelfmark.Class=org.mycore.mods.dedup.MCRMODSShelfmarkDeDupCriterionBuilder
MCR.DeDup.TitleResolver.mods.Class=org.mycore.mods.dedup.MCRMODSDeDupTitleResolver
Einzelne Strategien können in der Anwendung durch Ăśberschreiben oder Entfernen (“Leer-Setzen”) des jeweiligen Properties angepasst werden.
Der MCRDeDupURIResolver stellt Treffer als XML zur weiteren Verarbeitung in XSLT-Stylesheets bereit. Er unterstĂĽtzt zwei Aktionen.
dedup:duplicates:{objectId} sucht anhand der bereits gespeicherten Keys eines Objekts:
<xsl:variable name="duplicates" select="document('dedup:duplicates:app_book_00000001')/duplicates"/>
Das Ergebnis gruppiert alle gemeinsamen Kriterien nach dem jeweils anderen Objekt:
<duplicates for="app_book_00000001">
<duplicate id="app_book_00000002">
<criterion type="identifier" value="isbn:9781566199094"/>
<criterion type="title-author" value="beispieltitel:mustermann"/>
</duplicate>
</duplicates>
Bereits als Keine-Dublette markierte Paare werden nicht ausgegeben. Eine ungĂĽltige Objekt-ID oder eine unbekannte
Aktion fĂĽhrt zu einem TransformerException.
dedup:duplicates-for-session:{sessionKey} ist für ein Objekt gedacht, das während der Bearbeitung noch nicht
gespeichert wurde. Der Resolver liest das JDOM-Metadatenelement aus der aktuellen MyCoRe-Session, erzeugt daraus ein
MCRObject, berechnet dessen Kriterien direkt und vergleicht diese mit den gespeicherten Keys:
<xsl:variable name="duplicates"
select="document('dedup:duplicates-for-session:editor_book')/duplicates"/>
Aus Sicherheitsgründen darf der Key für Session-Objekte nur mit einem konfigurierten Präfix beginnen. Mehrere Präfixe werden durch Kommas getrennt:
MCR.URIResolver.ModuleResolver.dedup.SessionKeyPrefixes=editor_,myBookEditor_
Der zugehörige Wert des Session-Objektes muss ein org.jdom2.Element mit dem XML des MCRObject sein. Das Ergebnis hat dasselbe
Format wie oben; das for-Attribut enthält hierbei den Session-Key. Da das neue Objekt noch keine endgültige ID hat,
kann diese Abfrage keine bereits gespeicherte Keine-Dublette-Markierung fĂĽr das neue Objekt berĂĽcksichtigen.
Die JSON-API liegt unter {Webanwendung}/api/dedup. Alle Endpunkte setzen das MyCoRe-Recht
manage-deduplication voraus; ohne dieses Recht antwortet die API mit HTTP 403. Leseantworten werden nicht gecacht.
| Methode | Pfad | Bedeutung |
|---|---|---|
GET |
/api/dedup/duplicates |
Alle möglichen Dublettenpaare auflisten |
GET |
/api/dedup/duplicates/{id} |
Mögliche Dubletten eines Objekts auflisten |
GET |
/api/dedup/no-duplicates |
Alle Keine-Dublette-Markierungen auflisten |
POST |
/api/dedup/no-duplicates?id1={id1}&id2={id2} |
Ein Paar als Keine-Dublette markieren |
DELETE |
/api/dedup/no-duplicates/{markingId} |
Eine Markierung anhand ihrer Datenbank-ID entfernen |
Eine Trefferantwort enthält einen Eintrag je gemeinsamem Kriterium. Die Objekt-IDs eines Paars werden unabhängig von der Abfragerichtung lexikographisch sortiert:
[
{
"objectId1": "app_book_00000001",
"title1": "Ein Beispieltitel",
"objectId2": "app_book_00000002",
"title2": "Ein Beispieltitel, zweite Ausgabe",
"criterionType": "identifier",
"criterionValue": "isbn:9781566199094"
}
]
Fehlt ein gespeicherter Anzeigetitel, ist das entsprechende Feld null. Das Markieren eines Paars liefert HTTP 201;
wiederholtes Markieren desselben ungeordneten Paars ist wirkungsgleich. Identische oder ungĂĽltige Objekt-IDs fĂĽhren zu
HTTP 400. Das Löschen liefert HTTP 204 und ist auch dann erfolgreich, wenn die angegebene Markierung nicht existiert.
Eine Keine-Dublette-Markierung unterdrückt das Paar vollständig, auch wenn es mehrere Kriterien gemeinsam hat. Die
Antwort von GET /api/dedup/no-duplicates enthält zusätzlich die ID der Markierung, den erzeugenden Benutzer und den
Zeitpunkt (im Format ISO-8601):
[
{
"id": 17,
"objectId1": "app_book_00000001",
"title1": "Ein Beispieltitel",
"objectId2": "app_book_00000002",
"title2": "Ein anderer Titel",
"creator": "administrator",
"created": "2026-08-18T09:30:00Z"
}
]
Bei einem neuen Objekt steht die endgültige Objekt-ID während der Editorprüfung unter Umständen noch nicht fest. Die Entscheidung kann deshalb vor dem Speichern als Service-Flag am neuen Objekt mitgegeben werden:
object.getService().addFlag("dedupNoDuplicate", "app_book_00000002");
Der Flag-Typ ist mit MCR.DeDup.NoDuplicateFlagType konfigurierbar. Für jedes bestätigte Vergleichsobjekt wird ein
eigenes Flag gesetzt. Der erste Event-Handler wandelt gültige Flags beim Anlegen oder Ändern in dauerhafte
Keine-Dublette-Markierungen um und entfernt die Flags vor dem Speichern aus den Metadaten. UngĂĽltige IDs,
Selbstreferenzen und doppelte Werte werden ignoriert.