mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
[TLC-1097] ORCID external identifier sync fix
Handle SELF and PART_OF identifiers properly based on configuration, work type, and identifier type
This commit is contained in:
@@ -39,6 +39,7 @@ public class OrcidWorkFieldMapping {
|
||||
* The metadata fields related to the work external identifiers.
|
||||
*/
|
||||
private Map<String, String> externalIdentifierFields = new HashMap<>();
|
||||
private Map<String, List<String>> externalIdentifierPartOfMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The metadata field related to the work publication date.
|
||||
@@ -129,6 +130,15 @@ public class OrcidWorkFieldMapping {
|
||||
this.externalIdentifierFields = parseConfigurations(externalIdentifierFields);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getExternalIdentifierPartOfMap() {
|
||||
return this.externalIdentifierPartOfMap;
|
||||
}
|
||||
|
||||
public void setExternalIdentifierPartOfMap(
|
||||
HashMap<String, List<String>> externalIdentifierPartOfMap) {
|
||||
this.externalIdentifierPartOfMap = externalIdentifierPartOfMap;
|
||||
}
|
||||
|
||||
public String getPublicationDateField() {
|
||||
return publicationDateField;
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ package org.dspace.orcid.model.factory.impl;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.orcid.jaxb.model.common.Relationship.PART_OF;
|
||||
import static org.orcid.jaxb.model.common.Relationship.SELF;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -73,12 +74,12 @@ public class OrcidWorkFactory implements OrcidEntityFactory {
|
||||
@Override
|
||||
public Activity createOrcidObject(Context context, Item item) {
|
||||
Work work = new Work();
|
||||
work.setWorkType(getWorkType(context, item));
|
||||
work.setJournalTitle(getJournalTitle(context, item));
|
||||
work.setWorkContributors(getWorkContributors(context, item));
|
||||
work.setWorkTitle(getWorkTitle(context, item));
|
||||
work.setPublicationDate(getPublicationDate(context, item));
|
||||
work.setWorkExternalIdentifiers(getWorkExternalIds(context, item));
|
||||
work.setWorkType(getWorkType(context, item));
|
||||
work.setWorkExternalIdentifiers(getWorkExternalIds(context, item, work));
|
||||
work.setShortDescription(getShortDescription(context, item));
|
||||
work.setLanguageCode(getLanguageCode(context, item));
|
||||
work.setUrl(getUrl(context, item));
|
||||
@@ -148,58 +149,62 @@ public class OrcidWorkFactory implements OrcidEntityFactory {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of ExternalIDs from the metadata values of the given
|
||||
* item, using the orcid.mapping.funding.external-ids configuration.
|
||||
*/
|
||||
private ExternalIDs getWorkExternalIds(Context context, Item item) {
|
||||
ExternalIDs externalIdentifiers = new ExternalIDs();
|
||||
externalIdentifiers.getExternalIdentifier().addAll(getWorkSelfExternalIds(context, item));
|
||||
return externalIdentifiers;
|
||||
private ExternalIDs getWorkExternalIds(Context context, Item item, Work work) {
|
||||
ExternalIDs externalIDs = new ExternalIDs();
|
||||
externalIDs.getExternalIdentifier().addAll(getWorkExternalIdList(context, item, work));
|
||||
return externalIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of ExternalID, one for orcid.mapping.funding.external-ids
|
||||
* value, taking the values from the given item.
|
||||
*/
|
||||
private List<ExternalID> getWorkSelfExternalIds(Context context, Item item) {
|
||||
private List<ExternalID> getWorkExternalIdList(Context context, Item item, Work work) {
|
||||
|
||||
List<ExternalID> selfExternalIds = new ArrayList<>();
|
||||
List<ExternalID> externalIds = new ArrayList<>();
|
||||
|
||||
Map<String, String> externalIdentifierFields = fieldMapping.getExternalIdentifierFields();
|
||||
|
||||
if (externalIdentifierFields.containsKey(SIMPLE_HANDLE_PLACEHOLDER)) {
|
||||
String handleType = externalIdentifierFields.get(SIMPLE_HANDLE_PLACEHOLDER);
|
||||
selfExternalIds.add(getExternalId(handleType, item.getHandle(), SELF));
|
||||
ExternalID handle = new ExternalID();
|
||||
handle.setType(handleType);
|
||||
handle.setValue(item.getHandle());
|
||||
handle.setRelationship(SELF);
|
||||
externalIds.add(handle);
|
||||
}
|
||||
|
||||
// Resolve work type, used to determine identifier relationship type
|
||||
// For version / funding relationships, we might want to use more complex
|
||||
// business rules than just "work and id type"
|
||||
final String workType = (work != null && work.getWorkType() != null) ?
|
||||
work.getWorkType().value() : WorkType.OTHER.value();
|
||||
getMetadataValues(context, item, externalIdentifierFields.keySet()).stream()
|
||||
.map(this::getSelfExternalId)
|
||||
.forEach(selfExternalIds::add);
|
||||
.map(metadataValue -> this.getExternalId(metadataValue, workType))
|
||||
.forEach(externalIds::add);
|
||||
|
||||
return selfExternalIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of ExternalID taking the value from the given
|
||||
* metadataValue. The type of the ExternalID is calculated using the
|
||||
* orcid.mapping.funding.external-ids configuration. The relationship of the
|
||||
* ExternalID is SELF.
|
||||
*/
|
||||
private ExternalID getSelfExternalId(MetadataValue metadataValue) {
|
||||
Map<String, String> externalIdentifierFields = fieldMapping.getExternalIdentifierFields();
|
||||
String metadataField = metadataValue.getMetadataField().toString('.');
|
||||
return getExternalId(externalIdentifierFields.get(metadataField), metadataValue.getValue(), SELF);
|
||||
return externalIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of ExternalID with the given type, value and
|
||||
* relationship.
|
||||
*/
|
||||
private ExternalID getExternalId(String type, String value, Relationship relationship) {
|
||||
private ExternalID getExternalId(MetadataValue metadataValue, String workType) {
|
||||
Map<String, String> externalIdentifierFields = fieldMapping.getExternalIdentifierFields();
|
||||
Map<String, List<String>> externalIdentifierPartOfMap = fieldMapping.getExternalIdentifierPartOfMap();
|
||||
String metadataField = metadataValue.getMetadataField().toString('.');
|
||||
String identifierType = externalIdentifierFields.get(metadataField);
|
||||
// Default relationship type is SELF, configuration can
|
||||
// override to PART_OF based on identifier and work type
|
||||
Relationship relationship = SELF;
|
||||
if (externalIdentifierPartOfMap.containsKey(identifierType)
|
||||
&& externalIdentifierPartOfMap.get(identifierType).contains(workType)) {
|
||||
relationship = PART_OF;
|
||||
}
|
||||
ExternalID externalID = new ExternalID();
|
||||
externalID.setType(type);
|
||||
externalID.setValue(value);
|
||||
externalID.setType(identifierType);
|
||||
externalID.setValue(metadataValue.getValue());
|
||||
externalID.setRelationship(relationship);
|
||||
return externalID;
|
||||
}
|
||||
|
@@ -113,6 +113,14 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
||||
return addMetadataValue(item, "dc", "identifier", "scopus", scopus);
|
||||
}
|
||||
|
||||
public ItemBuilder withISSN(String issn) {
|
||||
return addMetadataValue(item, "dc", "identifier", "issn", issn);
|
||||
}
|
||||
|
||||
public ItemBuilder withISBN(String isbn) {
|
||||
return addMetadataValue(item, "dc", "identifier", "isbn", isbn);
|
||||
}
|
||||
|
||||
public ItemBuilder withRelationFunding(String funding) {
|
||||
return addMetadataValue(item, "dc", "relation", "funding", funding);
|
||||
}
|
||||
|
@@ -73,6 +73,9 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData
|
||||
|
||||
private Collection projects;
|
||||
|
||||
private static final String isbn = "978-0-439-02348-1";
|
||||
private static final String issn = "1234-1234X";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
@@ -117,6 +120,7 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData
|
||||
.withLanguage("en_US")
|
||||
.withType("Book")
|
||||
.withIsPartOf("Journal")
|
||||
.withISBN(isbn)
|
||||
.withDoiIdentifier("doi-id")
|
||||
.withScopusIdentifier("scopus-id")
|
||||
.build();
|
||||
@@ -149,13 +153,102 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData
|
||||
assertThat(work.getExternalIdentifiers(), notNullValue());
|
||||
|
||||
List<ExternalID> externalIds = work.getExternalIdentifiers().getExternalIdentifier();
|
||||
assertThat(externalIds, hasSize(3));
|
||||
assertThat(externalIds, hasSize(4));
|
||||
assertThat(externalIds, has(selfExternalId("doi", "doi-id")));
|
||||
assertThat(externalIds, has(selfExternalId("eid", "scopus-id")));
|
||||
assertThat(externalIds, has(selfExternalId("handle", publication.getHandle())));
|
||||
// Book type should have SELF rel for ISBN
|
||||
assertThat(externalIds, has(selfExternalId("isbn", isbn)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJournalArticleAndISSN() {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
Item publication = ItemBuilder.createItem(context, publications)
|
||||
.withTitle("Test publication")
|
||||
.withAuthor("Walter White")
|
||||
.withAuthor("Jesse Pinkman")
|
||||
.withEditor("Editor")
|
||||
.withIssueDate("2021-04-30")
|
||||
.withDescriptionAbstract("Publication description")
|
||||
.withLanguage("en_US")
|
||||
.withType("Article")
|
||||
.withIsPartOf("Journal")
|
||||
.withISSN(issn)
|
||||
.withDoiIdentifier("doi-id")
|
||||
.withScopusIdentifier("scopus-id")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
Activity activity = entityFactoryService.createOrcidObject(context, publication);
|
||||
assertThat(activity, instanceOf(Work.class));
|
||||
|
||||
Work work = (Work) activity;
|
||||
assertThat(work.getJournalTitle(), notNullValue());
|
||||
assertThat(work.getJournalTitle().getContent(), is("Journal"));
|
||||
assertThat(work.getLanguageCode(), is("en"));
|
||||
assertThat(work.getPublicationDate(), matches(date("2021", "04", "30")));
|
||||
assertThat(work.getShortDescription(), is("Publication description"));
|
||||
assertThat(work.getPutCode(), nullValue());
|
||||
assertThat(work.getWorkType(), is(WorkType.JOURNAL_ARTICLE));
|
||||
assertThat(work.getWorkTitle(), notNullValue());
|
||||
assertThat(work.getWorkTitle().getTitle(), notNullValue());
|
||||
assertThat(work.getWorkTitle().getTitle().getContent(), is("Test publication"));
|
||||
assertThat(work.getWorkContributors(), notNullValue());
|
||||
assertThat(work.getUrl(), matches(urlEndsWith(publication.getHandle())));
|
||||
|
||||
List<Contributor> contributors = work.getWorkContributors().getContributor();
|
||||
assertThat(contributors, hasSize(3));
|
||||
assertThat(contributors, has(contributor("Walter White", AUTHOR, FIRST)));
|
||||
assertThat(contributors, has(contributor("Editor", EDITOR, FIRST)));
|
||||
assertThat(contributors, has(contributor("Jesse Pinkman", AUTHOR, ADDITIONAL)));
|
||||
|
||||
assertThat(work.getExternalIdentifiers(), notNullValue());
|
||||
|
||||
List<ExternalID> externalIds = work.getExternalIdentifiers().getExternalIdentifier();
|
||||
assertThat(externalIds, hasSize(4));
|
||||
assertThat(externalIds, has(selfExternalId("doi", "doi-id")));
|
||||
assertThat(externalIds, has(selfExternalId("eid", "scopus-id")));
|
||||
assertThat(externalIds, has(selfExternalId("handle", publication.getHandle())));
|
||||
// journal-article should have PART_OF rel for ISSN
|
||||
assertThat(externalIds, has(externalId("issn", issn, Relationship.PART_OF)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJournalWithISSN() {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
Item publication = ItemBuilder.createItem(context, publications)
|
||||
.withTitle("Test journal")
|
||||
.withEditor("Editor")
|
||||
.withType("Journal")
|
||||
.withISSN(issn)
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
Activity activity = entityFactoryService.createOrcidObject(context, publication);
|
||||
assertThat(activity, instanceOf(Work.class));
|
||||
|
||||
Work work = (Work) activity;
|
||||
assertThat(work.getWorkType(), is(WorkType.JOURNAL_ISSUE));
|
||||
assertThat(work.getWorkTitle(), notNullValue());
|
||||
assertThat(work.getWorkTitle().getTitle(), notNullValue());
|
||||
assertThat(work.getWorkTitle().getTitle().getContent(), is("Test journal"));
|
||||
assertThat(work.getUrl(), matches(urlEndsWith(publication.getHandle())));
|
||||
|
||||
assertThat(work.getExternalIdentifiers(), notNullValue());
|
||||
|
||||
List<ExternalID> externalIds = work.getExternalIdentifiers().getExternalIdentifier();
|
||||
assertThat(externalIds, hasSize(2));
|
||||
// journal-issue should have SELF rel for ISSN
|
||||
assertThat(externalIds, has(selfExternalId("issn", issn)));
|
||||
assertThat(externalIds, has(selfExternalId("handle", publication.getHandle())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyWorkWithUnknownTypeCreation() {
|
||||
|
||||
@@ -163,6 +256,7 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData
|
||||
|
||||
Item publication = ItemBuilder.createItem(context, publications)
|
||||
.withType("TYPE")
|
||||
.withISSN(issn)
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
@@ -183,8 +277,9 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData
|
||||
assertThat(work.getExternalIdentifiers(), notNullValue());
|
||||
|
||||
List<ExternalID> externalIds = work.getExternalIdentifiers().getExternalIdentifier();
|
||||
assertThat(externalIds, hasSize(1));
|
||||
assertThat(externalIds, hasSize(2));
|
||||
assertThat(externalIds, has(selfExternalId("handle", publication.getHandle())));
|
||||
assertThat(externalIds, has(externalId("issn", issn, Relationship.PART_OF)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -7,6 +7,7 @@ Dataset = data-set
|
||||
Learning\ Object = other
|
||||
Image = other
|
||||
Image,\ 3-D = other
|
||||
Journal = journal-issue
|
||||
Map = other
|
||||
Musical\ Score = other
|
||||
Plan\ or\ blueprint = other
|
||||
@@ -20,4 +21,4 @@ Technical\ Report = other
|
||||
Thesis = other
|
||||
Video = other
|
||||
Working\ Paper = working-paper
|
||||
Other = other
|
||||
Other = other
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
#------------------------------------------------------------------#
|
||||
#--------------------ORCID GENERIC CONFIGURATIONS------------------#
|
||||
#------------------------------------------------------------------#
|
||||
@@ -61,12 +60,18 @@ orcid.mapping.work.contributors = dc.contributor.editor::editor
|
||||
|
||||
##orcid.mapping.work.external-ids syntax is <metadatafield>::<type> or $simple-handle::<type>
|
||||
##The full list of available external identifiers is available here https://pub.orcid.org/v3.0/identifiers
|
||||
# The identifiers need to have a relationship of SELF, PART_OF, VERSION_OF or FUNDED_BY.
|
||||
# The default for most identifiers is SELF. The default for identifiers more commonly
|
||||
# associated with 'parent' publciations (ISSN, ISBN) is PART_OF.
|
||||
# See the map in `orcid-services.xml`
|
||||
# VERSION_OF and FUNDED_BY are not currently implemented.
|
||||
orcid.mapping.work.external-ids = dc.identifier.doi::doi
|
||||
orcid.mapping.work.external-ids = dc.identifier.scopus::eid
|
||||
orcid.mapping.work.external-ids = dc.identifier.pmid::pmid
|
||||
orcid.mapping.work.external-ids = $simple-handle::handle
|
||||
orcid.mapping.work.external-ids = dc.identifier.isi::wosuid
|
||||
orcid.mapping.work.external-ids = dc.identifier.issn::issn
|
||||
orcid.mapping.work.external-ids = dc.identifier.isbn::isbn
|
||||
|
||||
### Funding mapping ###
|
||||
orcid.mapping.funding.title = dc.title
|
||||
@@ -146,6 +151,9 @@ orcid.bulk-synchronization.max-attempts = 5
|
||||
#--------------------ORCID EXTERNAL DATA MAPPING-------------------#
|
||||
#------------------------------------------------------------------#
|
||||
|
||||
# Note - the below mapping is for ORCID->DSpace imports, not for
|
||||
# DSpace->ORCID exports (see orcid.mapping.work.*)
|
||||
|
||||
### Work (Publication) external-data.mapping ###
|
||||
orcid.external-data.mapping.publication.title = dc.title
|
||||
|
||||
|
@@ -55,24 +55,45 @@
|
||||
<bean id="orcidWorkFactoryFieldMapping" class="org.dspace.orcid.model.OrcidWorkFieldMapping" >
|
||||
<property name="contributorFields" value="${orcid.mapping.work.contributors}" />
|
||||
<property name="externalIdentifierFields" value="${orcid.mapping.work.external-ids}" />
|
||||
<property name="publicationDateField" value="${orcid.mapping.work.publication-date}" />
|
||||
<property name="titleField" value="${orcid.mapping.work.title}" />
|
||||
<property name="journalTitleField" value="${orcid.mapping.work.journal-title}" />
|
||||
<property name="shortDescriptionField" value="${orcid.mapping.work.short-description}" />
|
||||
<property name="subTitleField" value="${orcid.mapping.work.sub-title}" />
|
||||
<property name="languageField" value="${orcid.mapping.work.language}" />
|
||||
<property name="languageConverter" ref="${orcid.mapping.work.language.converter}" />
|
||||
<property name="typeField" value="${orcid.mapping.work.type}" />
|
||||
<property name="typeConverter" ref="${orcid.mapping.work.type.converter}" />
|
||||
</bean>
|
||||
|
||||
<bean id="orcidFundingFactoryFieldMapping" class="org.dspace.orcid.model.OrcidFundingFieldMapping" >
|
||||
<property name="contributorFields" value="${orcid.mapping.funding.contributors}" />
|
||||
<property name="externalIdentifierFields" value="${orcid.mapping.funding.external-ids}" />
|
||||
<property name="titleField" value="${orcid.mapping.funding.title}" />
|
||||
<property name="typeField" value="${orcid.mapping.funding.type}" />
|
||||
<property name="typeConverter" ref="${orcid.mapping.funding.type.converter}" />
|
||||
<property name="amountField" value="${orcid.mapping.funding.amount}" />
|
||||
<property name="externalIdentifierPartOfMap">
|
||||
<map>
|
||||
<entry key="issn">
|
||||
<list>
|
||||
<value>journal-article</value>
|
||||
<value>magazine-article</value>
|
||||
<value>newspaper-article</value>
|
||||
<value>data-set</value>
|
||||
<value>learning-object</value>
|
||||
<value>other</value>
|
||||
</list>
|
||||
</entry>
|
||||
<entry key="isbn">
|
||||
<list>
|
||||
<value>book-chapter</value>
|
||||
<value>book-review</value>
|
||||
<value>other</value>
|
||||
</list>
|
||||
</entry>
|
||||
</map>
|
||||
</property>
|
||||
<property name="publicationDateField" value="${orcid.mapping.work.publication-date}" />
|
||||
<property name="titleField" value="${orcid.mapping.work.title}" />
|
||||
<property name="journalTitleField" value="${orcid.mapping.work.journal-title}" />
|
||||
<property name="shortDescriptionField" value="${orcid.mapping.work.short-description}" />
|
||||
<property name="subTitleField" value="${orcid.mapping.work.sub-title}" />
|
||||
<property name="languageField" value="${orcid.mapping.work.language}" />
|
||||
<property name="languageConverter" ref="${orcid.mapping.work.language.converter}" />
|
||||
<property name="typeField" value="${orcid.mapping.work.type}" />
|
||||
<property name="typeConverter" ref="${orcid.mapping.work.type.converter}" />
|
||||
</bean>
|
||||
|
||||
<bean id="orcidFundingFactoryFieldMapping" class="org.dspace.orcid.model.OrcidFundingFieldMapping" >
|
||||
<property name="contributorFields" value="${orcid.mapping.funding.contributors}" />
|
||||
<property name="externalIdentifierFields" value="${orcid.mapping.funding.external-ids}" />
|
||||
<property name="titleField" value="${orcid.mapping.funding.title}" />
|
||||
<property name="typeField" value="${orcid.mapping.funding.type}" />
|
||||
<property name="typeConverter" ref="${orcid.mapping.funding.type.converter}" />
|
||||
<property name="amountField" value="${orcid.mapping.funding.amount}" />
|
||||
<property name="amountCurrencyField" value="${orcid.mapping.funding.amount.currency}" />
|
||||
<property name="amountCurrencyConverter" ref="${orcid.mapping.funding.amount.currency.converter}" />
|
||||
<property name="descriptionField" value="${orcid.mapping.funding.description}" />
|
||||
|
Reference in New Issue
Block a user