
org.dspace.orcid.consumer.OrcidQueueConsumer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dspace-api Show documentation
Show all versions of dspace-api Show documentation
DSpace core data model and service APIs.
The newest version!
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.orcid.consumer;
import static java.util.Arrays.asList;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsFirst;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataFieldName;
import org.dspace.content.Relationship;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.RelationshipService;
import org.dspace.core.Context;
import org.dspace.event.Consumer;
import org.dspace.event.Event;
import org.dspace.orcid.OrcidHistory;
import org.dspace.orcid.OrcidOperation;
import org.dspace.orcid.factory.OrcidServiceFactory;
import org.dspace.orcid.model.OrcidEntityType;
import org.dspace.orcid.model.factory.OrcidProfileSectionFactory;
import org.dspace.orcid.service.OrcidHistoryService;
import org.dspace.orcid.service.OrcidProfileSectionFactoryService;
import org.dspace.orcid.service.OrcidQueueService;
import org.dspace.orcid.service.OrcidSynchronizationService;
import org.dspace.orcid.service.OrcidTokenService;
import org.dspace.profile.OrcidProfileSyncPreference;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* The consumer to fill the ORCID queue. The addition to the queue is made for
* all archived items that meet one of these conditions:
*
* - are profiles already linked to orcid that have some modified sections to
* be synchronized (based on the preferences set by the user)
* - are publications/fundings related to profile items linked to orcid (based
* on the preferences set by the user)
*
*
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class OrcidQueueConsumer implements Consumer {
private static final Logger LOGGER = LogManager.getLogger();
private OrcidQueueService orcidQueueService;
private OrcidHistoryService orcidHistoryService;
private OrcidTokenService orcidTokenService;
private OrcidSynchronizationService orcidSynchronizationService;
private ItemService itemService;
private OrcidProfileSectionFactoryService profileSectionFactoryService;
private ConfigurationService configurationService;
private RelationshipService relationshipService;
private final Set itemsToConsume = new HashSet<>();
@Override
public void initialize() throws Exception {
OrcidServiceFactory orcidServiceFactory = OrcidServiceFactory.getInstance();
this.orcidQueueService = orcidServiceFactory.getOrcidQueueService();
this.orcidHistoryService = orcidServiceFactory.getOrcidHistoryService();
this.orcidSynchronizationService = orcidServiceFactory.getOrcidSynchronizationService();
this.orcidTokenService = orcidServiceFactory.getOrcidTokenService();
this.profileSectionFactoryService = orcidServiceFactory.getOrcidProfileSectionFactoryService();
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
this.relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
this.itemService = ContentServiceFactory.getInstance().getItemService();
}
@Override
public void consume(Context context, Event event) throws Exception {
if (isOrcidSynchronizationDisabled()) {
return;
}
DSpaceObject dso = event.getSubject(context);
if (!(dso instanceof Item)) {
return;
}
Item item = (Item) dso;
if (!item.isArchived()) {
return;
}
itemsToConsume.add(item.getID());
}
@Override
public void end(Context context) throws Exception {
for (UUID itemId : itemsToConsume) {
Item item = itemService.find(context, itemId);
context.turnOffAuthorisationSystem();
try {
consumeItem(context, item);
} finally {
context.restoreAuthSystemState();
}
}
itemsToConsume.clear();
}
/**
* Consume the item if it is a profile or an ORCID entity.
*/
private void consumeItem(Context context, Item item) throws SQLException {
String entityType = itemService.getEntityTypeLabel(item);
if (entityType == null) {
return;
}
if (OrcidEntityType.isValidEntityType(entityType)) {
consumeEntity(context, item);
} else if (entityType.equals(getProfileType())) {
consumeProfile(context, item);
}
itemsToConsume.add(item.getID());
}
/**
* Search for all related items to the given entity and create a new ORCID queue
* record if one of this is a profile linked with ORCID and the entity item must
* be synchronized with ORCID.
*/
private void consumeEntity(Context context, Item entity) throws SQLException {
List- relatedItems = findAllRelatedItems(context, entity);
for (Item relatedItem : relatedItems) {
if (isNotProfileItem(relatedItem) || isNotLinkedToOrcid(context, relatedItem)) {
continue;
}
if (shouldNotBeSynchronized(relatedItem, entity) || isAlreadyQueued(context, relatedItem, entity)) {
continue;
}
if (isNotLatestVersion(context, entity)) {
continue;
}
orcidQueueService.create(context, relatedItem, entity);
}
}
private List
- findAllRelatedItems(Context context, Item entity) throws SQLException {
return relationshipService.findByItem(context, entity).stream()
.map(relationship -> getRelatedItem(entity, relationship))
.collect(Collectors.toList());
}
private Item getRelatedItem(Item item, Relationship relationship) {
return item.equals(relationship.getLeftItem()) ? relationship.getRightItem() : relationship.getLeftItem();
}
/**
* If the given profile item is linked with ORCID recalculate all the ORCID
* queue records of the configured profile sections that can be synchronized.
*/
private void consumeProfile(Context context, Item item) throws SQLException {
if (isNotLinkedToOrcid(context, item)) {
return;
}
for (OrcidProfileSectionFactory factory : getAllProfileSectionFactories(item)) {
String sectionType = factory.getProfileSectionType().name();
orcidQueueService.deleteByEntityAndRecordType(context, item, sectionType);
if (isProfileSectionSynchronizationDisabled(context, item, factory)) {
continue;
}
List
signatures = factory.getMetadataSignatures(context, item);
List historyRecords = findSuccessfullyOrcidHistoryRecords(context, item, sectionType);
createInsertionRecordForNewSignatures(context, item, historyRecords, factory, signatures);
createDeletionRecordForNoMorePresentSignatures(context, item, historyRecords, factory, signatures);
}
}
private boolean isProfileSectionSynchronizationDisabled(Context context,
Item item, OrcidProfileSectionFactory factory) {
List preferences = this.orcidSynchronizationService.getProfilePreferences(item);
return !preferences.contains(factory.getSynchronizationPreference());
}
/**
* Add new INSERTION record in the ORCID queue based on the metadata signatures
* calculated from the current item state.
*/
private void createInsertionRecordForNewSignatures(Context context, Item item, List historyRecords,
OrcidProfileSectionFactory factory, List signatures) throws SQLException {
String sectionType = factory.getProfileSectionType().name();
for (String signature : signatures) {
if (isNotAlreadySynchronized(historyRecords, signature)) {
String description = factory.getDescription(context, item, signature);
orcidQueueService.createProfileInsertionRecord(context, item, description, sectionType, signature);
}
}
}
/**
* Add new DELETION records in the ORCID queue for metadata signature presents
* in the ORCID history no more present in the metadata signatures calculated
* from the current item state.
*/
private void createDeletionRecordForNoMorePresentSignatures(Context context, Item profile,
List historyRecords, OrcidProfileSectionFactory factory, List signatures)
throws SQLException {
String sectionType = factory.getProfileSectionType().name();
for (OrcidHistory historyRecord : historyRecords) {
String storedSignature = historyRecord.getMetadata();
String putCode = historyRecord.getPutCode();
String description = historyRecord.getDescription();
if (signatures.contains(storedSignature) || isAlreadyDeleted(historyRecords, historyRecord)) {
continue;
}
if (StringUtils.isBlank(putCode)) {
LOGGER.warn("The orcid history record with id {} should have a not blank put code",
historyRecord::getID);
continue;
}
orcidQueueService.createProfileDeletionRecord(context, profile, description,
sectionType, storedSignature, putCode);
}
}
private List findSuccessfullyOrcidHistoryRecords(Context context, Item item,
String sectionType) throws SQLException {
return orcidHistoryService.findSuccessfullyRecordsByEntityAndType(context, item, sectionType);
}
private boolean isNotAlreadySynchronized(List records, String signature) {
return getLastOperation(records, signature)
.map(operation -> operation == OrcidOperation.DELETE)
.orElse(Boolean.TRUE);
}
private boolean isAlreadyDeleted(List records, OrcidHistory historyRecord) {
if (historyRecord.getOperation() == OrcidOperation.DELETE) {
return true;
}
return findDeletedHistoryRecordsBySignature(records, historyRecord.getMetadata())
.anyMatch(record -> record.getTimestamp().isAfter(historyRecord.getTimestamp()));
}
private Stream findDeletedHistoryRecordsBySignature(List records, String signature) {
return records.stream()
.filter(record -> signature.equals(record.getMetadata()))
.filter(record -> record.getOperation() == OrcidOperation.DELETE);
}
private Optional getLastOperation(List records, String signature) {
return records.stream()
.filter(record -> signature.equals(record.getMetadata()))
.sorted(comparing(OrcidHistory::getTimestamp, nullsFirst(naturalOrder())).reversed())
.map(OrcidHistory::getOperation)
.findFirst();
}
private boolean isAlreadyQueued(Context context, Item profileItem, Item entity) throws SQLException {
return isNotEmpty(orcidQueueService.findByProfileItemAndEntity(context, profileItem, entity));
}
private boolean isNotLinkedToOrcid(Context context, Item profileItemItem) {
return hasNotOrcidAccessToken(context, profileItemItem)
|| getMetadataValue(profileItemItem, "person.identifier.orcid") == null;
}
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 isNotProfileItem(Item profileItemItem) {
return !getProfileType().equals(itemService.getEntityTypeLabel(profileItemItem));
}
private boolean isNotLatestVersion(Context context, Item entity) {
try {
return !itemService.isLatestVersion(context, entity);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private String getMetadataValue(Item item, String metadataField) {
return itemService.getMetadataFirstValue(item, new MetadataFieldName(metadataField), Item.ANY);
}
private List getAllProfileSectionFactories(Item item) {
return this.profileSectionFactoryService.findByPreferences(asList(OrcidProfileSyncPreference.values()));
}
private String getProfileType() {
return configurationService.getProperty("researcher-profile.entity-type", "Person");
}
private boolean isOrcidSynchronizationDisabled() {
return !configurationService.getBooleanProperty("orcid.synchronization-enabled", true);
}
@Override
public void finish(Context context) throws Exception {
// nothing to do
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy