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

org.dspace.external.provider.impl.OrcidV3AuthorDataProvider 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.external.provider.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.OrcidRestConnector;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.AbstractExternalDataProvider;
import org.dspace.external.provider.orcid.xml.XMLtoBio;
import org.dspace.orcid.model.factory.OrcidFactoryUtils;
import org.orcid.jaxb.model.v3.release.common.OrcidIdentifier;
import org.orcid.jaxb.model.v3.release.record.Person;
import org.orcid.jaxb.model.v3.release.search.Result;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * This class is the implementation of the ExternalDataProvider interface that will deal with the OrcidV3 External
 * Data lookup
 */
public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider {

    private static final Logger log = LogManager.getLogger(OrcidV3AuthorDataProvider.class);

    private OrcidRestConnector orcidRestConnector;
    private String OAUTHUrl;

    private String clientId;
    private String clientSecret;

    private String accessToken;

    private String sourceIdentifier;

    private String orcidUrl;

    private XMLtoBio converter;

    /**
     * Maximum retries to allow for the access token retrieval
     */
    private int maxClientRetries = 3;

    public static final String ORCID_ID_SYNTAX = "\\d{4}-\\d{4}-\\d{4}-(\\d{3}X|\\d{4})";
    private static final int MAX_INDEX = 10000;

    @Override
    public String getSourceIdentifier() {
        return sourceIdentifier;
    }

    public OrcidV3AuthorDataProvider() {
        converter = new XMLtoBio();
    }

    /**
     * Initialize the accessToken that is required for all subsequent calls to ORCID.
     *
     * @throws java.io.IOException passed through from HTTPclient.
     */
    public void init() throws IOException {
        // Initialize access token at spring instantiation. If it fails, the access token will be null rather
        // than causing a fatal Spring startup error
        initializeAccessToken();
    }

    /**
     * Initialize access token, logging an error and decrementing remaining retries if an IOException is thrown.
     * If the optional access token result is empty, set to null instead.
     */
    public void initializeAccessToken() {
        // If we have reaches max retries or the access token is already set, return immediately
        if (maxClientRetries <= 0 || StringUtils.isNotBlank(accessToken)) {
            return;
        }
        try {
            accessToken = OrcidFactoryUtils.retrieveAccessToken(clientId, clientSecret, OAUTHUrl).orElse(null);
        } catch (IOException e) {
            log.error("Error retrieving ORCID access token, {} retries left", --maxClientRetries);
        }
    }

    @Override
    public Optional getExternalDataObject(String id) {
        initializeAccessToken();
        Person person = getBio(id);
        ExternalDataObject externalDataObject = convertToExternalDataObject(person);
        return Optional.of(externalDataObject);
    }

    protected ExternalDataObject convertToExternalDataObject(Person person) {
        initializeAccessToken();
        ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier);
        if (person.getName() != null) {
            String lastName = "";
            String firstName = "";
            if (person.getName().getFamilyName() != null) {
                lastName = person.getName().getFamilyName().getContent();
                externalDataObject.addMetadata(new MetadataValueDTO("person", "familyName", null, null,
                                                                    lastName));
            }
            if (person.getName().getGivenNames() != null) {
                firstName = person.getName().getGivenNames().getContent();
                externalDataObject.addMetadata(new MetadataValueDTO("person", "givenName", null, null,
                                                                    firstName));

            }
            externalDataObject.setId(person.getName().getPath());
            externalDataObject
                    .addMetadata(
                            new MetadataValueDTO("person", "identifier", "orcid", null, person.getName().getPath()));
            externalDataObject
                    .addMetadata(new MetadataValueDTO("dc", "identifier", "uri", null,
                                                      orcidUrl + "/" + person.getName().getPath()));
            if (!StringUtils.isBlank(lastName) && !StringUtils.isBlank(firstName)) {
                externalDataObject.setDisplayValue(lastName + ", " + firstName);
                externalDataObject.setValue(lastName + ", " + firstName);
            } else if (StringUtils.isBlank(firstName)) {
                externalDataObject.setDisplayValue(lastName);
                externalDataObject.setValue(lastName);
            } else if (StringUtils.isBlank(lastName)) {
                externalDataObject.setDisplayValue(firstName);
                externalDataObject.setValue(firstName);
            }
        } else if (person.getPath() != null ) {
            externalDataObject.setId(StringUtils.substringBetween(person.getPath(),"/","/person"));
        }
        return externalDataObject;
    }

    /**
     * Retrieve a Person object based on a given orcid identifier.
     * @param id orcid identifier
     * @return Person
     */
    public Person getBio(String id) {
        log.debug("getBio called with ID=" + id);
        if (!isValid(id)) {
            return null;
        }
        if (orcidRestConnector == null) {
            log.error("ORCID REST connector is null, returning null ORCID Person Bio");
            return null;
        }
        initializeAccessToken();
        InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken);
        Person person = converter.convertSinglePerson(bioDocument);
        try {
            bioDocument.close();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return person;
    }

    /**
     * Check to see if the provided text has the correct ORCID syntax.
     * Since only searching on ORCID id is allowed, this way, we filter out any queries that would return a
     * blank result anyway
     */
    private boolean isValid(String text) {
        return StringUtils.isNotBlank(text) && text.matches(ORCID_ID_SYNTAX);
    }

    @Override
    public List searchExternalDataObjects(String query, int start, int limit) {
        initializeAccessToken();
        if (limit > 100) {
            throw new IllegalArgumentException("The maximum number of results to retrieve cannot exceed 100.");
        }
        if (start > MAX_INDEX) {
            throw new IllegalArgumentException("The starting number of results to retrieve cannot exceed 10000.");
        }
        // Check REST connector is initialized
        if (orcidRestConnector == null) {
            log.error("ORCID REST connector is not initialized, returning empty list");
            return Collections.emptyList();
        }

        String searchPath = "search?q=" + URLEncoder.encode(query, StandardCharsets.UTF_8)
                + "&start=" + start
                + "&rows=" + limit;
        log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
        InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
        List results = converter.convert(bioDocument);
        List bios = new LinkedList<>();
        for (Result result : results) {
            OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier();
            if (orcidIdentifier != null) {
                log.debug("Found OrcidId=" + orcidIdentifier.toString());
                String orcid = orcidIdentifier.getPath();
                Person bio = getBio(orcid);
                if (bio != null) {
                    bios.add(bio);
                }
            }
        }
        try {
            bioDocument.close();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList());
    }

    @Override
    public boolean supports(String source) {
        return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
    }

    @Override
    public int getNumberOfResults(String query) {
        if (orcidRestConnector == null) {
            log.error("ORCID REST connector is null, returning 0");
            return 0;
        }
        initializeAccessToken();
        String searchPath = "search?q=" + URLEncoder.encode(query, StandardCharsets.UTF_8)
                + "&start=" + 0
                + "&rows=" + 0;
        log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
        InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
        return Math.min(converter.getNumberOfResultsFromXml(bioDocument), MAX_INDEX);
    }


    /**
     * Generic setter for the sourceIdentifier
     * @param sourceIdentifier   The sourceIdentifier to be set on this OrcidV3AuthorDataProvider
     */
    @Autowired(required = true)
    public void setSourceIdentifier(String sourceIdentifier) {
        this.sourceIdentifier = sourceIdentifier;
    }

    /**
     * Generic getter for the orcidUrl
     * @return the orcidUrl value of this OrcidV3AuthorDataProvider
     */
    public String getOrcidUrl() {
        return orcidUrl;
    }

    /**
     * Generic setter for the orcidUrl
     * @param orcidUrl   The orcidUrl to be set on this OrcidV3AuthorDataProvider
     */
    @Autowired(required = true)
    public void setOrcidUrl(String orcidUrl) {
        this.orcidUrl = orcidUrl;
    }

    /**
     * Generic setter for the OAUTHUrl
     * @param OAUTHUrl   The OAUTHUrl to be set on this OrcidV3AuthorDataProvider
     */
    public void setOAUTHUrl(String OAUTHUrl) {
        this.OAUTHUrl = OAUTHUrl;
    }

    /**
     * Generic setter for the clientId
     * @param clientId   The clientId to be set on this OrcidV3AuthorDataProvider
     */
    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    /**
     * Generic setter for the clientSecret
     * @param clientSecret   The clientSecret to be set on this OrcidV3AuthorDataProvider
     */
    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    public OrcidRestConnector getOrcidRestConnector() {
        return orcidRestConnector;
    }

    public void setOrcidRestConnector(OrcidRestConnector orcidRestConnector) {
        this.orcidRestConnector = orcidRestConnector;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy