diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 6b3ef003edca..f887174c54df 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -2151,7 +2151,7 @@ private void createOrcidQueueRecordsToDeleteOnOrcid(Context context, Item entity Map profileAndPutCodeMap = orcidHistoryService.findLastPutCodes(context, entity); for (Item profile : profileAndPutCodeMap.keySet()) { - if (orcidSynchronizationService.isSynchronizationAllowed(profile, entity)) { + if (orcidSynchronizationService.isSynchronizationAllowed(context, profile, entity)) { String putCode = profileAndPutCodeMap.get(profile); String title = getMetadataFirstValue(entity, "dc", "title", null, Item.ANY); orcidQueueService.createEntityDeletionRecord(context, profile, title, entityType, putCode); diff --git a/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java b/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java index 869dc452c7d8..d7b8309f56d2 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java +++ b/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java @@ -36,6 +36,7 @@ import org.dspace.event.Event; import org.dspace.orcid.OrcidHistory; import org.dspace.orcid.OrcidOperation; +import org.dspace.orcid.OrcidQueue; import org.dspace.orcid.factory.OrcidServiceFactory; import org.dspace.orcid.model.OrcidEntityType; import org.dspace.orcid.model.factory.OrcidProfileSectionFactory; @@ -170,7 +171,20 @@ private void consumeEntity(Context context, Item entity) throws SQLException { continue; } - if (shouldNotBeSynchronized(relatedItem, entity) || isAlreadyQueued(context, relatedItem, entity)) { + if (shouldNotBeSynchronized(context, relatedItem, entity) || + isAlreadyQueued(context, relatedItem, entity)) { + // delete queue entries which are queued and relate on relationships, + // but where the relation does not exist anymore. + // Using the shouldNotBySynchonized might not be actual, because the solr + // might be behind the database state + if (isAlreadyQueued(context, relatedItem, entity) + && isSyncSettingsBasedOnRelationshipCriteriaNotValid(context, relatedItem, entity)) { + List queueentries = + orcidQueueService.findByProfileItemAndEntity(context, relatedItem, entity); + for (OrcidQueue queueentry : queueentries) { + orcidQueueService.delete(context, queueentry); + } + } continue; } @@ -305,8 +319,14 @@ private boolean hasNotOrcidAccessToken(Context context, Item profileItemItem) { return orcidTokenService.findByProfileItem(context, profileItemItem) == null; } - private boolean shouldNotBeSynchronized(Item profileItem, Item entity) { - return !orcidSynchronizationService.isSynchronizationAllowed(profileItem, entity); + private boolean shouldNotBeSynchronized(Context context, Item profileItem, Item entity) { + return !orcidSynchronizationService.isSynchronizationAllowed(context, profileItem, entity); + } + + private boolean isSyncSettingsBasedOnRelationshipCriteriaNotValid( + Context context, Item profileItem, Item entity) { + return orcidSynchronizationService. + isSyncSettingsBasedOnRelationshipCriteriaNotValid(context, profileItem, entity); } private boolean isNotProfileItem(Item profileItemItem) { diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java b/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java index ae342f310553..8715288c1bac 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java @@ -22,7 +22,7 @@ import org.dspace.profile.OrcidSynchronizationMode; /** - * Service that handle the the syncronization between a DSpace profile and the + * Service that handle the syncronization between a DSpace profile and the * relative ORCID profile, if any. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) @@ -117,10 +117,10 @@ public boolean setSynchronizationMode(Context context, Item profile, OrcidSynchr * * @param profile the researcher profile item * @param item the entity type to check - * @return true if the given entity type can be synchronize with ORCID, + * @return true if the given entity type can be synchronized with ORCID, * false otherwise */ - public boolean isSynchronizationAllowed(Item profile, Item item); + public boolean isSynchronizationAllowed(Context context, Item profile, Item item); /** * Returns the ORCID synchronization mode configured for the given profile item. @@ -130,6 +130,18 @@ public boolean setSynchronizationMode(Context context, Item profile, OrcidSynchr */ Optional getSynchronizationMode(Item profile); + /** + * Check if the current sync setting for the items is related to some relationship. + * And if so than check if the relationship criteria is not valid. + * + * @param profile the researcher profile item + * @param item the entity type to check + * @return true if the given entity type is bases on relationship + * false otherwise + */ + boolean isSyncSettingsBasedOnRelationshipCriteriaNotValid(Context context, Item profile, Item item); + + /** * Returns the ORCID synchronization preference related to the given entity type * configured for the given profile item. diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java index c0f70911b562..9dce13cafda5 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java @@ -8,6 +8,9 @@ package org.dspace.orcid.service.impl; import static org.apache.commons.lang3.ArrayUtils.contains; +import static org.dspace.profile.OrcidEntitySyncPreference.DISABLED; +import static org.dspace.profile.OrcidEntitySyncPreference.MINE; +import static org.dspace.profile.OrcidEntitySyncPreference.MY_SELECTED; import java.sql.SQLException; import java.util.Collections; @@ -220,9 +223,27 @@ public void recalculateOrcidQueue(Context context, Item profileItem, OrcidEntity OrcidEntitySyncPreference preference) throws SQLException { String entityType = orcidEntityType.getEntityType(); - if (preference == OrcidEntitySyncPreference.DISABLED) { + if (preference == DISABLED) { deleteByProfileItemAndRecordType(context, profileItem, entityType); + } else if (preference == MY_SELECTED || preference == MINE) { + // delete existing queue + deleteByProfileItemAndRecordType(context, profileItem, entityType); + // add additional filterquery + String relationname = + configurationService.getProperty("orcid.relation." + entityType + "." + preference.name()); + boolean exclusion = configurationService.getBooleanProperty("orcid.relation." + + entityType + "." + preference.name() + ".exclusion", false); + if (StringUtils.isBlank(relationname)) { + return; + } + Iterator entities; + entities = findAllRelationShipEntitiesLinkableWith(context, profileItem, entityType, relationname, + exclusion); + while (entities.hasNext()) { + create(context, profileItem, entities.next()); + } } else { + deleteByProfileItemAndRecordType(context, profileItem, entityType); Iterator entities = findAllEntitiesLinkableWith(context, profileItem, entityType); while (entities.hasNext()) { create(context, profileItem, entities.next()); @@ -254,6 +275,40 @@ private Iterator findAllEntitiesLinkableWith(Context context, Item owner, } + private Iterator findAllRelationShipEntitiesLinkableWith( + Context context, Item owner, String entityType, String filterrelationname, boolean exclusion) { + + String ownerType = itemService.getEntityType(owner); + + String query = choiceAuthorityService.getAuthorityControlledFieldsByEntityType(ownerType).stream() + .map(metadataField -> metadataField.replaceAll("_", ".")) + .filter(metadataField -> shouldNotBeIgnoredForOrcid(metadataField)) + .map(metadataField -> metadataField + "_allauthority: \"" + owner.getID().toString() + "\"") + .collect(Collectors.joining(" OR ")); + + if (StringUtils.isEmpty(query)) { + return Collections.emptyIterator(); + } + + StringBuilder sb = new StringBuilder(); + if (exclusion) { + sb.append("-"); + } + sb.append("relation." + filterrelationname + ":"); + sb.append(owner.getID().toString()); + + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.addDSpaceObjectFilter(IndexableItem.TYPE); + discoverQuery.addFilterQueries(query); + discoverQuery.addFilterQueries("search.entitytype:" + entityType); + + discoverQuery.addFilterQueries(sb.toString()); + + return new DiscoverResultItemIterator(context, discoverQuery); + + } + + private boolean shouldNotBeIgnoredForOrcid(String metadataField) { return !contains(configurationService.getArrayProperty("orcid.linkable-metadata-fields.ignore"), metadataField); } diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java index 065dbc5ee343..d309427f12ed 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java @@ -15,7 +15,6 @@ import static org.apache.commons.lang3.EnumUtils.isValidEnum; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.dspace.content.Item.ANY; -import static org.dspace.profile.OrcidEntitySyncPreference.DISABLED; import java.sql.SQLException; import java.util.HashSet; @@ -30,6 +29,7 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResultItemIterator; @@ -81,6 +81,9 @@ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationServ @Autowired private ResearcherProfileService researcherProfileService; + @Autowired + private RelationshipService relationshipService; + @Override public void linkProfile(Context context, Item profile, OrcidTokenResponseDTO token) throws SQLException { @@ -181,7 +184,7 @@ public boolean setSynchronizationMode(Context context, Item profile, OrcidSynchr } @Override - public boolean isSynchronizationAllowed(Item profile, Item item) { + public boolean isSynchronizationAllowed(Context context, Item profile, Item item) { if (isOrcidSynchronizationDisabled()) { return false; @@ -193,9 +196,41 @@ public boolean isSynchronizationAllowed(Item profile, Item item) { } if (OrcidEntityType.isValidEntityType(entityType)) { - return getEntityPreference(profile, OrcidEntityType.fromEntityType(entityType)) - .filter(pref -> pref != DISABLED) - .isPresent(); + Optional option; + option = getEntityPreference(profile, OrcidEntityType.fromEntityType(entityType)); + if (option.isPresent()) { + try { + switch (option.get()) { + case DISABLED: + return false; + case ALL: + return true; + case MINE: + case MY_SELECTED: + String relationname = configurationService.getProperty("orcid.relation." + entityType + "." + + option.get().name()); + if (isBlank(relationname)) { + return false; + } + if (configurationService.getBooleanProperty("orcid.relation." + entityType + "." + + option.get().name() + ".exclusion", false)) { + //check if no relationship exists between profile and item (e.g. MINE) + return relationShipMatchForOrcidSync(context, profile, item, relationname, false); + } else { + //check, if any relationship exists between profile and item(e.g. MY_SELECTED) + + return relationShipMatchForOrcidSync(context, profile, item, relationname, true); + + } + default: + return false; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } else { + return false; + } } if (entityType.equals(researcherProfileService.getProfileType())) { @@ -206,6 +241,58 @@ public boolean isSynchronizationAllowed(Item profile, Item item) { } + @Override + public boolean isSyncSettingsBasedOnRelationshipCriteriaNotValid(Context context, Item profile, Item item) { + String entityType = itemService.getEntityTypeLabel(item); + if (entityType == null) { + return false; + } + Optional option = + getEntityPreference(profile, OrcidEntityType.fromEntityType(entityType)); + if (option.isPresent()) { + try { + switch (option.get()) { + case MINE: + case MY_SELECTED: + String relationname = configurationService.getProperty("orcid.relation." + entityType + "." + + option.get().name()); + if (isBlank(relationname)) { + return false; + } + + //because we check the criteria is not valid we have to check the opposite + if (configurationService.getBooleanProperty("orcid.relation." + entityType + "." + + option.get().name() + ".exclusion", false)) { + //check, if any relationship exists between profile and item (e.g. MINE) + return relationShipMatchForOrcidSync(context, profile, item, relationname, true); + } else { + //check if none relationship exist between profile and item (e.g. MY_SELECT) + return relationShipMatchForOrcidSync(context, profile, item, relationname, false); + } + + default: + return false; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + return false; + } + + protected boolean relationShipMatchForOrcidSync(Context context, Item profile, Item item, String relationname, + boolean any) throws SQLException { + if (any) { + return relationshipService.findByItem(context, profile).stream().anyMatch(relationship -> + relationship.getLeftItem().getID().toString().equals(item.getID().toString()) && + relationship.getLeftwardValue().contentEquals(relationname)); + } else { + return relationshipService.findByItem(context, profile).stream().noneMatch(relationship -> + relationship.getLeftItem().getID().toString().equals(item.getID().toString()) && + relationship.getLeftwardValue().contentEquals(relationname)); + } + } + @Override public Optional getSynchronizationMode(Item item) { return getMetadataValue(item, "dspace.orcid.sync-mode") diff --git a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java index a1ebec2197e4..2d4521de347d 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java @@ -16,6 +16,8 @@ import static org.dspace.orcid.model.OrcidProfileSectionType.KEYWORDS; import static org.dspace.profile.OrcidEntitySyncPreference.ALL; import static org.dspace.profile.OrcidEntitySyncPreference.DISABLED; +import static org.dspace.profile.OrcidEntitySyncPreference.MINE; +import static org.dspace.profile.OrcidEntitySyncPreference.MY_SELECTED; import static org.dspace.profile.OrcidProfileSyncPreference.AFFILIATION; import static org.dspace.profile.OrcidProfileSyncPreference.BIOGRAPHICAL; import static org.dspace.profile.OrcidProfileSyncPreference.EDUCATION; @@ -38,11 +40,17 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.OrcidHistoryBuilder; +import org.dspace.builder.RelationshipBuilder; +import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.Collection; +import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.MetadataValue; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.InstallItemService; @@ -50,7 +58,9 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.orcid.consumer.OrcidQueueConsumer; import org.dspace.orcid.factory.OrcidServiceFactory; +import org.dspace.orcid.model.OrcidEntityType; import org.dspace.orcid.service.OrcidQueueService; +import org.dspace.profile.OrcidEntitySyncPreference; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; @@ -487,6 +497,488 @@ public void testOrcidQueueRecordCreationForPublication() throws Exception { assertThat(orcidQueueRecords.get(0), equalTo(newOrcidQueueRecords.get(0))); } + @Test + public void testOrcidQueueRecordCreationForPublicationRelationMySELECTED() throws Exception { + + context.turnOffAuthorisationSystem(); + + configurationService.addPropertyValue("orcid.relation.Publication.MY_SELECTED", + "isResearchoutputsSelectedFor"); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MY_SELECTED) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item thirdpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 3") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType SelectedType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsSelectedFor", + "hasSelectedResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, publication, "Publication", INSERT)); + + addMetadata(publication, "dc", "contributor", "editor", "Editor", null); + addMetadata(secondpublication, "dc", "contributor", "editor", "Editor", null); + addMetadata(thirdpublication, "dc", "contributor", "editor", "Editor", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(1)); + + assertThat(orcidQueueRecords.get(0), equalTo(newOrcidQueueRecords.get(0))); + + context.turnOffAuthorisationSystem(); + Relationship secondrelselected = + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + context.restoreAuthSystemState(); + context.commit(); + + List newaftercreationOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newaftercreationOrcidQueueRecords, hasSize(2)); + assertThat(newaftercreationOrcidQueueRecords, hasItem(matches(profile, publication, "Publication", INSERT))); + assertThat(newaftercreationOrcidQueueRecords, hasItem(matches(profile, secondpublication, + "Publication", INSERT))); + + context.turnOffAuthorisationSystem(); + RelationshipBuilder.deleteRelationship(secondrelselected.getID()); + itemService.update(context, secondpublication); + context.restoreAuthSystemState(); + context.commit(); + + List newafterdeletionOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newafterdeletionOrcidQueueRecords, hasSize(1)); + assertThat(newafterdeletionOrcidQueueRecords.get(0), matches(profile, publication, "Publication", INSERT)); + } + + @Test + public void testNoOrcidQueueRecordCreationForPublicationIfRelationMYSELECTEDIsNotConfigured() throws Exception { + + context.turnOffAuthorisationSystem(); + configurationService.setProperty("orcid.relation.Publication.MY_SELECTED", null); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MY_SELECTED) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType SelectedType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsSelectedFor", + "hasSelectedResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(0)); + + addMetadata(publication, "dc", "contributor", "editor", "Editor", null); + addMetadata(secondpublication, "dc", "contributor", "editor", "Editor", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(0)); + + } + @Test + public void testNoOrcidQueueRecordCreationForPublicationIfRelationMYSELECTEDQueryIsDifferent() throws Exception { + + context.turnOffAuthorisationSystem(); + configurationService.setProperty("orcid.relation.Publication.MY_SELECTED", + "isSomeOtherRelationSelectedFor"); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MY_SELECTED) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType SelectedType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsSelectedFor", + "hasSelectedResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + + context.commit(); + context.restoreAuthSystemState(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(0)); + } + + @Test + public void testOrcidQueueRecordCreationForPublicationRelationMINE() throws Exception { + + context.turnOffAuthorisationSystem(); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MINE) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item thirdpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 3") + .withAuthor("Test User", profile.getID().toString()) + .build(); + itemService.update(context, thirdpublication); + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType SelectedType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsSelectedFor", + "hasSelectedResearchoutputs", 0, null, 0, + null).build(); + RelationshipType HiddenType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(2)); + assertThat(orcidQueueRecords, hasItem(matches(profile, thirdpublication, "Publication", INSERT))); + assertThat(orcidQueueRecords, hasItem(matches(profile, publication, "Publication", INSERT))); + + addMetadata(publication, "dc", "contributor", "editor", "Editor", null); + addMetadata(secondpublication, "dc", "contributor", "editor", "Editor", null); + addMetadata(thirdpublication, "dc", "contributor", "editor", "Editor", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(2)); + + assertThat(orcidQueueRecords, hasItem(equalTo(newOrcidQueueRecords.get(0)))); + assertThat(orcidQueueRecords, hasItem(equalTo(newOrcidQueueRecords.get(1)))); + + context.turnOffAuthorisationSystem(); + Relationship relbeingdeleted = + RelationshipBuilder.createRelationshipBuilder(context, thirdpublication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + context.commit(); + context.restoreAuthSystemState(); + + List newcreationQueueRecords = orcidQueueService.findAll(context); + assertThat(newcreationQueueRecords, hasSize(1)); + assertThat(newcreationQueueRecords.get(0), matches(profile, publication, "Publication", INSERT)); + context.turnOffAuthorisationSystem(); + + context.turnOffAuthorisationSystem(); + RelationshipBuilder.deleteRelationship(relbeingdeleted.getID()); + itemService.update(context, thirdpublication); + context.restoreAuthSystemState(); + context.commit(); + + List newafterdeletionQueueRecords = orcidQueueService.findAll(context); + assertThat(newafterdeletionQueueRecords, hasSize(2)); + assertThat(newafterdeletionQueueRecords, hasItem(matches(profile, publication, "Publication", INSERT))); + assertThat(newafterdeletionQueueRecords, hasItem(matches(profile, thirdpublication, "Publication", INSERT))); + } + + @Test + public void testNoOrcidQueueRecordCreationForPublicationRelationMineIfAllAreHidden() throws Exception { + + context.turnOffAuthorisationSystem(); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MINE) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType HiddenType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + context.commit(); + context.restoreAuthSystemState(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(0)); + + addMetadata(publication, "dc", "contributor", "editor", "Editor", null); + addMetadata(secondpublication, "dc", "contributor", "editor", "Editor", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(0)); + } + + @Test + public void testNoOrcidQueueRecordCreationForPublicationRelationMineIfOneDeleted() throws Exception { + + context.turnOffAuthorisationSystem(); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MINE) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType HiddenType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + Relationship hiddenbeingdeleted = + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(0)); + + addMetadata(publication, "dc", "contributor", "editor", "Editor", null); + addMetadata(secondpublication, "dc", "contributor", "editor", "Editor", null); + + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(0)); + + context.commit(); + context.turnOffAuthorisationSystem(); + RelationshipBuilder.deleteRelationship(hiddenbeingdeleted.getID()); + itemService.update(context,secondpublication); + + context.commit(); + context.restoreAuthSystemState(); + + List newafterdeletionOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newafterdeletionOrcidQueueRecords, hasSize(1)); + } + + @Test + public void testNoOrcidQueueRecordCreationOccursIfPublicationRelationMYSELECTEDNoneExist() throws Exception { + + context.turnOffAuthorisationSystem(); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(MY_SELECTED) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(0)); + + addMetadata(publication, "dc", "contributor", "editor", "Editor", null); + addMetadata(secondpublication, "dc", "contributor", "editor", "Editor", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(0)); + } + + @Test + public void testOrcidQueueRecalculationForChangedPublicationRelationSettings() throws Exception { + + context.turnOffAuthorisationSystem(); + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(ALL) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item secondpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 2") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Item thirdpublication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication 3") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType HiddenType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", 0, null, 0, + null).build(); + + RelationshipType SelectedType = RelationshipTypeBuilder + .createRelationshipTypeBuilder(context, null, personType, "isResearchoutputsSelectedFor", + "hasSelectedResearchoutputs", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication, profile, HiddenType) + .withLeftwardValue("isResearchoutputsHiddenFor").build(); + RelationshipBuilder.createRelationshipBuilder(context, secondpublication, profile, SelectedType) + .withLeftwardValue("isResearchoutputsSelectedFor").build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(3)); + assertThat(orcidQueueRecords, hasItem(matches(profile, publication, "Publication", INSERT))); + assertThat(orcidQueueRecords, hasItem(matches(profile, secondpublication, "Publication", INSERT))); + assertThat(orcidQueueRecords, hasItem(matches(profile, thirdpublication, "Publication", INSERT))); + + changeProfilePublicationSyncPreference(profile, MY_SELECTED); + + List selectedOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(selectedOrcidQueueRecords, hasSize(1)); + assertThat(selectedOrcidQueueRecords.get(0), matches(profile, secondpublication, "Publication", INSERT)); + + changeProfilePublicationSyncPreference(profile, MINE); + + List mineOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(mineOrcidQueueRecords, hasSize(2)); + assertThat(mineOrcidQueueRecords, hasItem(matches(profile, thirdpublication, "Publication", INSERT))); + assertThat(mineOrcidQueueRecords, hasItem(matches(profile, secondpublication, "Publication", INSERT))); + + changeProfilePublicationSyncPreference(profile, DISABLED); + + List disabledOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(disabledOrcidQueueRecords, hasSize(0)); + } @Test public void testOrcidQueueRecordCreationToUpdatePublication() throws Exception { @@ -1112,4 +1604,11 @@ private Collection createCollection(String name, String entityType) { .build(); } + private void changeProfilePublicationSyncPreference(Item profile, OrcidEntitySyncPreference preference) + throws SQLException { + context.turnOffAuthorisationSystem(); + orcidQueueService.recalculateOrcidQueue(context, profile, OrcidEntityType.PUBLICATION, preference); + context.commit(); + context.restoreAuthSystemState(); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java index 092b4e47479a..b21d8c1f5fe2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -20,6 +20,7 @@ import static org.dspace.app.rest.matcher.ResourcePolicyMatcher.matchResourcePolicyProperties; import static org.dspace.profile.OrcidEntitySyncPreference.ALL; import static org.dspace.profile.OrcidEntitySyncPreference.MINE; +import static org.dspace.profile.OrcidEntitySyncPreference.MY_SELECTED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -1239,6 +1240,19 @@ public void testPatchToSetOrcidSynchronizationPreferenceForPublications() throws .andExpect(status().isOk()) .andExpect(jsonPath("$.orcidSynchronization.publicationsPreference", is(MINE.name()))); + operations = asList(new ReplaceOperation("/orcid/publications", MY_SELECTED.name())); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.publicationsPreference", is(MY_SELECTED.name()))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", ePersonId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.publicationsPreference", is(MY_SELECTED.name()))); + + operations = asList(new ReplaceOperation("/orcid/publications", "INVALID_VALUE")); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) @@ -1298,6 +1312,19 @@ public void testPatchToSetOrcidSynchronizationPreferenceForFundings() throws Exc .andExpect(status().isOk()) .andExpect(jsonPath("$.orcidSynchronization.fundingsPreference", is(MINE.name()))); + operations = asList(new ReplaceOperation("/orcid/fundings", MY_SELECTED.name())); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.fundingsPreference", is(MY_SELECTED.name()))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", ePersonId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.fundingsPreference", is(MY_SELECTED.name()))); + + operations = asList(new ReplaceOperation("/orcid/fundings", "INVALID_VALUE")); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) diff --git a/dspace/config/modules/orcid.cfg b/dspace/config/modules/orcid.cfg index 4bce1fe38f96..77e4956fb87f 100644 --- a/dspace/config/modules/orcid.cfg +++ b/dspace/config/modules/orcid.cfg @@ -47,6 +47,28 @@ orcid.scope = /read-limited orcid.scope = /activities/update orcid.scope = /person/update +#------------------------------------------------------------------# +#--------------------------- ORCID RELATION -----------------------# +#------------------------------------------------------------------# +# This allows to push certains relations and their sync preferences as MINE and MY_SELECTED settings on entities +# e.g. MY_SELECTED pushes only selected publications of some user +# e.g. MINE pushes all publications of some user but not hidden ones +# The Relation must be configured between the item entity type and the profile entity type +# It's checked by some solr query while recalculating the orcid queue when changing the setting and +# it's checked by the orcid consumer wnhen some relation is being removed if the orcid queue entry is still valid. +# orcid.relation..: - usually Person.. +# orcid.relation...exclusion: add some check to check, if the relation dies not exist +#orcid.relation.Funding.MINE = isProjectsHiddenFor +#orcid.relation.Funding.MINE.exclusion = true +#orcid.relation.Funding.MY_SELECTED = isProjectsSelectedFor +# +orcid.relation.Publication.MINE = isResearchoutputsHiddenFor +orcid.relation.Publication.MINE.exclusion = true +orcid.relation.Publication.MY_SELECTED = isResearchoutputsSelectedFor +orcid.relation.Funding.MINE = isProjectsHiddenFor +orcid.relation.Funding.MINE.exclusion = true +orcid.relation.Funding.MY_SELECTED = isProjectsSelectedFor + #------------------------------------------------------------------# #--------------------ORCID MAPPING CONFIGURATIONS------------------# #------------------------------------------------------------------#