All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dspace.orcid.consumer.OrcidQueueConsumer Maven / Gradle / Ivy

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