Skip to content

Commit 255549b

Browse files
kosarkomilanmajchrak
authored andcommitted
UFAL/fix: DOI Organizer creates duplicate dc.identifier.doi metadata (ufal#1368) (#1350)
(cherry picked from commit e9392ae on dtq-dev) v9 adaptations: - DOIIdentifierProviderTest: the two new tests' 'new Date().getTime()' rewritten to 'Instant.now().toEpochMilli()' — v9-base's Date->Instant migration removed the java.util.Date import, so the clean cherry-pick would not compile (known tripwire, sync plan card e9392ae). Fulfils CLARIN_V9_POST_SNAPSHOT_SYNC_ACCEPTANCE.md §5 / e9392ae (BE-1, Vlna 1).
1 parent e738a32 commit 255549b

4 files changed

Lines changed: 119 additions & 6 deletions

File tree

dspace-api/src/main/java/org/dspace/ctask/general/ItemMetadataQAChecker.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public void init(Curator curator, String taskId) throws IOException {
8787
"dc.rights.label",
8888
"dc.date.available",
8989
"dc.source.uri",
90+
"dc.identifier.doi",
9091
"metashare.ResourceInfo#DistributionInfo#LicenseInfo.license"
9192
});
9293
strangeMetadata = configurationService.getArrayProperty("lr.curation.metadata.strange", new String[]{

dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,13 +1069,26 @@ protected void saveDOIToObject(Context context, DSpaceObject dso, String doi)
10691069
}
10701070
Item item = (Item) dso;
10711071

1072-
itemService.addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null,
1073-
doiService.DOIToExternalForm(doi));
1074-
try {
1075-
itemService.update(context, item);
1076-
} catch (SQLException | AuthorizeException ex) {
1077-
throw ex;
1072+
String doiURL = doiService.DOIToExternalForm(doi);
1073+
1074+
// Add the DOI to the metadata only if this exact value is not present yet. This keeps the operation
1075+
// idempotent (re-registration does not create duplicate values) without ever deleting metadata: a
1076+
// pre-existing, different DOI is left untouched. This method is called after the DOI has already been
1077+
// registered with the external agency, so destroying metadata here would be lossy and irreversible.
1078+
// Items that end up with more than one dc.identifier.doi value are surfaced by the ItemMetadataQAChecker
1079+
// curation task for manual review.
1080+
List<MetadataValue> existing = itemService.getMetadata(item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, Item.ANY);
1081+
boolean alreadyPresent = existing.stream()
1082+
.anyMatch(metadataValue -> doiURL.equals(metadataValue.getValue()));
1083+
1084+
if (alreadyPresent) {
1085+
log.debug("The DOI {} is already part of the metadata of Item {}. Not adding it again.",
1086+
doi, item.getID());
1087+
return;
10781088
}
1089+
1090+
itemService.addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, doiURL);
1091+
itemService.update(context, item);
10791092
}
10801093

10811094
/**

dspace-api/src/test/java/org/dspace/curate/ItemMetadataQACheckerIT.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public class ItemMetadataQACheckerIT extends AbstractIntegrationTestWithDatabase
6363
Item itemWithIncorrectLanguageName;
6464
Item itemWithTwoAvailableDates;
6565
Item itemWithTwoAvailableDatesAndLang;
66+
Item itemWithTwoDois;
6667
Item itemVersion1;
6768
Item itemVersion2;
6869
Item itemVersion3;
@@ -146,6 +147,13 @@ public void setUp() throws Exception {
146147
itemService.addMetadata(context, itemWithTwoAvailableDatesAndLang,"dc", "date",
147148
"available", "en_US", "2021-01-01");
148149

150+
itemWithTwoDois = ItemBuilder.createItem(context, collection)
151+
.withTitle("Item With Two DOIs")
152+
.withMetadata("dc", "type", null, "corpus")
153+
.withMetadata("dc", "identifier", "doi", "https://doi.org/10.5072/test-1")
154+
.withMetadata("dc", "identifier", "doi", "https://doi.org/10.5072/test-2")
155+
.build();
156+
149157
itemVersion1 = ItemBuilder.createItem(context, collection)
150158
.withTitle("Item Version 1")
151159
.withMetadata("dc", "type", null, "corpus")
@@ -233,6 +241,20 @@ public void testItemWithTwoAvailableDatesAndLang() throws IOException {
233241
assertTrue("Result should mention multiple dc.date.available", result.contains("dc.date.available"));
234242
}
235243

244+
@Test
245+
public void testItemWithTwoDois() throws IOException {
246+
Curator curator = new Curator();
247+
curator.addTask(TASK_NAME);
248+
context.setCurrentUser(admin);
249+
250+
// Run curator task for item with two dc.identifier.doi - should fail
251+
curator.curate(context, itemWithTwoDois.getHandle());
252+
int status = curator.getStatus(TASK_NAME);
253+
assertEquals("Curation should fail for item with two dc.identifier.doi", Curator.CURATE_FAIL, status);
254+
String result = curator.getResult(TASK_NAME);
255+
assertTrue("Result should mention multiple dc.identifier.doi", result.contains("dc.identifier.doi"));
256+
}
257+
236258
@Test
237259
public void testValidItem() throws IOException {
238260
Curator curator = new Curator();

dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.ArrayList;
2424
import java.util.List;
2525
import java.util.Random;
26+
import java.util.stream.Collectors;
2627

2728
import org.apache.commons.collections4.CollectionUtils;
2829
import org.apache.commons.lang3.ObjectUtils;
@@ -332,6 +333,52 @@ public void testStore_DOI_as_item_metadata()
332333
assertTrue("Cannot store DOI as item metadata value.", result);
333334
}
334335

336+
@Test
337+
public void testStore_DOI_keeps_existing_different_doi_metadata() throws SQLException, AuthorizeException,
338+
IOException, IdentifierException, IllegalAccessException, WorkflowException {
339+
Item item = newItem();
340+
341+
// this checks that the method does not fail if there is already a *different* DOI in the metadata,
342+
// here we verify that the existing DOI is preserved (not deleted) and the new one is added alongside it.
343+
// Items with more than one DOI are reported by the ItemMetadataQAChecker curation task, not silently
344+
// cleaned up here.
345+
String oldDoi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR + "1234";
346+
String newDoi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR
347+
+ Long.toHexString(Instant.now().toEpochMilli());
348+
349+
context.turnOffAuthorisationSystem();
350+
itemService.addMetadata(context, item, DOIIdentifierProvider.MD_SCHEMA,
351+
DOIIdentifierProvider.DOI_ELEMENT,
352+
DOIIdentifierProvider.DOI_QUALIFIER,
353+
null,
354+
doiService.DOIToExternalForm(oldDoi));
355+
provider.saveDOIToObject(context, item, newDoi);
356+
context.restoreAuthSystemState();
357+
358+
checkDoiMetadata(item, oldDoi, newDoi);
359+
}
360+
361+
@Test
362+
public void testStore_DOI_check_single_doi_metadata() throws SQLException, AuthorizeException, IOException,
363+
IdentifierException, IllegalAccessException, WorkflowException {
364+
Item item = newItem();
365+
366+
// this checks that the method does not fail if there is already a DOI in the metadata,
367+
// here we check if DOI metadata are not duplicated
368+
String doi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR + Long.toHexString(Instant.now().toEpochMilli());
369+
370+
context.turnOffAuthorisationSystem();
371+
itemService.addMetadata(context, item, DOIIdentifierProvider.MD_SCHEMA,
372+
DOIIdentifierProvider.DOI_ELEMENT,
373+
DOIIdentifierProvider.DOI_QUALIFIER,
374+
null,
375+
doiService.DOIToExternalForm(doi));
376+
provider.saveDOIToObject(context, item, doi);
377+
context.restoreAuthSystemState();
378+
379+
checkSingleDoiMetadata(item, doi);
380+
}
381+
335382
@Test
336383
public void testGet_DOI_out_of_item_metadata()
337384
throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException,
@@ -868,4 +915,34 @@ public void testLoadOrCreateDOIReturnsMintedStatus()
868915
// registerOnline
869916
// reserveOnline
870917

918+
private void checkSingleDoiMetadata(Item item, String doi) throws IdentifierException {
919+
List<MetadataValue> metadata = itemService.getMetadata(item, DOIIdentifierProvider.MD_SCHEMA,
920+
DOIIdentifierProvider.DOI_ELEMENT,
921+
DOIIdentifierProvider.DOI_QUALIFIER,
922+
Item.ANY);
923+
boolean result = false;
924+
if (metadata.size() == 1 && metadata.get(0).getValue().equals(doiService.DOIToExternalForm(doi))) {
925+
result = true;
926+
}
927+
assertTrue("Invalid or duplicate 'dc.identifier.doi' metadata value(s).", result);
928+
}
929+
930+
private void checkDoiMetadata(Item item, String... dois) throws IdentifierException {
931+
List<String> values = itemService.getMetadata(item, DOIIdentifierProvider.MD_SCHEMA,
932+
DOIIdentifierProvider.DOI_ELEMENT,
933+
DOIIdentifierProvider.DOI_QUALIFIER,
934+
Item.ANY)
935+
.stream()
936+
.map(MetadataValue::getValue)
937+
.collect(Collectors.toList());
938+
939+
List<String> expected = new ArrayList<>();
940+
for (String doi : dois) {
941+
expected.add(doiService.DOIToExternalForm(doi));
942+
}
943+
944+
assertEquals("Unexpected number of 'dc.identifier.doi' metadata values.", expected.size(), values.size());
945+
assertTrue("Expected 'dc.identifier.doi' metadata values are missing.", values.containsAll(expected));
946+
}
947+
871948
}

0 commit comments

Comments
 (0)