Merge branch 'main' into CST-5587

This commit is contained in:
Luca Giamminonni
2022-05-20 15:55:33 +02:00
35 changed files with 3090 additions and 15 deletions

View File

@@ -10,6 +10,7 @@ package org.dspace.core;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -91,12 +92,12 @@ public class Context implements AutoCloseable {
/**
* Group IDs of special groups user is a member of
*/
private List<UUID> specialGroups;
private Set<UUID> specialGroups;
/**
* Temporary store for the specialGroups when the current user is temporary switched
*/
private List<UUID> specialGroupsPreviousState;
private Set<UUID> specialGroupsPreviousState;
/**
* The currently used authentication method
@@ -183,7 +184,7 @@ public class Context implements AutoCloseable {
extraLogInfo = "";
ignoreAuth = false;
specialGroups = new ArrayList<>();
specialGroups = new HashSet<>();
authStateChangeHistory = new ConcurrentLinkedDeque<>();
authStateClassCallHistory = new ConcurrentLinkedDeque<>();
@@ -703,7 +704,7 @@ public class Context implements AutoCloseable {
currentUserPreviousState = currentUser;
specialGroupsPreviousState = specialGroups;
specialGroups = new ArrayList<>();
specialGroups = new HashSet<>();
currentUser = newUser;
}

View File

@@ -0,0 +1,67 @@
/**
* 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.importer.external.crossref;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor;
/**
* This class is used for CrossRef's Live-Import to extract
* attributes such as "given" and "family" from the array of authors/editors
* and return them concatenated.
* Beans are configured in the crossref-integration.xml file.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class CrossRefAuthorMetadataProcessor implements JsonPathMetadataProcessor {
private final static Logger log = LogManager.getLogger();
private String pathToArray;
@Override
public Collection<String> processMetadata(String json) {
JsonNode rootNode = convertStringJsonToJsonNode(json);
Iterator<JsonNode> authors = rootNode.at(pathToArray).iterator();
Collection<String> values = new ArrayList<>();
while (authors.hasNext()) {
JsonNode author = authors.next();
String givenName = author.at("/given").textValue();
String familyName = author.at("/family").textValue();
if (StringUtils.isNoneBlank(givenName) && StringUtils.isNoneBlank(familyName)) {
values.add(givenName + " " + familyName);
}
}
return values;
}
private JsonNode convertStringJsonToJsonNode(String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode body = null;
try {
body = mapper.readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return body;
}
public void setPathToArray(String pathToArray) {
this.pathToArray = pathToArray;
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.importer.external.crossref;
import java.util.Map;
import javax.annotation.Resource;
import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping;
/**
* An implementation of {@link AbstractMetadataFieldMapping}
* Responsible for defining the mapping of the CrossRef metadatum fields on the DSpace metadatum fields
*
* @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
*/
@SuppressWarnings("rawtypes")
public class CrossRefFieldMapping extends AbstractMetadataFieldMapping {
/**
* Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
* only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
* what metadatafield is generated.
*
* @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to
* the item.
*/
@Override
@SuppressWarnings("unchecked")
@Resource(name = "crossrefMetadataFieldMap")
public void setMetadataFieldMap(Map metadataFieldMap) {
super.setMetadataFieldMap(metadataFieldMap);
}
}

View File

@@ -0,0 +1,337 @@
/**
* 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.importer.external.crossref;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import javax.el.MethodNotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.datamodel.Query;
import org.dspace.importer.external.exception.MetadataSourceException;
import org.dspace.importer.external.liveimportclient.service.LiveImportClient;
import org.dspace.importer.external.service.AbstractImportMetadataSourceService;
import org.dspace.importer.external.service.DoiCheck;
import org.dspace.importer.external.service.components.QuerySource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implements a data source for querying CrossRef
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService<String>
implements QuerySource {
private final static Logger log = LogManager.getLogger();
private String url;
@Autowired
private LiveImportClient liveImportClient;
@Override
public String getImportSource() {
return "crossref";
}
@Override
public void init() throws Exception {}
@Override
public ImportRecord getRecord(String recordId) throws MetadataSourceException {
String id = getID(recordId);
List<ImportRecord> records = StringUtils.isNotBlank(id) ? retry(new SearchByIdCallable(id))
: retry(new SearchByIdCallable(recordId));
return CollectionUtils.isEmpty(records) ? null : records.get(0);
}
@Override
public int getRecordsCount(String query) throws MetadataSourceException {
String id = getID(query);
return StringUtils.isNotBlank(id) ? retry(new DoiCheckCallable(id)) : retry(new CountByQueryCallable(query));
}
@Override
public int getRecordsCount(Query query) throws MetadataSourceException {
String id = getID(query.toString());
return StringUtils.isNotBlank(id) ? retry(new DoiCheckCallable(id)) : retry(new CountByQueryCallable(query));
}
@Override
public Collection<ImportRecord> getRecords(String query, int start, int count) throws MetadataSourceException {
String id = getID(query.toString());
return StringUtils.isNotBlank(id) ? retry(new SearchByIdCallable(id))
: retry(new SearchByQueryCallable(query, count, start));
}
@Override
public Collection<ImportRecord> getRecords(Query query) throws MetadataSourceException {
String id = getID(query.toString());
if (StringUtils.isNotBlank(id)) {
return retry(new SearchByIdCallable(id));
}
return retry(new SearchByQueryCallable(query));
}
@Override
public ImportRecord getRecord(Query query) throws MetadataSourceException {
String id = getID(query.toString());
List<ImportRecord> records = StringUtils.isNotBlank(id) ? retry(new SearchByIdCallable(id))
: retry(new SearchByIdCallable(query));
return CollectionUtils.isEmpty(records) ? null : records.get(0);
}
@Override
public Collection<ImportRecord> findMatchingRecords(Query query) throws MetadataSourceException {
String id = getID(query.toString());
return StringUtils.isNotBlank(id) ? retry(new SearchByIdCallable(id))
: retry(new FindMatchingRecordCallable(query));
}
@Override
public Collection<ImportRecord> findMatchingRecords(Item item) throws MetadataSourceException {
throw new MethodNotFoundException("This method is not implemented for CrossRef");
}
public String getID(String id) {
return DoiCheck.isDoi(id) ? "filter=doi:" + id : StringUtils.EMPTY;
}
/**
* This class is a Callable implementation to get CrossRef entries based on query object.
* This Callable use as query value the string queryString passed to constructor.
* If the object will be construct through Query.class instance, a Query's map entry with key "query" will be used.
* Pagination is supported too, using the value of the Query's map with keys "start" and "count".
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class SearchByQueryCallable implements Callable<List<ImportRecord>> {
private Query query;
private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) {
query = new Query();
query.addParameter("query", queryString);
query.addParameter("count", maxResult);
query.addParameter("start", start);
}
private SearchByQueryCallable(Query query) {
this.query = query;
}
@Override
public List<ImportRecord> call() throws Exception {
List<ImportRecord> results = new ArrayList<>();
Integer count = query.getParameterAsClass("count", Integer.class);
Integer start = query.getParameterAsClass("start", Integer.class);
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.addParameter("query", query.getParameterAsClass("query", String.class));
if (Objects.nonNull(count)) {
uriBuilder.addParameter("rows", count.toString());
}
if (Objects.nonNull(start)) {
uriBuilder.addParameter("offset", start.toString());
}
String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
JsonNode jsonNode = convertStringJsonToJsonNode(response);
Iterator<JsonNode> nodes = jsonNode.at("/message/items").iterator();
while (nodes.hasNext()) {
JsonNode node = nodes.next();
results.add(transformSourceRecords(node.toString()));
}
return results;
}
}
/**
* This class is a Callable implementation to get an CrossRef entry using DOI
* The DOI to use can be passed through the constructor as a String or as Query's map entry, with the key "id".
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class SearchByIdCallable implements Callable<List<ImportRecord>> {
private Query query;
private SearchByIdCallable(Query query) {
this.query = query;
}
private SearchByIdCallable(String id) {
this.query = new Query();
query.addParameter("id", id);
}
@Override
public List<ImportRecord> call() throws Exception {
List<ImportRecord> results = new ArrayList<>();
String ID = URLDecoder.decode(query.getParameterAsClass("id", String.class), "UTF-8");
URIBuilder uriBuilder = new URIBuilder(url + "/" + ID);
String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
JsonNode jsonNode = convertStringJsonToJsonNode(responseString);
JsonNode messageNode = jsonNode.at("/message");
results.add(transformSourceRecords(messageNode.toString()));
return results;
}
}
/**
* This class is a Callable implementation to search CrossRef entries using author and title.
* There are two field in the Query map to pass, with keys "title" and "author"
* (at least one must be used).
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class FindMatchingRecordCallable implements Callable<List<ImportRecord>> {
private Query query;
private FindMatchingRecordCallable(Query q) {
query = q;
}
@Override
public List<ImportRecord> call() throws Exception {
String queryValue = query.getParameterAsClass("query", String.class);
Integer count = query.getParameterAsClass("count", Integer.class);
Integer start = query.getParameterAsClass("start", Integer.class);
String author = query.getParameterAsClass("author", String.class);
String title = query.getParameterAsClass("title", String.class);
String bibliographics = query.getParameterAsClass("bibliographics", String.class);
List<ImportRecord> results = new ArrayList<>();
URIBuilder uriBuilder = new URIBuilder(url);
if (Objects.nonNull(queryValue)) {
uriBuilder.addParameter("query", queryValue);
}
if (Objects.nonNull(count)) {
uriBuilder.addParameter("rows", count.toString());
}
if (Objects.nonNull(start)) {
uriBuilder.addParameter("offset", start.toString());
}
if (Objects.nonNull(author)) {
uriBuilder.addParameter("query.author", author);
}
if (Objects.nonNull(title )) {
uriBuilder.addParameter("query.container-title", title);
}
if (Objects.nonNull(bibliographics)) {
uriBuilder.addParameter("query.bibliographic", bibliographics);
}
String resp = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
JsonNode jsonNode = convertStringJsonToJsonNode(resp);
Iterator<JsonNode> nodes = jsonNode.at("/message/items").iterator();
while (nodes.hasNext()) {
JsonNode node = nodes.next();
results.add(transformSourceRecords(node.toString()));
}
return results;
}
}
/**
* This class is a Callable implementation to count the number of entries for an CrossRef query.
* This Callable use as query value to CrossRef the string queryString passed to constructor.
* If the object will be construct through Query.class instance, the value of the Query's
* map with the key "query" will be used.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class CountByQueryCallable implements Callable<Integer> {
private Query query;
private CountByQueryCallable(String queryString) {
query = new Query();
query.addParameter("query", queryString);
}
private CountByQueryCallable(Query query) {
this.query = query;
}
@Override
public Integer call() throws Exception {
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.addParameter("query", query.getParameterAsClass("query", String.class));
String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
JsonNode jsonNode = convertStringJsonToJsonNode(responseString);
return jsonNode.at("/message/total-results").asInt();
}
}
/**
* This class is a Callable implementation to check if exist an CrossRef entry using DOI.
* The DOI to use can be passed through the constructor as a String or as Query's map entry, with the key "id".
* return 1 if CrossRef entry exists otherwise 0
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class DoiCheckCallable implements Callable<Integer> {
private final Query query;
private DoiCheckCallable(final String id) {
final Query query = new Query();
query.addParameter("id", id);
this.query = query;
}
private DoiCheckCallable(final Query query) {
this.query = query;
}
@Override
public Integer call() throws Exception {
URIBuilder uriBuilder = new URIBuilder(url + "/" + query.getParameterAsClass("id", String.class));
String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
JsonNode jsonNode = convertStringJsonToJsonNode(responseString);
return StringUtils.equals(jsonNode.at("/status").toString(), "ok") ? 1 : 0;
}
}
private JsonNode convertStringJsonToJsonNode(String json) {
try {
return new ObjectMapper().readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return null;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@@ -0,0 +1,21 @@
/**
* 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.importer.external.liveimportclient.service;
import java.util.Map;
/**
* Interface for classes that allow to contact LiveImport clients.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public interface LiveImportClient {
public String executeHttpGetRequest(int timeout, String URL, Map<String, String> requestParams);
}

View File

@@ -0,0 +1,113 @@
/**
* 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.importer.external.liveimportclient.service;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link LiveImportClient}.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science dot com)
*/
public class LiveImportClientImpl implements LiveImportClient {
private final static Logger log = LogManager.getLogger();
private CloseableHttpClient httpClient;
@Autowired
private ConfigurationService configurationService;
@Override
public String executeHttpGetRequest(int timeout, String URL, Map<String, String> requestParams) {
HttpGet method = null;
try (CloseableHttpClient httpClient = Optional.ofNullable(this.httpClient)
.orElseGet(HttpClients::createDefault)) {
Builder requestConfigBuilder = RequestConfig.custom();
requestConfigBuilder.setConnectionRequestTimeout(timeout);
RequestConfig defaultRequestConfig = requestConfigBuilder.build();
method = new HttpGet(getSearchUrl(URL, requestParams));
method.setConfig(defaultRequestConfig);
configureProxy(method, defaultRequestConfig);
HttpResponse httpResponse = httpClient.execute(method);
if (isNotSuccessfull(httpResponse)) {
throw new RuntimeException("The request failed with: " + getStatusCode(httpResponse) + " code");
}
InputStream inputStream = httpResponse.getEntity().getContent();
return IOUtils.toString(inputStream, Charset.defaultCharset());
} catch (Exception e1) {
log.error(e1.getMessage(), e1);
} finally {
if (Objects.nonNull(method)) {
method.releaseConnection();
}
}
return StringUtils.EMPTY;
}
private void configureProxy(HttpGet method, RequestConfig defaultRequestConfig) {
String proxyHost = configurationService.getProperty("http.proxy.host");
String proxyPort = configurationService.getProperty("http.proxy.port");
if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) {
RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig)
.setProxy(new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"))
.build();
method.setConfig(requestConfig);
}
}
private String getSearchUrl(String URL, Map<String, String> requestParams) throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(URL);
for (String param : requestParams.keySet()) {
uriBuilder.setParameter(param, requestParams.get(param));
}
return uriBuilder.toString();
}
private boolean isNotSuccessfull(HttpResponse response) {
int statusCode = getStatusCode(response);
return statusCode < 200 || statusCode > 299;
}
private int getStatusCode(HttpResponse response) {
return response.getStatusLine().getStatusCode();
}
public CloseableHttpClient getHttpClient() {
return httpClient;
}
public void setHttpClient(CloseableHttpClient httpClient) {
this.httpClient = httpClient;
}
}

View File

@@ -0,0 +1,82 @@
/**
* 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.importer.external.metadatamapping.contributor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This Processor allows to extract attribute values of an array.
* For exaple to extract all values of secondAttribute,
* "array":[
* {
* "firstAttribute":"first value",
* "secondAttribute":"second value"
* },
* {
* "firstAttribute":"first value",
* "secondAttribute":"second value"
* }
* ]
*
* it's possible configure a bean with
* pathToArray=/array and elementAttribute=/secondAttribute
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class ArrayElementAttributeProcessor implements JsonPathMetadataProcessor {
private final static Logger log = LogManager.getLogger();
private String pathToArray;
private String elementAttribute;
@Override
public Collection<String> processMetadata(String json) {
JsonNode rootNode = convertStringJsonToJsonNode(json);
Iterator<JsonNode> array = rootNode.at(pathToArray).iterator();
Collection<String> values = new ArrayList<>();
while (array.hasNext()) {
JsonNode element = array.next();
String value = element.at(elementAttribute).textValue();
if (StringUtils.isNoneBlank(value)) {
values.add(value);
}
}
return values;
}
private JsonNode convertStringJsonToJsonNode(String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode body = null;
try {
body = mapper.readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return body;
}
public void setPathToArray(String pathToArray) {
this.pathToArray = pathToArray;
}
public void setElementAttribute(String elementAttribute) {
this.elementAttribute = elementAttribute;
}
}

View File

@@ -0,0 +1,23 @@
/**
* 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.importer.external.metadatamapping.contributor;
import java.util.Collection;
/**
* Service interface class for processing json object.
* The implementation of this class is responsible for all business logic calls
* for extracting of values from json object.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public interface JsonPathMetadataProcessor {
public Collection<String> processMetadata(String json);
}

View File

@@ -0,0 +1,87 @@
/**
* 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.importer.external.metadatamapping.contributor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This Processor allows to extract all values of a matrix.
* Only need to configure the path to the matrix in "pathToMatrix"
* For exaple to extract all values
* "matrix": [
* [
* "first",
* "second"
* ],
* [
* "third"
* ],
* [
* "fourth",
* "fifth"
* ]
* ],
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class MatrixElementProcessor implements JsonPathMetadataProcessor {
private final static Logger log = LogManager.getLogger();
private String pathToMatrix;
@Override
public Collection<String> processMetadata(String json) {
JsonNode rootNode = convertStringJsonToJsonNode(json);
Iterator<JsonNode> array = rootNode.at(pathToMatrix).elements();
Collection<String> values = new ArrayList<>();
while (array.hasNext()) {
JsonNode element = array.next();
if (element.isArray()) {
Iterator<JsonNode> nodes = element.iterator();
while (nodes.hasNext()) {
String nodeValue = nodes.next().textValue();
if (StringUtils.isNotBlank(nodeValue)) {
values.add(nodeValue);
}
}
} else {
String nodeValue = element.textValue();
if (StringUtils.isNotBlank(nodeValue)) {
values.add(nodeValue);
}
}
}
return values;
}
private JsonNode convertStringJsonToJsonNode(String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode body = null;
try {
body = mapper.readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return body;
}
public void setPathToMatrix(String pathToMatrix) {
this.pathToMatrix = pathToMatrix;
}
}

View File

@@ -0,0 +1,181 @@
/**
* 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.importer.external.metadatamapping.contributor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.importer.external.metadatamapping.MetadataFieldConfig;
import org.dspace.importer.external.metadatamapping.MetadataFieldMapping;
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
/**
* A simple JsonPath Metadata processor
* that allow extract value from json object
* by configuring the path in the query variable via the bean.
* moreover this can also perform more compact extractions
* by configuring specific json processor in "metadataProcessor"
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class SimpleJsonPathMetadataContributor implements MetadataContributor<String> {
private final static Logger log = LogManager.getLogger();
private String query;
private MetadataFieldConfig field;
protected JsonPathMetadataProcessor metadataProcessor;
/**
* Initialize SimpleJsonPathMetadataContributor with a query, prefixToNamespaceMapping and MetadataFieldConfig
*
* @param query The JSonPath query
* @param field the matadata field to map the result of the Json path query
* <a href="https://github.com/DSpace/DSpace/tree/master/dspace-api/src/main/java/org/dspace/importer/external#metadata-mapping-">MetadataFieldConfig</a>
*/
public SimpleJsonPathMetadataContributor(String query, MetadataFieldConfig field) {
this.query = query;
this.field = field;
}
/**
* Unused by this implementation
*/
@Override
public void setMetadataFieldMapping(MetadataFieldMapping<String, MetadataContributor<String>> rt) {
}
/**
* Empty constructor for SimpleJsonPathMetadataContributor
*/
public SimpleJsonPathMetadataContributor() {
}
/**
* Return the MetadataFieldConfig used while retrieving MetadatumDTO
*
* @return MetadataFieldConfig
*/
public MetadataFieldConfig getField() {
return field;
}
/**
* Setting the MetadataFieldConfig
*
* @param field MetadataFieldConfig used while retrieving MetadatumDTO
*/
public void setField(MetadataFieldConfig field) {
this.field = field;
}
/**
* Return query used to create the JSonPath
*
* @return the query this instance is based on
*/
public String getQuery() {
return query;
}
/**
* Return query used to create the JSonPath
*
*/
public void setQuery(String query) {
this.query = query;
}
/**
* Used to process data got by jsonpath expression, like arrays to stringify, change date format or else
* If it is null, toString will be used.
*
* @param metadataProcessor
*/
public void setMetadataProcessor(JsonPathMetadataProcessor metadataProcessor) {
this.metadataProcessor = metadataProcessor;
}
/**
* Retrieve the metadata associated with the given object.
* The toString() of the resulting object will be used.
*
* @param t A class to retrieve metadata from.
* @return a collection of import records. Only the identifier of the found records may be put in the record.
*/
@Override
public Collection<MetadatumDTO> contributeMetadata(String fullJson) {
Collection<MetadatumDTO> metadata = new ArrayList<>();
Collection<String> metadataValue = new ArrayList<>();
if (Objects.nonNull(metadataProcessor)) {
metadataValue = metadataProcessor.processMetadata(fullJson);
} else {
JsonNode jsonNode = convertStringJsonToJsonNode(fullJson);
JsonNode node = jsonNode.at(query);
if (node.isArray()) {
Iterator<JsonNode> nodes = node.iterator();
while (nodes.hasNext()) {
String nodeValue = getStringValue(nodes.next());
if (StringUtils.isNotBlank(nodeValue)) {
metadataValue.add(nodeValue);
}
}
} else if (!node.isNull() && StringUtils.isNotBlank(node.toString())) {
String nodeValue = getStringValue(node);
if (StringUtils.isNotBlank(nodeValue)) {
metadataValue.add(nodeValue);
}
}
}
for (String value : metadataValue) {
MetadatumDTO metadatumDto = new MetadatumDTO();
metadatumDto.setValue(value);
metadatumDto.setElement(field.getElement());
metadatumDto.setQualifier(field.getQualifier());
metadatumDto.setSchema(field.getSchema());
metadata.add(metadatumDto);
}
return metadata;
}
private String getStringValue(JsonNode node) {
if (node.isTextual()) {
return node.textValue();
}
if (node.isNumber()) {
return node.numberValue().toString();
}
log.error("It wasn't possible to convert the value of the following JsonNode:" + node.asText());
return StringUtils.EMPTY;
}
private JsonNode convertStringJsonToJsonNode(String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode body = null;
try {
body = mapper.readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return body;
}
}

View File

@@ -0,0 +1,47 @@
/**
* 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.importer.external.service;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class that provides methods to check if a given string is a DOI and exists on CrossRef services
*
* @author Corrado Lombardi (corrado.lombardi at 4science.it)
*/
public class DoiCheck {
private static final List<String> DOI_PREFIXES = Arrays.asList("http://dx.doi.org/", "https://dx.doi.org/");
private static final Pattern PATTERN = Pattern.compile("10.\\d{4,9}/[-._;()/:A-Z0-9]+" +
"|10.1002/[^\\s]+" +
"|10.\\d{4}/\\d+-\\d+X?(\\d+)" +
"\\d+<[\\d\\w]+:[\\d\\w]*>\\d+.\\d+.\\w+;\\d" +
"|10.1021/\\w\\w\\d++" +
"|10.1207/[\\w\\d]+\\&\\d+_\\d+",
Pattern.CASE_INSENSITIVE);
private DoiCheck() {}
public static boolean isDoi(final String value) {
Matcher m = PATTERN.matcher(purgeDoiValue(value));
return m.matches();
}
public static String purgeDoiValue(final String query) {
String value = query.replaceAll(",", "");
for (final String prefix : DOI_PREFIXES) {
value = value.replaceAll(prefix, "");
}
return value.trim();
}
}

View File

@@ -0,0 +1,336 @@
/**
* 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.importer.external.vufind;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import javax.el.MethodNotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.datamodel.Query;
import org.dspace.importer.external.exception.MetadataSourceException;
import org.dspace.importer.external.liveimportclient.service.LiveImportClient;
import org.dspace.importer.external.service.AbstractImportMetadataSourceService;
import org.dspace.importer.external.service.components.QuerySource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implements a data source for querying VuFind
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class VuFindImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService<String>
implements QuerySource {
private final static Logger log = LogManager.getLogger();
private String url;
private String urlSearch;
private String fields;
@Autowired
private LiveImportClient liveImportClient;
public VuFindImportMetadataSourceServiceImpl(String fields) {
this.fields = fields;
}
@Override
public String getImportSource() {
return "VuFind";
}
@Override
public ImportRecord getRecord(String id) throws MetadataSourceException {
String records = retry(new GetByVuFindIdCallable(id, fields));
List<ImportRecord> importRecords = extractMetadataFromRecordList(records);
return importRecords != null && !importRecords.isEmpty() ? importRecords.get(0) : null;
}
@Override
public int getRecordsCount(String query) throws MetadataSourceException {
return retry(new CountByQueryCallable(query));
}
@Override
public int getRecordsCount(Query query) throws MetadataSourceException {
return retry(new CountByQueryCallable(query));
}
@Override
public Collection<ImportRecord> getRecords(String query, int start, int count) throws MetadataSourceException {
String records = retry(new SearchByQueryCallable(query, count, start, fields));
return extractMetadataFromRecordList(records);
}
@Override
public Collection<ImportRecord> getRecords(Query query) throws MetadataSourceException {
String records = retry(new SearchByQueryCallable(query, fields));
return extractMetadataFromRecordList(records);
}
@Override
public ImportRecord getRecord(Query query) throws MetadataSourceException {
String records = retry(new SearchByQueryCallable(query, fields));
List<ImportRecord> importRecords = extractMetadataFromRecordList(records);
return importRecords != null && !importRecords.isEmpty() ? importRecords.get(0) : null;
}
@Override
public Collection<ImportRecord> findMatchingRecords(Query query) throws MetadataSourceException {
String records = retry(new FindMatchingRecordsCallable(query));
return extractMetadataFromRecordList(records);
}
@Override
public Collection<ImportRecord> findMatchingRecords(Item item) throws MetadataSourceException {
throw new MethodNotFoundException("This method is not implemented for VuFind");
}
@Override
public void init() throws Exception {}
/**
* This class is a Callable implementation to count the number of entries for an VuFind query.
* This Callable use as query value to CrossRef the string queryString passed to constructor.
* If the object will be construct through Query.class instance, the value of the Query's
* map with the key "query" will be used.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class CountByQueryCallable implements Callable<Integer> {
private Query query;
public CountByQueryCallable(String queryString) {
query = new Query();
query.addParameter("query", queryString);
}
public CountByQueryCallable(Query query) {
this.query = query;
}
@Override
public Integer call() throws Exception {
Integer start = 0;
Integer count = 1;
int page = start / count + 1;
URIBuilder uriBuilder = new URIBuilder(urlSearch);
uriBuilder.addParameter("type", "AllField");
uriBuilder.addParameter("page", String.valueOf(page));
uriBuilder.addParameter("limit", count.toString());
uriBuilder.addParameter("prettyPrint", String.valueOf(true));
uriBuilder.addParameter("lookfor", query.getParameterAsClass("query", String.class));
String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
JsonNode node = convertStringJsonToJsonNode(responseString);
JsonNode resultCountNode = node.get("resultCount");
return resultCountNode.intValue();
}
}
/**
* This class is a Callable implementation to get an VuFind entry using VuFind id
* The id to use can be passed through the constructor as a String or as Query's map entry, with the key "id".
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class GetByVuFindIdCallable implements Callable<String> {
private String id;
private String fields;
public GetByVuFindIdCallable(String id, String fields) {
this.id = id;
if (fields != null && fields.length() > 0) {
this.fields = fields;
} else {
this.fields = null;
}
}
@Override
public String call() throws Exception {
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.addParameter("id", id);
uriBuilder.addParameter("prettyPrint", "false");
if (StringUtils.isNotBlank(fields)) {
for (String field : fields.split(",")) {
uriBuilder.addParameter("field[]", field);
}
}
String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(),
new HashMap<String, String>());
return response;
}
}
/**
* This class is a Callable implementation to get VuFind entries based on query object.
* This Callable use as query value the string queryString passed to constructor.
* If the object will be construct through Query.class instance, a Query's map entry with key "query" will be used.
* Pagination is supported too, using the value of the Query's map with keys "start" and "count".
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
private class SearchByQueryCallable implements Callable<String> {
private Query query;
private String fields;
public SearchByQueryCallable(String queryString, Integer maxResult, Integer start, String fields) {
query = new Query();
query.addParameter("query", queryString);
query.addParameter("count", maxResult);
query.addParameter("start", start);
if (StringUtils.isNotBlank(fields)) {
this.fields = fields;
} else {
this.fields = null;
}
}
public SearchByQueryCallable(Query query, String fields) {
this.query = query;
if (StringUtils.isNotBlank(fields)) {
this.fields = fields;
} else {
this.fields = null;
}
}
@Override
public String call() throws Exception {
Integer start = query.getParameterAsClass("start", Integer.class);
Integer count = query.getParameterAsClass("count", Integer.class);
int page = count != 0 ? start / count : 0;
URIBuilder uriBuilder = new URIBuilder(urlSearch);
uriBuilder.addParameter("type", "AllField");
//page looks 1 based (start = 0, count = 20 -> page = 0)
uriBuilder.addParameter("page", String.valueOf(page + 1));
uriBuilder.addParameter("limit", count.toString());
uriBuilder.addParameter("prettyPrint", String.valueOf(true));
uriBuilder.addParameter("lookfor", query.getParameterAsClass("query", String.class));
if (StringUtils.isNotBlank(fields)) {
for (String field : fields.split(",")) {
uriBuilder.addParameter("field[]", field);
}
}
return liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), new HashMap<String, String>());
}
}
/**
* This class is a Callable implementation to search VuFind entries using author and title.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class FindMatchingRecordsCallable implements Callable<String> {
private Query query;
private String fields;
public FindMatchingRecordsCallable(Query query) {
this.query = query;
}
@Override
public String call() throws Exception {
String author = query.getParameterAsClass("author", String.class);
String title = query.getParameterAsClass("title", String.class);
Integer start = query.getParameterAsClass("start", Integer.class);
Integer count = query.getParameterAsClass("count", Integer.class);
int page = count != 0 ? start / count : 0;
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.addParameter("type", "AllField");
//pagination is 1 based (first page: start = 0, count = 20 -> page = 0 -> +1 = 1)
uriBuilder.addParameter("page", String.valueOf(page ++));
uriBuilder.addParameter("limit", count.toString());
uriBuilder.addParameter("prettyPrint", "true");
if (fields != null && !fields.isEmpty()) {
for (String field : fields.split(",")) {
uriBuilder.addParameter("field[]", field);
}
}
String filter = StringUtils.EMPTY;
if (StringUtils.isNotBlank(author)) {
filter = "author:" + author;
}
if (StringUtils.isNotBlank(title)) {
if (StringUtils.isNotBlank(filter)) {
filter = filter + " AND title:" + title;
} else {
filter = "title:" + title;
}
}
uriBuilder.addParameter("lookfor", filter);
return liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), new HashMap<String, String>());
}
}
private JsonNode convertStringJsonToJsonNode(String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode body = null;
try {
body = mapper.readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return body;
}
private List<ImportRecord> extractMetadataFromRecordList(String records) {
List<ImportRecord> recordsResult = new ArrayList<>();
JsonNode jsonNode = convertStringJsonToJsonNode(records);
JsonNode node = jsonNode.get("records");
if (Objects.nonNull(node) && node.isArray()) {
Iterator<JsonNode> nodes = node.iterator();
while (nodes.hasNext()) {
recordsResult.add(transformSourceRecords(nodes.next().toString()));
}
}
return recordsResult;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUrlSearch() {
return urlSearch;
}
public void setUrlSearch(String urlSearch) {
this.urlSearch = urlSearch;
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.importer.external.vufind.metadatamapping;
import java.util.Map;
import javax.annotation.Resource;
import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping;
/**
* An implementation of {@link AbstractMetadataFieldMapping}
* Responsible for defining the mapping of the VuFind metadatum fields on the DSpace metadatum fields
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
@SuppressWarnings("rawtypes")
public class VuFindFieldMapping extends AbstractMetadataFieldMapping {
/**
* Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
* only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
* what metadatafield is generated.
*
* @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to
* the item.
*/
@Override
@SuppressWarnings("unchecked")
@Resource(name = "vufindMetadataFieldMap")
public void setMetadataFieldMap(Map metadataFieldMap) {
super.setMetadataFieldMap(metadataFieldMap);
}
}

View File

@@ -115,6 +115,20 @@
</property>
</bean>
<bean id="CrossRefImportService" class="org.dspace.importer.external.crossref.CrossRefImportMetadataSourceServiceImpl" scope="singleton">
<property name="metadataFieldMapping" ref="CrossRefMetadataFieldMapping"/>
<property name="url" value="${crossref.url}"/>
</bean>
<bean id="CrossRefMetadataFieldMapping" class="org.dspace.importer.external.crossref.CrossRefFieldMapping"/>
<bean id="vufindImportService" class="org.dspace.importer.external.vufind.VuFindImportMetadataSourceServiceImpl" scope="singleton">
<!-- Set to empty to use the default set of fields -->
<constructor-arg type="java.lang.String" value=""/>
<property name="metadataFieldMapping" ref="vufindMetadataFieldMapping"/>
<property name="url" value="${vufind.url}"/>
<property name="urlSearch" value="${vufind.url.search}"/>
</bean>
<bean id="vufindMetadataFieldMapping" class="org.dspace.importer.external.vufind.metadatamapping.VuFindFieldMapping"/>
<!-- Metadatafield used to check against if it's already imported or not during the JSONLookupSearcher-->
<bean id="lookupID" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">

View File

@@ -6,6 +6,8 @@
<bean class="org.dspace.external.service.impl.ExternalDataServiceImpl"/>
<bean class="org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl"/>
<bean class="org.dspace.external.provider.impl.MockDataProvider" init-method="init">
<property name="sourceIdentifier" value="mock"/>
</bean>

View File

@@ -8,6 +8,7 @@
package org.dspace.core;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -511,9 +512,8 @@ public class ContextTest extends AbstractUnitTest {
// Now get our special groups
List<Group> specialGroups = instance.getSpecialGroups();
assertThat("testGetSpecialGroup 0", specialGroups.size(), equalTo(2));
assertThat("testGetSpecialGroup 1", specialGroups.get(0), equalTo(group));
assertThat("testGetSpecialGroup 1", specialGroups.get(1), equalTo(adminGroup));
assertThat("testGetSpecialGroup size", specialGroups.size(), equalTo(2));
assertThat("testGetSpecialGroup content", specialGroups, hasItems(group, adminGroup));
// Cleanup our context & group
groupService.delete(instance, group);

View File

@@ -0,0 +1,65 @@
/**
* 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.util;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.text.ParseException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.dspace.importer.external.service.DoiCheck;
import org.junit.Test;
/**
* Test class for the DoiCheck
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class DoiCheckTest {
@Test
public void checkDOIsTest() throws ParseException {
for (String doi : DOIsToTest()) {
assertTrue("The: " + doi + " is a doi!", DoiCheck.isDoi(doi));
}
}
@Test
public void checkWrongDOIsTest() throws ParseException {
for (String key : wrongDOIsToTest()) {
assertFalse("This : " + key + " isn't a doi!", DoiCheck.isDoi(key));
}
}
private List<String> DOIsToTest() {
return Arrays.asList(
"10.1430/8105",
"10.1038/nphys1170",
"10.1002/0470841559.ch1",
"10.1594/PANGAEA.726855",
"10.1594/GFZ.GEOFON.gfz2009kciu",
"10.3866/PKU.WHXB201112303",
"10.11467/isss2003.7.1_11",
"10.3972/water973.0145.db"
);
}
private List<String> wrongDOIsToTest() {
return Arrays.asList(
StringUtils.EMPTY,
"123456789",
"nphys1170/10.1038",
"10.", "10",
"10.1038/"
);
}
}

View File

@@ -7,8 +7,13 @@
*/
package org.dspace.app.rest;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -19,9 +24,11 @@ import org.dspace.app.rest.model.AuthenticationStatusRest;
import org.dspace.app.rest.model.AuthenticationTokenRest;
import org.dspace.app.rest.model.AuthnRest;
import org.dspace.app.rest.model.EPersonRest;
import org.dspace.app.rest.model.GroupRest;
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource;
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
import org.dspace.app.rest.model.hateoas.AuthnResource;
import org.dspace.app.rest.model.hateoas.EmbeddedPage;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.security.RestAuthenticationService;
@@ -34,6 +41,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -109,6 +120,8 @@ public class AuthenticationRestController implements InitializingBean {
if (context.getCurrentUser() != null) {
ePersonRest = converter.toRest(context.getCurrentUser(), projection);
}
List<GroupRest> groupList = context.getSpecialGroups().stream()
.map(g -> (GroupRest) converter.toRest(g, projection)).collect(Collectors.toList());
AuthenticationStatusRest authenticationStatusRest = new AuthenticationStatusRest(ePersonRest);
// When not authenticated add WWW-Authenticate so client can retrieve all available authentication methods
@@ -120,11 +133,41 @@ public class AuthenticationRestController implements InitializingBean {
}
authenticationStatusRest.setAuthenticationMethod(context.getAuthenticationMethod());
authenticationStatusRest.setProjection(projection);
authenticationStatusRest.setSpecialGroups(groupList);
AuthenticationStatusResource authenticationStatusResource = converter.toResource(authenticationStatusRest);
return authenticationStatusResource;
}
/**
* Check the current user's authentication status (i.e. whether they are authenticated or not) and,
* if authenticated, retrieves the current context's special groups.
* @param page
* @param assembler
* @param request
* @param response
* @return
* @throws SQLException
*/
@RequestMapping(value = "/status/specialGroups", method = RequestMethod.GET)
public EntityModel retrieveSpecialGroups(Pageable page, PagedResourcesAssembler assembler,
HttpServletRequest request, HttpServletResponse response)
throws SQLException {
Context context = ContextUtil.obtainContext(request);
Projection projection = utils.obtainProjection();
List<GroupRest> groupList = context.getSpecialGroups().stream()
.map(g -> (GroupRest) converter.toRest(g, projection)).collect(Collectors.toList());
Page<GroupRest> groupPage = (Page<GroupRest>) utils.getPage(groupList, page);
Link link = linkTo(
methodOn(AuthenticationRestController.class).retrieveSpecialGroups(page, assembler, request, response))
.withSelfRel();
return EntityModel.of(new EmbeddedPage(link.getHref(),
groupPage.map(converter::toResource), null, "specialGroups"));
}
/**
* Check whether the login has succeeded or not. The actual login is performed by one of the enabled login filters
* (e.g. {@link org.dspace.app.rest.security.StatelessLoginFilter}).

View File

@@ -7,9 +7,12 @@
*/
package org.dspace.app.rest.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.dspace.app.rest.RestResourceController;
/**
* Find out your authentication status.
*/
@@ -18,7 +21,11 @@ public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
private boolean authenticated;
private String authenticationMethod;
private EPersonRest ePersonRest;
private List<GroupRest> specialGroups;
public static final String NAME = "status";
public static final String SPECIALGROUPS = "specialGroups";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
@Override
@@ -41,9 +48,6 @@ public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
return RestResourceController.class;
}
private EPersonRest ePersonRest;
public AuthenticationStatusRest() {
setOkay(true);
setAuthenticated(false);
@@ -90,4 +94,14 @@ public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
public void setAuthenticationMethod(final String authenticationMethod) {
this.authenticationMethod = authenticationMethod;
}
public void setSpecialGroups(List<GroupRest> groupList) {
this.specialGroups = groupList;
}
@LinkRest(name = "specialGroups")
@JsonIgnore
public List<GroupRest> getSpecialGroups() {
return specialGroups;
}
}

View File

@@ -68,6 +68,11 @@ public class GroupRestPermissionEvaluatorPlugin extends RestObjectPermissionEval
Group group = groupService.find(context, dsoId);
// if the group is one of the special groups of the context it is readable
if (context.getSpecialGroups().contains(group)) {
return true;
}
// anonymous user
if (ePerson == null) {
return false;

View File

@@ -0,0 +1,100 @@
/**
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.tools.ant.filters.StringInputStream;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
/**
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class AbstractLiveImportIntegrationTest extends AbstractControllerIntegrationTest {
protected void matchRecords(ArrayList<ImportRecord> recordsImported, ArrayList<ImportRecord> records2match) {
assertEquals(records2match.size(), recordsImported.size());
for (int i = 0; i < recordsImported.size(); i++) {
ImportRecord firstImported = recordsImported.get(i);
ImportRecord first2match = records2match.get(i);
checkMetadataValue(firstImported.getValueList(), first2match.getValueList());
}
}
private void checkMetadataValue(List<MetadatumDTO> list, List<MetadatumDTO> list2) {
assertEquals(list.size(), list2.size());
for (int i = 0; i < list.size(); i++) {
assertTrue(sameMetadatum(list.get(i), list2.get(i)));
}
}
private boolean sameMetadatum(MetadatumDTO metadatum, MetadatumDTO metadatum2) {
if (StringUtils.equals(metadatum.getSchema(), metadatum2.getSchema()) &&
StringUtils.equals(metadatum.getElement(), metadatum2.getElement()) &&
StringUtils.equals(metadatum.getQualifier(), metadatum2.getQualifier()) &&
StringUtils.equals(metadatum.getValue(), metadatum2.getValue())) {
return true;
}
return false;
}
protected MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) {
MetadatumDTO metadatumDTO = new MetadatumDTO();
metadatumDTO.setSchema(schema);
metadatumDTO.setElement(element);
metadatumDTO.setQualifier(qualifier);
metadatumDTO.setValue(value);
return metadatumDTO;
}
protected CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason)
throws UnsupportedEncodingException {
BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
basicHttpEntity.setChunked(true);
basicHttpEntity.setContent(new StringInputStream(xmlExample));
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason));
when(response.getEntity()).thenReturn(basicHttpEntity);
return response;
}
protected StatusLine statusLine(int statusCode, String reason) {
return new StatusLine() {
@Override
public ProtocolVersion getProtocolVersion() {
return new ProtocolVersion("http", 1, 1);
}
@Override
public int getStatusCode() {
return statusCode;
}
@Override
public String getReasonPhrase() {
return reason;
}
};
}
}

View File

@@ -53,6 +53,7 @@ import org.dspace.app.rest.converter.EPersonConverter;
import org.dspace.app.rest.matcher.AuthenticationStatusMatcher;
import org.dspace.app.rest.matcher.AuthorizationMatcher;
import org.dspace.app.rest.matcher.EPersonMatcher;
import org.dspace.app.rest.matcher.GroupMatcher;
import org.dspace.app.rest.matcher.HalMatcher;
import org.dspace.app.rest.model.AuthnRest;
import org.dspace.app.rest.model.EPersonRest;
@@ -120,6 +121,10 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
"org.dspace.authenticate.IPAuthentication",
"org.dspace.authenticate.ShibAuthentication"
};
public static final String[] PASS_AND_IP = {
"org.dspace.authenticate.PasswordAuthentication",
"org.dspace.authenticate.IPAuthentication"
};
// see proxies.trusted.ipranges in local.cfg
public static final String TRUSTED_IP = "7.7.7.7";
@@ -174,6 +179,101 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
.andExpect(status().isNoContent());
}
/**
* This test verifies:
* - that a logged in via password user finds the expected specialGroupPwd in _embedded.specialGroups;
* - that a logged in via password and specific IP user finds the expected specialGroupPwd and specialGroupIP
* in _embedded.specialGroups;
* - that a not logged in user with a specific IP finds the expected specialGroupIP in _embedded.specialGroups;
* @throws Exception
*/
@Test
public void testStatusGetSpecialGroups() throws Exception {
context.turnOffAuthorisationSystem();
Group specialGroupPwd = GroupBuilder.createGroup(context)
.withName("specialGroupPwd")
.build();
Group specialGroupIP = GroupBuilder.createGroup(context)
.withName("specialGroupIP")
.build();
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", PASS_AND_IP);
configurationService.setProperty("authentication-password.login.specialgroup","specialGroupPwd");
configurationService.setProperty("authentication-ip.specialGroupIP", "123.123.123.123");
context.restoreAuthSystemState();
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/authn/status").param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", AuthenticationStatusMatcher.matchFullEmbeds()))
.andExpect(jsonPath("$", AuthenticationStatusMatcher.matchLinks()))
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(true)))
.andExpect(jsonPath("$.authenticationMethod", is("password")))
.andExpect(jsonPath("$.type", is("status")))
.andExpect(jsonPath("$._links.specialGroups.href", startsWith(REST_SERVER_URL)))
.andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups",
Matchers.containsInAnyOrder(
GroupMatcher.matchGroupWithName("specialGroupPwd"))));
// try the special groups link endpoint in the same scenario than above
getClient(token).perform(get("/api/authn/status/specialGroups").param("projection", "full"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.specialGroups",
Matchers.containsInAnyOrder(
GroupMatcher.matchGroupWithName("specialGroupPwd"))));
getClient(token).perform(get("/api/authn/status").param("projection", "full")
.with(ip("123.123.123.123")))
.andExpect(status().isOk())
.andExpect(jsonPath("$", AuthenticationStatusMatcher.matchFullEmbeds()))
.andExpect(jsonPath("$", AuthenticationStatusMatcher.matchLinks()))
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(true)))
.andExpect(jsonPath("$.authenticationMethod", is("password")))
.andExpect(jsonPath("$.type", is("status")))
.andExpect(jsonPath("$._links.specialGroups.href", startsWith(REST_SERVER_URL)))
.andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups",
Matchers.containsInAnyOrder(
GroupMatcher.matchGroupWithName("specialGroupPwd"),
GroupMatcher.matchGroupWithName("specialGroupIP"))));
// try the special groups link endpoint in the same scenario than above
getClient(token).perform(get("/api/authn/status/specialGroups").param("projection", "full")
.with(ip("123.123.123.123")))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.specialGroups",
Matchers.containsInAnyOrder(
GroupMatcher.matchGroupWithName("specialGroupPwd"),
GroupMatcher.matchGroupWithName("specialGroupIP"))));
getClient().perform(get("/api/authn/status").param("projection", "full").with(ip("123.123.123.123")))
.andExpect(status().isOk())
.andExpect(jsonPath("$", AuthenticationStatusMatcher.matchFullEmbeds()))
// fails due to bug https://github.com/DSpace/DSpace/issues/8274
//.andExpect(jsonPath("$", AuthenticationStatusMatcher.matchLinks()))
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(false)))
.andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups",
Matchers.containsInAnyOrder(GroupMatcher.matchGroupWithName("specialGroupIP"))));
// try the special groups link endpoint in the same scenario than above
getClient().perform(get("/api/authn/status/specialGroups").param("projection", "full")
.with(ip("123.123.123.123")))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.specialGroups",
Matchers.containsInAnyOrder(
GroupMatcher.matchGroupWithName("specialGroupIP"))));
}
@Test
@Ignore
// Ignored until an endpoint is added to return all groups. Anonymous is not considered a direct group.

View File

@@ -0,0 +1,199 @@
/**
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.el.MethodNotFoundException;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Item;
import org.dspace.importer.external.crossref.CrossRefImportMetadataSourceServiceImpl;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl;
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link CrossRefImportMetadataSourceServiceImpl}
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest {
@Autowired
private LiveImportClientImpl liveImportClientImpl;
@Autowired
private CrossRefImportMetadataSourceServiceImpl crossRefServiceImpl;
@Test
public void crossRefImportMetadataGetRecordsTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream crossRefResp = getClass().getResourceAsStream("crossRef-test.json")) {
String crossRefRespXmlResp = IOUtils.toString(crossRefResp, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(crossRefRespXmlResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
ArrayList<ImportRecord> collection2match = getRecords();
Collection<ImportRecord> recordsImported = crossRefServiceImpl.getRecords("test query", 0, 2);
assertEquals(2, recordsImported.size());
matchRecords(new ArrayList<ImportRecord>(recordsImported), collection2match);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test
public void crossRefImportMetadataGetRecordsCountTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream crossRefResp = getClass().getResourceAsStream("crossRef-test.json")) {
String crossRefRespXmlResp = IOUtils.toString(crossRefResp, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(crossRefRespXmlResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
int tot = crossRefServiceImpl.getRecordsCount("test query");
assertEquals(10, tot);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test
public void crossRefImportMetadataGetRecordByIdTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream crossRefResp = getClass().getResourceAsStream("crossRef-by-id.json")) {
String crossRefRespXmlResp = IOUtils.toString(crossRefResp, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(crossRefRespXmlResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
ArrayList<ImportRecord> collection2match = getRecords();
collection2match.remove(1);
ImportRecord recordImported = crossRefServiceImpl.getRecord("10.26693/jmbs01.02.184");
assertNotNull(recordImported);
Collection<ImportRecord> recordsImported = Arrays.asList(recordImported);
matchRecords(new ArrayList<ImportRecord>(recordsImported), collection2match);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test(expected = MethodNotFoundException.class)
public void crossRefImportMetadataFindMatchingRecordsTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
org.dspace.content.Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
Item testItem = ItemBuilder.createItem(context, col1)
.withTitle("test item")
.withIssueDate("2021")
.build();
context.restoreAuthSystemState();
crossRefServiceImpl.findMatchingRecords(testItem);
}
private ArrayList<ImportRecord> getRecords() {
ArrayList<ImportRecord> records = new ArrayList<>();
//define first record
List<MetadatumDTO> metadatums = new ArrayList<MetadatumDTO>();
MetadatumDTO title = createMetadatumDTO("dc", "title", null,
"State of Awareness of Freshers Groups Chortkiv State"
+ " Medical College of Prevention of Iodine Deficiency Diseases");
MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "L.V. Senyuk");
MetadatumDTO type = createMetadatumDTO("dc", "type", null, "journal-article");
MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2016");
MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof",
"Ukraïnsʹkij žurnal medicini, bìologìï ta sportu");
MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.184");
MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "2415-3060");
MetadatumDTO volume = createMetadatumDTO("oaire", "citation", "volume", "1");
MetadatumDTO issue = createMetadatumDTO("oaire", "citation", "issue", "2");
metadatums.add(title);
metadatums.add(author);
metadatums.add(date);
metadatums.add(type);
metadatums.add(ispartof);
metadatums.add(doi);
metadatums.add(issn);
metadatums.add(volume);
metadatums.add(issue);
ImportRecord firstrRecord = new ImportRecord(metadatums);
//define second record
List<MetadatumDTO> metadatums2 = new ArrayList<MetadatumDTO>();
MetadatumDTO title2 = createMetadatumDTO("dc", "title", null,
"Ischemic Heart Disease and Role of Nurse of Cardiology Department");
MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "K. І. Kozak");
MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "journal-article");
MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2016");
MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof",
"Ukraïnsʹkij žurnal medicini, bìologìï ta sportu");
MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.105");
MetadatumDTO issn2 = createMetadatumDTO("dc", "identifier", "issn", "2415-3060");
MetadatumDTO volume2 = createMetadatumDTO("oaire", "citation", "volume", "1");
MetadatumDTO issue2 = createMetadatumDTO("oaire", "citation", "issue", "2");
metadatums2.add(title2);
metadatums2.add(author2);
metadatums2.add(date2);
metadatums2.add(type2);
metadatums2.add(ispartof2);
metadatums2.add(doi2);
metadatums2.add(issn2);
metadatums2.add(volume2);
metadatums2.add(issue2);
ImportRecord secondRecord = new ImportRecord(metadatums2);
records.add(firstrRecord);
records.add(secondRecord);
return records;
}
}

View File

@@ -0,0 +1,199 @@
/**
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.el.MethodNotFoundException;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Item;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl;
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
import org.dspace.importer.external.vufind.VuFindImportMetadataSourceServiceImpl;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link VuFindImportMetadataSourceServiceImpl}
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class VuFindImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest {
@Autowired
private LiveImportClientImpl liveImportClientImpl;
@Autowired
private VuFindImportMetadataSourceServiceImpl vuFindService;
@Test
public void vuFindImportMetadataGetRecordsTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream vuFindRespIS = getClass().getResourceAsStream("vuFind-generic.json")) {
String vuFindResp = IOUtils.toString(vuFindRespIS, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(vuFindResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
ArrayList<ImportRecord> collection2match = getRecords();
Collection<ImportRecord> recordsImported = vuFindService.getRecords("test query", 0, 2);
assertEquals(2, recordsImported.size());
matchRecords(new ArrayList<>(recordsImported), collection2match);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test
public void vuFindImportMetadataGetRecordsCountTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream vuFindRespIS = getClass().getResourceAsStream("vuFind-generic.json")) {
String vuFindResp = IOUtils.toString(vuFindRespIS, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(vuFindResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
int tot = vuFindService.getRecordsCount("test query");
assertEquals(1994, tot);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test
public void vuFindImportMetadataGetRecordByIdTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream vuFindByIdResp = getClass().getResourceAsStream("vuFind-by-id.json")) {
String vuFindResp = IOUtils.toString(vuFindByIdResp, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(vuFindResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
ArrayList<ImportRecord> collection2match = getRecords();
collection2match.remove(1);
ImportRecord recordImported = vuFindService.getRecord("653510");
assertNotNull(recordImported);
Collection<ImportRecord> recordsImported = Arrays.asList(recordImported);
matchRecords(new ArrayList<>(recordsImported), collection2match);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test(expected = MethodNotFoundException.class)
public void vuFindImportMetadataFindMatchingRecordsTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
org.dspace.content.Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
Item testItem = ItemBuilder.createItem(context, col1)
.withTitle("test item")
.withIssueDate("2021")
.build();
context.restoreAuthSystemState();
vuFindService.findMatchingRecords(testItem);
}
private ArrayList<ImportRecord> getRecords() {
ArrayList<ImportRecord> records = new ArrayList<>();
//define first record
List<MetadatumDTO> metadatums = new ArrayList<MetadatumDTO>();
MetadatumDTO identifierOther = createMetadatumDTO("dc", "identifier", "other", "653510");
MetadatumDTO language = createMetadatumDTO("dc", "language", "iso", "Italian");
MetadatumDTO title = createMetadatumDTO("dc", "title", null,
"La pianta marmorea di Roma antica: Forma urbis Romae /");
MetadatumDTO subject = createMetadatumDTO("dc", "subject", null, "Rome (Italy)");
MetadatumDTO subject2 = createMetadatumDTO("dc", "subject", null, "Maps");
MetadatumDTO subject3 = createMetadatumDTO("dc", "subject", null, "Early works to 1800.");
MetadatumDTO subject4 = createMetadatumDTO("dc", "subject", null, "Rome (Italy)");
MetadatumDTO subject5 = createMetadatumDTO("dc", "subject", null, "Antiquities");
MetadatumDTO subject6 = createMetadatumDTO("dc", "subject", null, "Maps.");
MetadatumDTO identifier = createMetadatumDTO("dc", "identifier", null,
"http://hdl.handle.net/20.500.12390/231");
metadatums.add(identifierOther);
metadatums.add(language);
metadatums.add(title);
metadatums.add(identifier);
metadatums.add(subject);
metadatums.add(subject2);
metadatums.add(subject3);
metadatums.add(subject4);
metadatums.add(subject5);
metadatums.add(subject6);
ImportRecord firstrRecord = new ImportRecord(metadatums);
//define second record
List<MetadatumDTO> metadatums2 = new ArrayList<MetadatumDTO>();
MetadatumDTO identifierOther2 = createMetadatumDTO("dc", "identifier", "other", "1665326");
MetadatumDTO language2 = createMetadatumDTO("dc", "language", "iso", "English");
MetadatumDTO title2 = createMetadatumDTO("dc", "title", null,
"Expert frames : scientific and policy practices of Roma classification /");
MetadatumDTO subject7 = createMetadatumDTO("dc", "subject", null, "Public opinion");
MetadatumDTO subject8 = createMetadatumDTO("dc", "subject", null, "Europe.");
MetadatumDTO subject9 = createMetadatumDTO("dc", "subject", null, "Stereotypes (Social psychology)");
MetadatumDTO subject10 = createMetadatumDTO("dc", "subject", null, "Romanies");
MetadatumDTO subject11 = createMetadatumDTO("dc", "subject", null, "Public opinion.");
MetadatumDTO identifier2 = createMetadatumDTO("dc", "identifier", null,
"http://ezproxy.villanova.edu/login?URL=http://www.jstor.org/stable/10.7829/j.ctt1ggjj08");
metadatums2.add(identifierOther2);
metadatums2.add(language2);
metadatums2.add(title2);
metadatums2.add(identifier2);
metadatums2.add(subject7);
metadatums2.add(subject8);
metadatums2.add(subject9);
metadatums2.add(subject10);
metadatums2.add(subject11);
ImportRecord secondRecord = new ImportRecord(metadatums2);
records.add(firstrRecord);
records.add(secondRecord);
return records;
}
}

View File

@@ -27,7 +27,8 @@ public class AuthenticationStatusMatcher {
*/
public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds(
"eperson"
"eperson",
"specialGroups"
);
}
@@ -36,7 +37,9 @@ public class AuthenticationStatusMatcher {
*/
public static Matcher<? super Object> matchLinks() {
return allOf(
//FIXME https://github.com/DSpace/DSpace/issues/8274
hasJsonPath("$._links.eperson.href", containsString("api/eperson/epersons")),
hasJsonPath("$._links.self.href", containsString("api/authn/status")));
hasJsonPath("$._links.self.href", containsString("api/authn/status")),
hasJsonPath("$._links.specialGroups.href", containsString("api/authn/status/specialGroups")));
}
}

View File

@@ -0,0 +1,169 @@
{
"status":"ok",
"message-type":"work",
"message-version":"1.0.0",
"message":{
"indexed":{
"date-parts":[
[
2022,
4,
5
]
],
"date-time":"2022-04-05T22:05:30Z",
"timestamp":1649196330913
},
"reference-count":0,
"publisher":"Petro Mohyla Black Sea National University",
"issue":"2",
"content-domain":{
"domain":[
],
"crossmark-restriction":false
},
"short-container-title":[
"Ukr. \u017e. med. b\u00ecol. sportu"
],
"published-print":{
"date-parts":[
[
2016,
5,
19
]
]
},
"DOI":"10.26693\/jmbs01.02.184",
"type":"journal-article",
"created":{
"date-parts":[
[
2017,
9,
7
]
],
"date-time":"2017-09-07T13:30:46Z",
"timestamp":1504791046000
},
"page":"184-187",
"source":"Crossref",
"is-referenced-by-count":0,
"title":[
"State of Awareness of Freshers\u2019 Groups Chortkiv State Medical College of Prevention of Iodine Deficiency Diseases"
],
"prefix":"10.26693",
"volume":"1",
"author":[
{
"given":"L.V.",
"family":"Senyuk",
"sequence":"first",
"affiliation":[
]
},
{
"name":"Chortkiv State Medical College 7, Gogola St., Chortkiv, Ternopil region 48500, Ukraine",
"sequence":"first",
"affiliation":[
]
}
],
"member":"11225",
"published-online":{
"date-parts":[
[
2016,
5,
19
]
]
},
"container-title":[
"Ukra\u00efns\u02b9kij \u017eurnal medicini, b\u00ecolog\u00ec\u00ef ta sportu"
],
"original-title":[
"\u0421\u0422\u0410\u041d \u041e\u0411\u0406\u0417\u041d\u0410\u041d\u041e\u0421\u0422\u0406 \u0421\u0422\u0423\u0414\u0415\u041d\u0422\u0406\u0412 \u041d\u041e\u0412\u041e\u041d\u0410\u0411\u0420\u0410\u041d\u0418\u0425 \u0413\u0420\u0423\u041f \u0427\u041e\u0420\u0422\u041a\u0406\u0412\u0421\u042c\u041a\u041e\u0413\u041e \u0414\u0415\u0420\u0416\u0410\u0412\u041d\u041e\u0413\u041e \u041c\u0415\u0414\u0418\u0427\u041d\u041e\u0413\u041e \u041a\u041e\u041b\u0415\u0414\u0416\u0423 \u0417 \u041f\u0418\u0422\u0410\u041d\u042c \u041f\u0420\u041e\u0424\u0406\u041b\u0410\u041a\u0422\u0418\u041a\u0418 \u0419\u041e\u0414\u041e\u0414\u0415\u0424\u0406\u0426\u0418\u0422\u041d\u0418\u0425 \u0417\u0410\u0425\u0412\u041e\u0420\u042e\u0412\u0410\u041d\u042c"
],
"deposited":{
"date-parts":[
[
2017,
9,
8
]
],
"date-time":"2017-09-08T10:14:53Z",
"timestamp":1504865693000
},
"score":1,
"resource":{
"primary":{
"URL":"http:\/\/en.jmbs.com.ua\/archive\/1\/2\/184"
}
},
"subtitle":[
],
"short-title":[
],
"issued":{
"date-parts":[
[
2016,
5,
19
]
]
},
"references-count":0,
"journal-issue":{
"issue":"2",
"published-online":{
"date-parts":[
[
2016,
5,
19
]
]
},
"published-print":{
"date-parts":[
[
2016,
5,
19
]
]
}
},
"URL":"http:\/\/dx.doi.org\/10.26693\/jmbs01.02.184",
"relation":{
},
"ISSN":[
"2415-3060"
],
"issn-type":[
{
"value":"2415-3060",
"type":"print"
}
],
"published":{
"date-parts":[
[
2016,
5,
19
]
]
}
}
}

View File

@@ -0,0 +1,309 @@
{
"status": "ok",
"message-type": "work-list",
"message-version": "1.0.0",
"message": {
"facets": {},
"total-results": 10,
"items": [
{
"indexed": {
"date-parts": [
[
2021,
12,
22
]
],
"date-time": "2021-12-22T10:58:16Z",
"timestamp": 1640170696598
},
"reference-count": 0,
"publisher": "Petro Mohyla Black Sea National University",
"issue": "2",
"content-domain": {
"domain": [],
"crossmark-restriction": false
},
"short-container-title": [
"Ukr. ž. med. bìol. sportu"
],
"published-print": {
"date-parts": [
[
2016,
5,
19
]
]
},
"DOI": "10.26693/jmbs01.02.184",
"type": "journal-article",
"created": {
"date-parts": [
[
2017,
9,
7
]
],
"date-time": "2017-09-07T13:30:46Z",
"timestamp": 1504791046000
},
"page": "184-187",
"source": "Crossref",
"is-referenced-by-count": 0,
"title": [
"State of Awareness of Freshers Groups Chortkiv State Medical College of Prevention of Iodine Deficiency Diseases"
],
"prefix": "10.26693",
"volume": "1",
"author": [
{
"given": "L.V.",
"family": "Senyuk",
"sequence": "first",
"affiliation": []
},
{
"name": "Chortkiv State Medical College 7, Gogola St., Chortkiv, Ternopil region 48500, Ukraine",
"sequence": "first",
"affiliation": []
}
],
"member": "11225",
"published-online": {
"date-parts": [
[
2016,
5,
19
]
]
},
"container-title": [
"Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"
],
"original-title": [
"СТАН ОБІЗНАНОСТІ СТУДЕНТІВ НОВОНАБРАНИХ ГРУП ЧОРТКІВСЬКОГО ДЕРЖАВНОГО МЕДИЧНОГО КОЛЕДЖУ З ПИТАНЬ ПРОФІЛАКТИКИ ЙОДОДЕФІЦИТНИХ ЗАХВОРЮВАНЬ"
],
"deposited": {
"date-parts": [
[
2017,
9,
8
]
],
"date-time": "2017-09-08T10:14:53Z",
"timestamp": 1504865693000
},
"score": 22.728952,
"issued": {
"date-parts": [
[
2016,
5,
19
]
]
},
"references-count": 0,
"journal-issue": {
"issue": "2",
"published-online": {
"date-parts": [
[
2016,
5,
19
]
]
},
"published-print": {
"date-parts": [
[
2016,
5,
19
]
]
}
},
"URL": "http://dx.doi.org/10.26693/jmbs01.02.184",
"ISSN": [
"2415-3060"
],
"issn-type": [
{
"value": "2415-3060",
"type": "print"
}
],
"published": {
"date-parts": [
[
2016,
5,
19
]
]
}
},
{
"indexed": {
"date-parts": [
[
2022,
3,
29
]
],
"date-time": "2022-03-29T13:04:48Z",
"timestamp": 1648559088439
},
"reference-count": 0,
"publisher": "Petro Mohyla Black Sea National University",
"issue": "2",
"content-domain": {
"domain": [],
"crossmark-restriction": false
},
"short-container-title": [
"Ukr. ž. med. bìol. sportu"
],
"published-print": {
"date-parts": [
[
2016,
5,
19
]
]
},
"DOI": "10.26693/jmbs01.02.105",
"type": "journal-article",
"created": {
"date-parts": [
[
2017,
9,
1
]
],
"date-time": "2017-09-01T10:04:04Z",
"timestamp": 1504260244000
},
"page": "105-108",
"source": "Crossref",
"is-referenced-by-count": 0,
"title": [
"Ischemic Heart Disease and Role of Nurse of Cardiology Department"
],
"prefix": "10.26693",
"volume": "1",
"author": [
{
"given": "K. І.",
"family": "Kozak",
"sequence": "first",
"affiliation": []
},
{
"name": "Chortkiv State Medical College 7, Gogola St., Chortkiv, Ternopil region 48500, Ukraine",
"sequence": "first",
"affiliation": []
}
],
"member": "11225",
"published-online": {
"date-parts": [
[
2016,
5,
19
]
]
},
"container-title": [
"Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"
],
"original-title": [
"ІШЕМІЧНА ХВОРОБА СЕРЦЯ ТА РОЛЬ МЕДИЧНОЇ СЕСТРИ КАРДІОЛОГІЧНОГО ВІДДІЛЕННЯ"
],
"deposited": {
"date-parts": [
[
2017,
9,
2
]
],
"date-time": "2017-09-02T12:36:15Z",
"timestamp": 1504355775000
},
"score": 18.263277,
"resource": {
"primary": {
"URL": "http://en.jmbs.com.ua/archive/1/2/105"
}
},
"issued": {
"date-parts": [
[
2016,
5,
19
]
]
},
"references-count": 0,
"journal-issue": {
"issue": "2",
"published-online": {
"date-parts": [
[
2016,
5,
19
]
]
},
"published-print": {
"date-parts": [
[
2016,
5,
19
]
]
}
},
"URL": "http://dx.doi.org/10.26693/jmbs01.02.105",
"ISSN": [
"2415-3060"
],
"issn-type": [
{
"value": "2415-3060",
"type": "print"
}
],
"published": {
"date-parts": [
[
2016,
5,
19
]
]
}
}
],
"items-per-page": 2,
"query": {
"start-index": 0,
"search-terms": "chortkiv"
}
}
}

View File

@@ -0,0 +1,44 @@
{
"resultCount": 1,
"records": [
{
"authors": {
"primary": [],
"secondary": {
"Carettoni, Gianfilippo.": []
},
"corporate": []
},
"formats": [
"Map",
"Map",
"Book"
],
"id": "653510",
"languages": [
"Italian"
],
"series": [],
"subjects": [
[
"Rome (Italy)",
"Maps",
"Early works to 1800."
],
[
"Rome (Italy)",
"Antiquities",
"Maps."
]
],
"title": "La pianta marmorea di Roma antica: Forma urbis Romae /",
"urls": [
{
"url": "http://hdl.handle.net/20.500.12390/231",
"desc": "http://hdl.handle.net/20.500.12390/231"
}
]
}
],
"status": "OK"
}

View File

@@ -0,0 +1,82 @@
{
"resultCount": 1994,
"records": [
{
"authors": {
"primary": [],
"secondary": {
"Carettoni, Gianfilippo.": []
},
"corporate": []
},
"formats": [
"Map",
"Map",
"Book"
],
"id": "653510",
"languages": [
"Italian"
],
"series": [],
"subjects": [
[
"Rome (Italy)",
"Maps",
"Early works to 1800."
],
[
"Rome (Italy)",
"Antiquities",
"Maps."
]
],
"title": "La pianta marmorea di Roma antica: Forma urbis Romae /",
"urls": [
{
"url": "http://hdl.handle.net/20.500.12390/231",
"desc": "http://hdl.handle.net/20.500.12390/231"
}
]
},
{
"authors": {
"primary": {
"Surdu, Mihai.": []
},
"secondary": [],
"corporate": []
},
"formats": [
"Online",
"Book"
],
"id": "1665326",
"languages": [
"English"
],
"series": [],
"subjects": [
[
"Public opinion",
"Europe."
],
[
"Stereotypes (Social psychology)"
],
[
"Romanies",
"Public opinion."
]
],
"title": "Expert frames : scientific and policy practices of Roma classification /",
"urls": [
{
"url": "http://ezproxy.villanova.edu/login?URL=http://www.jstor.org/stable/10.7829/j.ctt1ggjj08",
"desc": "http://ezproxy.villanova.edu/login?URL=http://www.jstor.org/stable/10.7829/j.ctt1ggjj08"
}
]
}
],
"status": "OK"
}

View File

@@ -1576,3 +1576,4 @@ include = ${module_dir}/usage-statistics.cfg
include = ${module_dir}/versioning.cfg
include = ${module_dir}/workflow.cfg
include = ${module_dir}/authority.cfg
include = ${module_dir}/external-providers.cfg

View File

@@ -0,0 +1,22 @@
#---------------------------------------------------------------#
#------------- EXTERNAL PROVIDER CONFIGURATIONS ----------------#
#---------------------------------------------------------------#
# Configuration properties used solely by external providers #
# as Scopus, Pubmed, CiNii and ect. #
#---------------------------------------------------------------#
#################################################################
#---------------------- CrossRef ---------------------------#
#---------------------------------------------------------------#
crossref.url = https://api.crossref.org/works
#################################################################
#---------------------- VuFind -----------------------------#
#---------------------------------------------------------------#
vufind.url = https://vufind.org/advanced_demo/api/v1/record
vufind.url.search = https://vufind.org/advanced_demo/api/v1/search
#################################################################

View File

@@ -0,0 +1,141 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-autowire-candidates="*Service,*DAO,javax.sql.DataSource">
<context:annotation-config/>
<!-- allows us to use spring annotations in beans -->
<util:map id="crossrefMetadataFieldMap" key-type="org.dspace.importer.external.metadatamapping.MetadataFieldConfig"
value-type="org.dspace.importer.external.metadatamapping.contributor.MetadataContributor">
<description>Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
what metadatafield is generated.
</description>
<entry key-ref="crossref.title" value-ref="crossrefTitleContrib"/>
<entry key-ref="crossref.authors" value-ref="crossrefAuthorContrib"/>
<entry key-ref="crossref.isbn" value-ref="crossrefISBNContrib"/>
<entry key-ref="crossref.year" value-ref="crossrefYearContrib"/>
<entry key-ref="crossref.editors" value-ref="crossrefEditorsContrib"/>
<entry key-ref="crossref.type" value-ref="crossrefDoiTypeContrib"/>
<entry key-ref="crossref.journal" value-ref="crossrefJournalContrib"/>
<entry key-ref="crossref.id" value-ref="crossrefIDContrib"/>
<entry key-ref="crossref.issn" value-ref="crossrefIdentifierISSN" />
<entry key-ref="crossref.volume" value-ref="crossrefVolume" />
<entry key-ref="crossref.issue" value-ref="crossrefIssue" />
<entry key-ref="crossref.abstract" value-ref="crossrefAbstract" />
</util:map>
<bean id="crossrefIDContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.id"/>
<property name="query" value="/DOI"/>
</bean>
<bean id="crossref.id" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.doi"/>
</bean>
<bean id="crossrefJournalContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.journal"/>
<property name="query" value="/container-title"/>
</bean>
<bean id="crossref.journal" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.relation.ispartof"/>
</bean>
<bean id="crossrefDoiTypeContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.type"/>
<property name="query" value="/type"/>
</bean>
<bean id="crossref.type" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.type"/>
</bean>
<bean id="crossrefEditorsContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.editors"/>
<property name="metadataProcessor" ref="crossrefEditorMetadataProcessor"></property>
</bean>
<bean name="crossrefEditorMetadataProcessor" class="org.dspace.importer.external.crossref.CrossRefAuthorMetadataProcessor">
<property name="pathToArray" value="/editor"></property>
</bean>
<bean id="crossref.editors" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.contributor.editor"/>
</bean>
<bean id="crossrefYearContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.year"/>
<property name="query" value="/issued/date-parts/0/0"/>
</bean>
<bean id="crossref.year" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.date.issued"/>
</bean>
<bean id="crossrefISBNContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.isbn"/>
<property name="query" value="/ISBN"/>
</bean>
<bean id="crossref.isbn" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.isbn"/>
</bean>
<bean id="crossrefAuthorContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.authors"/>
<property name="metadataProcessor" ref="crossrefAuthorMetadataProcessor"></property>
</bean>
<bean name="crossrefAuthorMetadataProcessor" class="org.dspace.importer.external.crossref.CrossRefAuthorMetadataProcessor">
<property name="pathToArray" value="/author"></property>
</bean>
<bean id="crossref.authors" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.contributor.author"/>
</bean>
<bean id="crossrefTitleContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.title"/>
<property name="query" value="/title"/>
</bean>
<bean id="crossref.title" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title"/>
</bean>
<bean id="crossrefIdentifierISSN" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.issn"/>
<property name="query" value="/ISSN"/>
</bean>
<bean id="crossref.issn" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.issn"/>
</bean>
<bean id="crossrefVolume" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.volume"/>
<property name="query" value="/volume"/>
</bean>
<bean id="crossref.volume" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.citation.volume"/>
</bean>
<bean id="crossrefIssue" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.issue"/>
<property name="query" value="/journal-issue/issue"/>
</bean>
<bean id="crossref.issue" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.citation.issue"/>
</bean>
<bean id="crossrefAbstract" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="crossref.abstract"/>
<property name="query" value="/abstract"/>
</bean>
<bean id="crossref.abstract" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.description.abstract"/>
</bean>
<bean class="java.lang.Integer" id="maxRetry">
<constructor-arg value="3"/>
</bean>
</beans>

View File

@@ -5,6 +5,8 @@
<bean class="org.dspace.external.service.impl.ExternalDataServiceImpl"/>
<bean class="org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl"/>
<bean class="org.dspace.external.provider.impl.SHERPAv2JournalISSNDataProvider" init-method="init">
<property name="sherpaService">
<bean class="org.dspace.app.sherpa.SHERPAService">
@@ -92,5 +94,28 @@
</property>
</bean>
</beans>
<bean id="crossRefLiveImportDataProvider" class="org.dspace.external.provider.impl.LiveImportDataProvider">
<property name="metadataSource" ref="CrossRefImportService"/>
<property name="sourceIdentifier" value="crossref"/>
<property name="recordIdMetadata" value="dc.identifier.doi"/>
<property name="supportedEntityTypes">
<list>
<value>Publication</value>
<value>none</value>
</list>
</property>
</bean>
<bean id="vufindLiveImportDataProvider" class="org.dspace.external.provider.impl.LiveImportDataProvider">
<property name="metadataSource" ref="vufindImportService"/>
<property name="sourceIdentifier" value="vufind"/>
<property name="recordIdMetadata" value="dc.identifier.other"/>
<property name="supportedEntityTypes">
<list>
<value>Publication</value>
<value>none</value>
</list>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,165 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-autowire-candidates="*Service,*DAO,javax.sql.DataSource">
<context:annotation-config/>
<!-- allows us to use spring annotations in beans -->
<util:map id="vufindMetadataFieldMap" key-type="org.dspace.importer.external.metadatamapping.MetadataFieldConfig"
value-type="org.dspace.importer.external.metadatamapping.contributor.MetadataContributor">
<description>Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
what metadatafield is generated.
</description>
<entry key-ref="vufind.id" value-ref="vufindIDContrib"/>
<entry key-ref="vufind.dc.language.iso" value-ref="vufindLanguages"/>
<entry key-ref="vufind.dc.publisher" value-ref="vufindPublisher"/>
<entry key-ref="vufind.dc.title" value-ref="vufindTitle"/>
<entry key-ref="vufind.dc.identifier" value-ref="vufindIdentifier"/>
<entry key-ref="vufind.dc.contributor.author" value-ref="vufindAuthors"/>
<entry key-ref="vufind.dc.subject" value-ref="vufindSubject"/>
<entry key-ref="vufind.dc.description.abstract" value-ref="vufindAbstract"/>
<entry key-ref="vufind.dc.titlealternative" value-ref="vufindTitleAlternative"/>
<entry key-ref="vufind.dc.contributor.editor" value-ref="vufindContributorEditor"/>
<entry key-ref="vufind.dc.date.issued" value-ref="vufindDateIssued"/>
<entry key-ref="vufind.dc.identifier.doi" value-ref="vufindIdentifierDoi"/>
<entry key-ref="vufind.dc.identifier.isbn" value-ref="vufindIdentifierIsbn"/>
</util:map>
<bean id="vufindIDContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.id"/>
<property name="query" value="/id"/>
</bean>
<bean id="vufind.id" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.other"/>
</bean>
<bean id="vufindLanguages" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.language.iso"/>
<property name="query" value="/languages"/>
</bean>
<bean id="vufind.dc.language.iso" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.language.iso"/>
</bean>
<bean id="vufindTitle" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.title"/>
<property name="query" value="/title"/>
</bean>
<bean id="vufind.dc.title" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title"/>
</bean>
<bean id="vufindIdentifier" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.identifier"/>
<property name="metadataProcessor" ref="vufindUriProcessor"></property>
</bean>
<bean name="vufindUriProcessor" class="org.dspace.importer.external.metadatamapping.contributor.ArrayElementAttributeProcessor">
<property name="pathToArray" value="/urls"></property>
<property name="elementAttribute" value="/url"></property>
</bean>
<bean id="vufind.dc.identifier" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier"/>
</bean>
<bean id="vufindSubject" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.subject"/>
<property name="metadataProcessor" ref="vufindSubjectsProcessor"></property>
</bean>
<bean name="vufindSubjectsProcessor" class="org.dspace.importer.external.metadatamapping.contributor.MatrixElementProcessor">
<property name="pathToMatrix" value="/subjects"></property>
</bean>
<bean id="vufind.dc.subject" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.subject"/>
</bean>
<bean id="vufindAuthors" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.contributor.author"/>
<property name="query" value="/dcContributorAuthor"/>
</bean>
<util:list id="vufindAuthorsList">
<bean id="vufindPrimaryAuthors" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.contributor.author"/>
<property name="query" value="/authors/primary" />
</bean>
<bean id="vufindSecondaryAuthors" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.contributor.author"/>
<property name="query" value="/authors/secondary" />
</bean>
</util:list>
<bean id="vufind.dc.contributor.author" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.contributor.author"/>
</bean>
<bean class="java.lang.Integer" id="maxRetry">
<constructor-arg value="3"/>
</bean>
<!-- Below there are other mapping that apply to an extended configuration in use for instance at the vuFind installation
of ALICIA https://alicia.concytec.gob.pe/vufind/Search/Results?type=AllFields -->
<bean id="vufindIdentifierIsbn" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.identifier.isbn"/>
<property name="query" value="/dcIdentifierIsbn"/>
</bean>
<bean id="vufind.dc.identifier.isbn" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.isbn"/>
</bean>
<bean id="vufindIdentifierDoi" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.identifier.doi"/>
<property name="query" value="/dcIdentifierDoi"/>
</bean>
<bean id="vufind.dc.identifier.doi" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.doi"/>
</bean>
<bean id="vufindDateIssued" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.date.issued"/>
<property name="query" value="/dcDateIssued"/>
</bean>
<bean id="vufind.dc.date.issued" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.date.issued"/>
</bean>
<bean id="vufindContributorEditor" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.contributor.editor"/>
<property name="query" value="/dcContributorEditor"/>
</bean>
<bean id="vufind.dc.contributor.editor" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.contributor.editor"/>
</bean>
<bean id="vufindTitleAlternative" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.titlealternative"/>
<property name="query" value="/dcTitleAlternative"/>
</bean>
<bean id="vufind.dc.titlealternative" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title.alternative"/>
</bean>
<bean id="vufindAbstract" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.description.abstract"/>
<property name="query" value="/dcDescriptionAbstract"/>
</bean>
<bean id="vufind.dc.description.abstract" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.description.abstract"/>
</bean>
<bean id="vufindPublisher" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="vufind.dc.publisher"/>
<property name="query" value="/dcPublisher"/>
</bean>
<bean id="vufind.dc.publisher" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.publisher"/>
</bean>
</beans>