Merge remote-tracking branch 'upstream/main' into #2956

This commit is contained in:
Mark H. Wood
2020-11-30 17:24:48 -05:00
15 changed files with 704 additions and 107 deletions

View File

@@ -4,6 +4,13 @@
# Can be validated via instructions at:
# https://docs.codecov.io/docs/codecov-yaml#validate-your-repository-yaml
# Tell Codecov not to send a coverage notification until (at least) 2 builds are completed
# Since we run Unit & Integration tests in parallel, this lets Codecov know that coverage
# needs to be merged across those builds
codecov:
notify:
after_n_builds: 2
# Settings related to code coverage analysis
coverage:
status:

65
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
# DSpace Continuous Integration/Build via GitHub Actions
# Concepts borrowed from
# https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-java-with-maven
name: Build
# Run this Build for all pushes / PRs to current branch
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
env:
# Give Maven 1GB of memory to work with
# Suppress all Maven "downloading" messages in Travis logs (see https://stackoverflow.com/a/35653426)
# This also slightly speeds builds, as there is less logging
MAVEN_OPTS: "-Xmx1024M -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
strategy:
# Create a matrix of two separate configurations for Unit vs Integration Tests
# This will ensure those tasks are run in parallel
matrix:
include:
# NOTE: Unit Tests include deprecated REST API v6 (as it has unit tests)
- type: "Unit Tests"
mvnflags: "-DskipUnitTests=false -Pdspace-rest"
# NOTE: ITs skip all code validation checks, as they are already done by Unit Test job.
# - enforcer.skip => Skip maven-enforcer-plugin rules
# - checkstyle.skip => Skip all checkstyle checks by maven-checkstyle-plugin
# - license.skip => Skip all license header checks by license-maven-plugin
# - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin
- type: "Integration Tests"
mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
# Do NOT exit immediately if one matrix job fails
# This ensures ITs continue running even if Unit Tests fail, or visa versa
fail-fast: false
# These are the actual CI steps to perform per job
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v1
# https://github.com/actions/setup-java
- name: Install JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
# https://github.com/actions/cache
- name: Cache Maven dependencies
uses: actions/cache@v2
with:
# Cache entire ~/.m2/repository
path: ~/.m2/repository
# Cache key is hash of all pom.xml files. Therefore any changes to POMs will invalidate cache
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
# Run parallel Maven builds based on the above 'strategy.matrix'
- name: Run Maven ${{ matrix.type }}
env:
TEST_FLAGS: ${{ matrix.mvnflags }}
run: mvn install -B -V -P-assembly -Pcoverage-report $TEST_FLAGS
# https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
uses: codecov/codecov-action@v1

View File

@@ -1,55 +0,0 @@
# DSpace's Travis CI Configuration
# Builds: https://travis-ci.com/github/DSpace/DSpace
# Travis configuration guide/validation: https://config.travis-ci.com/explore
language: java
# TODO: Upgrade to Bionic
dist: trusty
os: linux
jdk:
# DS-3384 Oracle JDK has DocLint enabled by default.
# Let's use this to catch any newly introduced DocLint issues.
- oraclejdk11
# Define global environment variables (shared across all jobs)
env:
global:
# Suppress all Maven "downloading" messages in Travis logs (see https://stackoverflow.com/a/35653426)
# This also slightly speeds builds in Travis, as there is less logging
- HIDE_MAVEN_DOWNLOADS="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
# Give Maven 1GB of memory to work with
- MAVEN_OPTS="-Xmx1024M $HIDE_MAVEN_DOWNLOADS"
# Maven options which will skip ALL code validation checks. Includes skipping:
# - enforcer.skip => Skip maven-enforcer-plugin rules
# - checkstyle.skip => Skip all checkstyle checks by maven-checkstyle-plugin
# - license.skip => Skip all license header checks by license-maven-plugin
# - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin
# (Useful for builds which don't need to repeat code checks)
- SKIP_CODE_CHECKS="-Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
# Create two jobs to run Unit & Integration tests in parallel.
# These jobs only differ in the TEST_FLAGS defined below,
# and otherwise share all the other configs in this file
jobs:
include:
- name: "Run Unit Tests & Check Code"
# NOTE: unit tests include deprecated REST API v6 (as it has unit tests)
env: TEST_FLAGS="-DskipUnitTests=false -Pdspace-rest"
- name: "Run Integration Tests"
# NOTE: skips code checks, as they are already done by Unit Test job
env: TEST_FLAGS="-DskipIntegrationTests=false $SKIP_CODE_CHECKS"
# Skip 'install' process to save time. We build/install/test all at once in "script" below.
install: skip
# Build DSpace and run configured tests (see 'jobs' above)
# Notes on flags used:
# -B => Maven batch/non-interactive mode (recommended for CI)
# -V => Display Maven version info before build
# -P-assembly => Disable build of dspace-installer in [src]/dspace/, as it can be memory intensive
# -Pcoverage-report => Enable aggregate code coverage report (across all modules) via JaCoCo
script: mvn install -B -V -P-assembly -Pcoverage-report $TEST_FLAGS
# After a successful build and test (see 'script'), send aggregate code coverage reports
# (generated by -Pcoverage-report above) to CodeCov.io
after_success: bash <(curl -s https://codecov.io/bash)

View File

@@ -1,7 +1,7 @@
# DSpace
[![Build Status](https://travis-ci.com/DSpace/DSpace.png?branch=main)](https://travis-ci.com/DSpace/DSpace)
[![Build Status](https://github.com/DSpace/DSpace/workflows/Build/badge.svg)](https://github.com/DSpace/DSpace/actions?query=workflow%3ABuild)
[DSpace Documentation](https://wiki.lyrasis.org/display/DSDOC/) |
[DSpace Releases](https://github.com/DSpace/DSpace/releases) |
@@ -86,7 +86,7 @@ DSpace uses GitHub to track issues:
### Running Tests
By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are
run automatically by [Travis CI](https://travis-ci.com/DSpace/DSpace/) for all Pull Requests and code commits.
run automatically by [GitHub Actions](https://github.com/DSpace/DSpace/actions?query=workflow%3ABuild) for all Pull Requests and code commits.
* How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`):
```

View File

@@ -842,12 +842,30 @@
<version>1.10.50</version>
</dependency>
<!-- For ORCID v2 integration -->
<dependency>
<groupId>org.dspace</groupId>
<artifactId>orcid-jaxb-api</artifactId>
<version>2.1.0</version>
<groupId>org.orcid</groupId>
<artifactId>orcid-model</artifactId>
<version>3.0.2</version>
<exclusions>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>

View File

@@ -37,6 +37,7 @@ public class OrcidRestConnector {
}
public InputStream get(String path, String accessToken) {
HttpResponse getResponse = null;
InputStream result = null;
path = trimSlashes(path);
@@ -48,7 +49,7 @@ public class OrcidRestConnector {
}
try {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse getResponse = httpClient.execute(httpGet);
getResponse = httpClient.execute(httpGet);
//do not close this httpClient
result = getResponse.getEntity().getContent();
} catch (Exception e) {
@@ -76,6 +77,4 @@ public class OrcidRestConnector {
Scanner s = new Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
}

View File

@@ -32,30 +32,33 @@ import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
import org.dspace.external.provider.orcid.xml.XMLtoBio;
import org.json.JSONObject;
import org.orcid.jaxb.model.common_v2.OrcidId;
import org.orcid.jaxb.model.record_v2.Person;
import org.orcid.jaxb.model.search_v2.Result;
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 OrcidV2 External
* This class is the implementation of the ExternalDataProvider interface that will deal with the OrcidV3 External
* Data lookup
*/
public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
public class OrcidV3AuthorDataProvider implements ExternalDataProvider {
private static final Logger log = LogManager.getLogger(OrcidV2AuthorDataProvider.class);
private static final Logger log = LogManager.getLogger(OrcidV3AuthorDataProvider.class);
private final OrcidRestConnector orcidRestConnector;
private OrcidRestConnector orcidRestConnector;
private String OAUTHUrl;
private String clientId;
private String clientId;
private String clientSecret;
private String accessToken;
private String sourceIdentifier;
private String orcidUrl;
private XMLtoBio converter;
public static final String ORCID_ID_SYNTAX = "\\d{4}-\\d{4}-\\d{4}-(\\d{3}X|\\d{4})";
@Override
@@ -63,13 +66,18 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
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 {
if (StringUtils.isNotBlank(accessToken) && StringUtils.isNotBlank(clientSecret)) {
if (StringUtils.isNotBlank(clientSecret) && StringUtils.isNotBlank(clientId)
&& StringUtils.isNotBlank(OAUTHUrl)) {
String authenticationParameters = "?client_id=" + clientId +
"&client_secret=" + clientSecret +
"&scope=/read-public&grant_type=client_credentials";
@@ -101,14 +109,6 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
}
}
/**
* Makes an instance of the Orcidv2 class based on the provided parameters.
* This constructor is called through the spring bean initialization
*/
private OrcidV2AuthorDataProvider(String url) {
this.orcidRestConnector = new OrcidRestConnector(url);
}
@Override
public Optional<ExternalDataObject> getExternalDataObject(String id) {
Person person = getBio(id);
@@ -121,12 +121,12 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
String lastName = "";
String firstName = "";
if (person.getName().getFamilyName() != null) {
lastName = person.getName().getFamilyName().getValue();
lastName = person.getName().getFamilyName().getContent();
externalDataObject.addMetadata(new MetadataValueDTO("person", "familyName", null, null,
lastName));
}
if (person.getName().getGivenNames() != null) {
firstName = person.getName().getGivenNames().getValue();
firstName = person.getName().getGivenNames().getContent();
externalDataObject.addMetadata(new MetadataValueDTO("person", "givenName", null, null,
firstName));
@@ -150,7 +150,7 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
}
/**
* Retrieve a Person object based on a given orcid identifier
* Retrieve a Person object based on a given orcid identifier.
* @param id orcid identifier
* @return Person
*/
@@ -160,7 +160,6 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
return null;
}
InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken);
XMLtoBio converter = new XMLtoBio();
Person person = converter.convertSinglePerson(bioDocument);
try {
bioDocument.close();
@@ -190,14 +189,13 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
+ "&rows=" + limit;
log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
XMLtoBio converter = new XMLtoBio();
List<Result> results = converter.convert(bioDocument);
List<Person> bios = new LinkedList<>();
for (Result result : results) {
OrcidId orcidIdentifier = result.getOrcidIdentifier();
OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier();
if (orcidIdentifier != null) {
log.debug("Found OrcidId=" + orcidIdentifier.toString());
String orcid = orcidIdentifier.getUriPath();
String orcid = orcidIdentifier.getPath();
Person bio = getBio(orcid);
if (bio != null) {
bios.add(bio);
@@ -228,14 +226,13 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
+ "&rows=" + 0;
log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
XMLtoBio converter = new XMLtoBio();
return converter.getNumberOfResultsFromXml(bioDocument);
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this OrcidV2AuthorDataProvider
* @param sourceIdentifier The sourceIdentifier to be set on this OrcidV3AuthorDataProvider
*/
@Autowired(required = true)
public void setSourceIdentifier(String sourceIdentifier) {
@@ -244,7 +241,7 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
/**
* Generic getter for the orcidUrl
* @return the orcidUrl value of this OrcidV2AuthorDataProvider
* @return the orcidUrl value of this OrcidV3AuthorDataProvider
*/
public String getOrcidUrl() {
return orcidUrl;
@@ -252,7 +249,7 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
/**
* Generic setter for the orcidUrl
* @param orcidUrl The orcidUrl to be set on this OrcidV2AuthorDataProvider
* @param orcidUrl The orcidUrl to be set on this OrcidV3AuthorDataProvider
*/
@Autowired(required = true)
public void setOrcidUrl(String orcidUrl) {
@@ -261,7 +258,7 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
/**
* Generic setter for the OAUTHUrl
* @param OAUTHUrl The OAUTHUrl to be set on this OrcidV2AuthorDataProvider
* @param OAUTHUrl The OAUTHUrl to be set on this OrcidV3AuthorDataProvider
*/
public void setOAUTHUrl(String OAUTHUrl) {
this.OAUTHUrl = OAUTHUrl;
@@ -269,7 +266,7 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
/**
* Generic setter for the clientId
* @param clientId The clientId to be set on this OrcidV2AuthorDataProvider
* @param clientId The clientId to be set on this OrcidV3AuthorDataProvider
*/
public void setClientId(String clientId) {
this.clientId = clientId;
@@ -277,9 +274,18 @@ public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
/**
* Generic setter for the clientSecret
* @param clientSecret The clientSecret to be set on this OrcidV2AuthorDataProvider
* @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;
}
}

View File

@@ -13,11 +13,12 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.orcid.jaxb.model.record_v2.Person;
import org.orcid.jaxb.model.search_v2.Result;
import org.orcid.jaxb.model.search_v2.Search;
import org.orcid.jaxb.model.v3.release.record.Person;
import org.orcid.jaxb.model.v3.release.search.Result;
import org.orcid.jaxb.model.v3.release.search.Search;
import org.xml.sax.SAXException;
/**
* @author Antoine Snyers (antoine at atmire.com)
* @author Kevin Van de Velde (kevin at atmire dot com)
@@ -36,7 +37,7 @@ public class XMLtoBio extends Converter<List<Result>> {
List<Result> bios = new ArrayList<>();
try {
Search search = (Search) unmarshall(xml, Search.class);
bios = search.getResult();
bios = search.getResults();
} catch (SAXException | URISyntaxException e) {
log.error(e);
}
@@ -52,6 +53,7 @@ public class XMLtoBio extends Converter<List<Result>> {
}
return 0;
}
public Person convertSinglePerson(InputStream xml) {
Person person = null;
try {

View File

@@ -10,5 +10,17 @@
<property name="sourceIdentifier" value="mock"/>
</bean>
<bean class="org.dspace.external.provider.impl.OrcidV3AuthorDataProvider" init-method="init">
<property name="sourceIdentifier" value="orcid"/>
<property name="orcidUrl" value="${orcid.url}" />
<property name="clientId" value="${orcid.clientid}" />
<property name="clientSecret" value="${orcid.clientsecret}" />
<property name="OAUTHUrl" value="${orcid.oauth.url}" />
<property name="orcidRestConnector" ref="orcidRestConnector"/>
</bean>
<bean id="orcidRestConnector" class="org.dspace.external.OrcidRestConnector">
<constructor-arg value="${orcid.api.url}"/>
</bean>
</beans>

View File

@@ -24,10 +24,11 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati
public void findAllExternalSources() throws Exception {
getClient().perform(get("/api/integration/externalsources"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItem(
ExternalSourceMatcher.matchExternalSource("mock", "mock", false)
.andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems(
ExternalSourceMatcher.matchExternalSource("mock", "mock", false),
ExternalSourceMatcher.matchExternalSource("orcid", "orcid", false)
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
.andExpect(jsonPath("$.page.totalElements", Matchers.is(2)));
}
@Test

View File

@@ -0,0 +1,281 @@
/**
* 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.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.InputStream;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.external.OrcidRestConnector;
import org.dspace.external.provider.impl.OrcidV3AuthorDataProvider;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers;
import org.junit.Assume;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This test suite includes static test with mock data and end to end test to
* verify the integration with ORCID as an External Source. The end to end test
* run only if the orcid.clientid property is configured but of course also
* orcid.clientsecret is needed to successful run the tests. This can be enabled
* setting the orcid credentials via env variables, see the comments in the
* override section of the config-definition.xml
*
* @author Mykhaylo Boychuk (4Science.it)
*
*/
public class OrcidExternalSourcesIT extends AbstractControllerIntegrationTest {
@Autowired
ConfigurationService configurationService;
@Autowired
private OrcidV3AuthorDataProvider orcidV3AuthorDataProvider;
public void onlyRunIfConfigExists() {
if (StringUtils.isBlank(configurationService.getProperty("orcid.clientid"))) {
Assume.assumeNoException(new IllegalStateException("Missing ORCID credentials"));
}
}
@Test
public void findOneExternalSourcesExistingSources() throws Exception {
getClient().perform(get("/api/integration/externalsources/orcid"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.allOf(
hasJsonPath("$.id", is("orcid")),
hasJsonPath("$.name", is("orcid")),
hasJsonPath("$.hierarchical", is(false)),
hasJsonPath("$.type", is("externalsource"))
)));
}
@Test
public void findOneExternalSourcesExistingSourcesWithentryValueTest() throws Exception {
// this test will query the real ORCID API if configured in the CI otherwise will be skipped
onlyRunIfConfigExists();
String entry = "0000-0002-9029-1854";
getClient().perform(get("/api/integration/externalsources/orcid/entryValues/" + entry))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.allOf(
hasJsonPath("$.id", is(entry)),
hasJsonPath("$.display", is("Bollini, Andrea")),
hasJsonPath("$.value", is("Bollini, Andrea")),
hasJsonPath("$.externalSource", is("orcid")),
hasJsonPath("$.type", is("externalSourceEntry"))
)))
.andExpect(jsonPath("$.metadata['dc.identifier.uri'][0].value",is("https://orcid.org/" + entry)))
.andExpect(jsonPath("$.metadata['person.familyName'][0].value",is("Bollini")))
.andExpect(jsonPath("$.metadata['person.givenName'][0].value",is("Andrea")))
.andExpect(jsonPath("$.metadata['person.identifier.orcid'][0].value",is(entry)));
}
@Test
public void findOneExternalSourceEntriesApplicableQueryTest() throws Exception {
// this test will query the real ORCID API if configured in the CI otherwise will be skipped
onlyRunIfConfigExists();
String q = "orcid:0000-0002-9029-1854";
getClient().perform(get("/api/integration/externalsources/orcid/entries")
.param("query", q))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries[0]", Matchers.allOf(
hasJsonPath("$.id", is("0000-0002-9029-1854")),
hasJsonPath("$.display", is("Bollini, Andrea")),
hasJsonPath("$.value", is("Bollini, Andrea")),
hasJsonPath("$.externalSource", is("orcid")),
hasJsonPath("$.type", is("externalSourceEntry"))
)))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['dc.identifier.uri'][0].value",
is("https://orcid.org/0000-0002-9029-1854")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.familyName'][0].value",
is("Bollini")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.givenName'][0].value",
is("Andrea")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.identifier.orcid'][0].value",
is("0000-0002-9029-1854")));
}
@Test
public void findOneExternalSourceEntriesApplicableQueryFamilyNameAndGivenNamesTest() throws Exception {
// this test will query the real ORCID API if configured in the CI otherwise will be skipped
onlyRunIfConfigExists();
String q = "family-name:bollini AND given-names:andrea";
getClient().perform(get("/api/integration/externalsources/orcid/entries")
.param("query", q))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries", Matchers.hasItem(
Matchers.allOf(
hasJsonPath("$.id", is("0000-0002-9029-1854")),
hasJsonPath("$.display", is("Bollini, Andrea")),
hasJsonPath("$.value", is("Bollini, Andrea")),
hasJsonPath("$.externalSource", is("orcid")),
hasJsonPath("$.type", is("externalSourceEntry")))
)))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['dc.identifier.uri'][0].value",
is("https://orcid.org/0000-0002-9029-1854")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.familyName'][0].value",
is("Bollini")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.givenName'][0].value",
is("Andrea")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.identifier.orcid'][0].value",
is("0000-0002-9029-1854")));
}
@Test
/**
* This test uses mock data in the orcid-person-record.xml file to simulate the
* response from ORCID and verify that it is properly consumed and exposed by
* the REST API
*
* @throws Exception
*/
public void findOneExternalSourcesMockitoTest() throws Exception {
OrcidRestConnector orcidConnector = Mockito.mock(OrcidRestConnector.class);
OrcidRestConnector realConnector = orcidV3AuthorDataProvider.getOrcidRestConnector();
orcidV3AuthorDataProvider.setOrcidRestConnector(orcidConnector);
when(orcidConnector.get(ArgumentMatchers.endsWith("/person"), ArgumentMatchers.any()))
.thenAnswer(new Answer<InputStream>() {
public InputStream answer(InvocationOnMock invocation) {
return getClass().getResourceAsStream("orcid-person-record.xml");
}
});
String entry = "0000-0002-9029-1854";
getClient().perform(get("/api/integration/externalsources/orcid/entryValues/" + entry))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.allOf(
hasJsonPath("$.id", is(entry)),
hasJsonPath("$.display", is("Bollini, Andrea")),
hasJsonPath("$.value", is("Bollini, Andrea")),
hasJsonPath("$.externalSource", is("orcid")),
hasJsonPath("$.type", is("externalSourceEntry"))
)));
orcidV3AuthorDataProvider.setOrcidRestConnector(realConnector);
}
@Test
/**
* This test uses mock data in the orcid-search.xml and orcid-person-record.xml
* file to simulate the response from ORCID and verify that it is properly
* consumed and exposed by the REST API. The orcid-search.xml file indeed
* contains the ORCID matching the user query, for each of them our integration
* need to grab details making a second call to the ORCID profile (this is due
* to the ORCID API structure and cannot be avoid)
*
* @throws Exception
*/
public void findOneExternalSourceEntriesApplicableQueryMockitoTest() throws Exception {
OrcidRestConnector orcidConnector = Mockito.mock(OrcidRestConnector.class);
OrcidRestConnector realConnector = orcidV3AuthorDataProvider.getOrcidRestConnector();
orcidV3AuthorDataProvider.setOrcidRestConnector(orcidConnector);
try {
when(orcidConnector.get(ArgumentMatchers.startsWith("search?"), ArgumentMatchers.any()))
.thenAnswer(new Answer<InputStream>() {
public InputStream answer(InvocationOnMock invocation) {
return getClass().getResourceAsStream("orcid-search.xml");
}
});
when(orcidConnector.get(ArgumentMatchers.endsWith("/person"), ArgumentMatchers.any()))
.thenAnswer(new Answer<InputStream>() {
public InputStream answer(InvocationOnMock invocation) {
return getClass().getResourceAsStream("orcid-person-record.xml");
}
});
String q = "orcid:0000-0002-9029-1854";
getClient().perform(get("/api/integration/externalsources/orcid/entries")
.param("query", q))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries[0]", Matchers.allOf(
hasJsonPath("$.id", is("0000-0002-9029-1854")),
hasJsonPath("$.display", is("Bollini, Andrea")),
hasJsonPath("$.value", is("Bollini, Andrea")),
hasJsonPath("$.externalSource", is("orcid")),
hasJsonPath("$.type", is("externalSourceEntry"))
)))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['dc.identifier.uri'][0].value",
is("https://orcid.org/0000-0002-9029-1854")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.familyName'][0].value",
is("Bollini")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.givenName'][0].value",
is("Andrea")))
.andExpect(jsonPath(
"$._embedded.externalSourceEntries[0].metadata['person.identifier.orcid'][0].value",
is("0000-0002-9029-1854")));
} finally {
orcidV3AuthorDataProvider.setOrcidRestConnector(realConnector);
}
}
@Test
/**
* This test uses mock data in the orcid-search.xml and orcid-person-record.xml
* file to simulate the response from ORCID and verify that it is properly
* consumed and exposed by the REST API. The orcid-search.xml file indeed
* contains the ORCID matching the user query, for each of them our integration
* need to grab details making a second call to the ORCID profile (this is due
* to the ORCID API structure and cannot be avoid)
*
* @throws Exception
*/
public void findOneExternalSourceEntriesApplicableQueryFamilyNameAndGivenNamesMockitoTest() throws Exception {
OrcidRestConnector orcidConnector = Mockito.mock(OrcidRestConnector.class);
OrcidRestConnector realConnector = orcidV3AuthorDataProvider.getOrcidRestConnector();
orcidV3AuthorDataProvider.setOrcidRestConnector(orcidConnector);
try {
when(orcidConnector.get(ArgumentMatchers.startsWith("search?"), ArgumentMatchers.any()))
.thenAnswer(new Answer<InputStream>() {
public InputStream answer(InvocationOnMock invocation) {
return getClass().getResourceAsStream("orcid-search.xml");
}
});
when(orcidConnector.get(ArgumentMatchers.endsWith("/person"), ArgumentMatchers.any()))
.thenAnswer(new Answer<InputStream>() {
public InputStream answer(InvocationOnMock invocation) {
return getClass().getResourceAsStream("orcid-person-record.xml");
}
});
String q = "family-name:bollini AND given-names:andrea";
getClient().perform(get("/api/integration/externalsources/orcid/entries")
.param("query", q))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries", Matchers.hasItem(
Matchers.allOf(
hasJsonPath("$.id", is("0000-0002-9029-1854")),
hasJsonPath("$.display", is("Bollini, Andrea")),
hasJsonPath("$.value", is("Bollini, Andrea")),
hasJsonPath("$.externalSource", is("orcid")),
hasJsonPath("$.type", is("externalSourceEntry")))
)))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['dc.identifier.uri'][0].value",
is("https://orcid.org/0000-0002-9029-1854")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.familyName'][0].value",
is("Bollini")))
.andExpect(jsonPath("$._embedded.externalSourceEntries[0].metadata['person.givenName'][0].value",
is("Andrea")))
.andExpect(jsonPath(
"$._embedded.externalSourceEntries[0].metadata['person.identifier.orcid'][0].value",
is("0000-0002-9029-1854")));
} finally {
orcidV3AuthorDataProvider.setOrcidRestConnector(realConnector);
}
}
}

View File

@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person:person path="/0000-0002-9029-1854/person" xmlns:internal="http://www.orcid.org/ns/internal" xmlns:education="http://www.orcid.org/ns/education" xmlns:distinction="http://www.orcid.org/ns/distinction" xmlns:deprecated="http://www.orcid.org/ns/deprecated" xmlns:other-name="http://www.orcid.org/ns/other-name" xmlns:membership="http://www.orcid.org/ns/membership" xmlns:error="http://www.orcid.org/ns/error" xmlns:common="http://www.orcid.org/ns/common" xmlns:record="http://www.orcid.org/ns/record" xmlns:personal-details="http://www.orcid.org/ns/personal-details" xmlns:keyword="http://www.orcid.org/ns/keyword" xmlns:email="http://www.orcid.org/ns/email" xmlns:external-identifier="http://www.orcid.org/ns/external-identifier" xmlns:funding="http://www.orcid.org/ns/funding" xmlns:preferences="http://www.orcid.org/ns/preferences" xmlns:address="http://www.orcid.org/ns/address" xmlns:invited-position="http://www.orcid.org/ns/invited-position" xmlns:work="http://www.orcid.org/ns/work" xmlns:history="http://www.orcid.org/ns/history" xmlns:employment="http://www.orcid.org/ns/employment" xmlns:qualification="http://www.orcid.org/ns/qualification" xmlns:service="http://www.orcid.org/ns/service" xmlns:person="http://www.orcid.org/ns/person" xmlns:activities="http://www.orcid.org/ns/activities" xmlns:researcher-url="http://www.orcid.org/ns/researcher-url" xmlns:peer-review="http://www.orcid.org/ns/peer-review" xmlns:bulk="http://www.orcid.org/ns/bulk" xmlns:research-resource="http://www.orcid.org/ns/research-resource">
<common:last-modified-date>2018-02-05T23:27:36.636Z</common:last-modified-date>
<person:name visibility="public" path="0000-0002-9029-1854">
<common:created-date>2016-04-15T23:17:03.663Z</common:created-date>
<common:last-modified-date>2016-04-15T23:17:03.663Z</common:last-modified-date>
<personal-details:given-names>Andrea</personal-details:given-names>
<personal-details:family-name>Bollini</personal-details:family-name>
</person:name>
<other-name:other-names path="/0000-0002-9029-1854/other-names"/>
<researcher-url:researcher-urls path="/0000-0002-9029-1854/researcher-urls">
<common:last-modified-date>2017-07-18T15:10:48.940Z</common:last-modified-date>
<researcher-url:researcher-url put-code="1005571" visibility="public" path="/0000-0002-9029-1854/researcher-urls/1005571" display-index="5">
<common:created-date>2017-01-16T08:12:12.946Z</common:created-date>
<common:last-modified-date>2017-07-18T15:10:48.940Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<researcher-url:url-name>Linkedin</researcher-url:url-name>
<researcher-url:url>https://it.linkedin.com/in/andreabollini</researcher-url:url>
</researcher-url:researcher-url>
<researcher-url:researcher-url put-code="369007" visibility="public" path="/0000-0002-9029-1854/researcher-urls/369007" display-index="4">
<common:created-date>2014-11-06T10:37:30.383Z</common:created-date>
<common:last-modified-date>2017-07-18T15:10:48.940Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<researcher-url:url-name>4Science</researcher-url:url-name>
<researcher-url:url>http://www.4science.it/en/</researcher-url:url>
</researcher-url:researcher-url>
<researcher-url:researcher-url put-code="369009" visibility="public" path="/0000-0002-9029-1854/researcher-urls/369009" display-index="3">
<common:created-date>2014-11-06T10:37:30.412Z</common:created-date>
<common:last-modified-date>2017-07-18T15:10:48.940Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<researcher-url:url-name>DSpace</researcher-url:url-name>
<researcher-url:url>http://www.dspace.org</researcher-url:url>
</researcher-url:researcher-url>
<researcher-url:researcher-url put-code="369008" visibility="public" path="/0000-0002-9029-1854/researcher-urls/369008" display-index="2">
<common:created-date>2014-11-06T10:37:30.398Z</common:created-date>
<common:last-modified-date>2017-07-18T15:10:48.940Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<researcher-url:url-name>DSpace-CRIS</researcher-url:url-name>
<researcher-url:url>https://wiki.duraspace.org/display/DSPACECRIS</researcher-url:url>
</researcher-url:researcher-url>
</researcher-url:researcher-urls>
<email:emails path="/0000-0002-9029-1854/email">
<common:last-modified-date>2016-09-12T11:22:47.354Z</common:last-modified-date>
<email:email visibility="public" verified="true" primary="true">
<common:created-date>2016-09-12T10:45:26.123Z</common:created-date>
<common:last-modified-date>2016-09-12T11:22:47.354Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<email:email>andrea.bollini@4science.it</email:email>
</email:email>
</email:emails>
<address:addresses path="/0000-0002-9029-1854/address">
<common:last-modified-date>2016-06-06T15:29:36.952Z</common:last-modified-date>
<address:address put-code="240615" visibility="public" path="/0000-0002-9029-1854/address/240615" display-index="0">
<common:created-date>2016-01-24T18:24:26.704Z</common:created-date>
<common:last-modified-date>2016-06-06T15:29:36.952Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<address:country>IT</address:country>
</address:address>
</address:addresses>
<keyword:keywords path="/0000-0002-9029-1854/keywords">
<common:last-modified-date>2016-03-01T11:03:22.508Z</common:last-modified-date>
<keyword:keyword put-code="7508" visibility="public" path="/0000-0002-9029-1854/keywords/7508" display-index="3">
<common:created-date>2013-05-30T10:55:45.614Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.508Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content>Software</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="7509" visibility="public" path="/0000-0002-9029-1854/keywords/7509" display-index="2">
<common:created-date>2013-05-30T10:55:45.614Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.508Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content> Open Source</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="12596" visibility="public" path="/0000-0002-9029-1854/keywords/12596" display-index="1">
<common:created-date>2013-05-30T10:55:45.614Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.508Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content> JAVA</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="381655" visibility="public" path="/0000-0002-9029-1854/keywords/381655" display-index="0">
<common:created-date>2016-03-01T11:03:22.491Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.491Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content>CERIF</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="381656" visibility="public" path="/0000-0002-9029-1854/keywords/381656" display-index="0">
<common:created-date>2016-03-01T11:03:22.502Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.502Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content>CRIS</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="381657" visibility="public" path="/0000-0002-9029-1854/keywords/381657" display-index="0">
<common:created-date>2016-03-01T11:03:22.503Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.503Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content>RIMS</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="381658" visibility="public" path="/0000-0002-9029-1854/keywords/381658" display-index="0">
<common:created-date>2016-03-01T11:03:22.504Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.504Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content>Open Standards</keyword:content>
</keyword:keyword>
<keyword:keyword put-code="381659" visibility="public" path="/0000-0002-9029-1854/keywords/381659" display-index="0">
<common:created-date>2016-03-01T11:03:22.505Z</common:created-date>
<common:last-modified-date>2016-03-01T11:03:22.505Z</common:last-modified-date>
<common:source>
<common:source-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:source-orcid>
<common:source-name>Andrea Bollini</common:source-name>
</common:source>
<keyword:content>OAI-PMH</keyword:content>
</keyword:keyword>
</keyword:keywords>
<external-identifier:external-identifiers path="/0000-0002-9029-1854/external-identifiers">
<common:last-modified-date>2018-02-05T23:27:36.636Z</common:last-modified-date>
<external-identifier:external-identifier put-code="153301" visibility="public" path="/0000-0002-9029-1854/external-identifiers/153301" display-index="0">
<common:created-date>2013-05-30T10:55:45.614Z</common:created-date>
<common:last-modified-date>2018-02-05T23:27:36.636Z</common:last-modified-date>
<common:source>
<common:source-client-id>
<common:uri>https://orcid.org/client/0000-0002-5982-8983</common:uri>
<common:path>0000-0002-5982-8983</common:path>
<common:host>orcid.org</common:host>
</common:source-client-id>
<common:source-name>Scopus - Elsevier</common:source-name>
<common:assertion-origin-orcid>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:assertion-origin-orcid>
<common:assertion-origin-name>Andrea Bollini</common:assertion-origin-name>
</common:source>
<common:external-id-type>Scopus Author ID</common:external-id-type>
<common:external-id-value>55484808800</common:external-id-value>
<common:external-id-url>http://www.scopus.com/inward/authorDetails.url?authorID=55484808800&amp;partnerID=MN8TOARS</common:external-id-url>
<common:external-id-relationship>self</common:external-id-relationship>
</external-identifier:external-identifier>
</external-identifier:external-identifiers>
</person:person>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<search:search num-found="1" xmlns:search="http://www.orcid.org/ns/search" xmlns:common="http://www.orcid.org/ns/common">
<search:result>
<common:orcid-identifier>
<common:uri>https://orcid.org/0000-0002-9029-1854</common:uri>
<common:path>0000-0002-9029-1854</common:path>
<common:host>orcid.org</common:host>
</common:orcid-identifier>
</search:result>
</search:search>

View File

@@ -1473,10 +1473,20 @@ sherpa.romeo.apikey =
# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority
# URL of ORCID API
# Defaults to using the Public API (pub.orcid.org)
orcid.api.url = https://pub.orcid.org/v2.1
# Defaults to using the Public API V3 (pub.orcid.org)
orcid.api.url = https://pub.orcid.org/v3.0
orcid.url = https://orcid.org/
# ORCID Credentials
# Your public or member API Credentials, see
# https://orcid.org/content/register-client-application-0
orcid.clientid =
orcid.clientsecret =
#ORCID JWT Endpoint
orcid.oauth.url = https://orcid.org/oauth/token
## The DCInputAuthority plugin is automatically configured with every
## value-pairs element in input-forms.xml, namely:
## common_identifiers, common_types, common_iso_languages

View File

@@ -20,10 +20,17 @@
<property name="apiKey" value="${sherpa.romeo.apikey}"/>
</bean>
<bean class="org.dspace.external.provider.impl.OrcidV2AuthorDataProvider" init-method="init">
<constructor-arg value="${orcid.api.url}"/>
<property name="sourceIdentifier" value="orcidV2"/>
<bean class="org.dspace.external.provider.impl.OrcidV3AuthorDataProvider" init-method="init">
<property name="sourceIdentifier" value="orcid"/>
<property name="orcidUrl" value="${orcid.url}" />
<property name="clientId" value="${orcid.clientid}" />
<property name="clientSecret" value="${orcid.clientsecret}" />
<property name="OAUTHUrl" value="${orcid.oauth.url}" />
<property name="orcidRestConnector" ref="orcidRestConnector"/>
</bean>
<bean id="orcidRestConnector" class="org.dspace.external.OrcidRestConnector">
<constructor-arg value="${orcid.api.url}"/>
</bean>
<bean class="org.dspace.external.provider.impl.LCNameDataProvider">