Merge pull request #2560 from atmire/feature-external-sources

Feature: external sources
This commit is contained in:
Tim Donohue
2019-11-26 10:51:58 -06:00
committed by GitHub
51 changed files with 2622 additions and 1171 deletions

View File

@@ -15,6 +15,9 @@ import java.util.List;
* @author Andrea Bollini
*/
public class SHERPAPublisher {
private String id;
private String name;
private String alias;
@@ -49,7 +52,7 @@ public class SHERPAPublisher {
private String dateupdated;
public SHERPAPublisher(String name, String alias, String homeurl,
public SHERPAPublisher(String id, String name, String alias, String homeurl,
String prearchiving, List<String> prerestriction,
String postarchiving, List<String> postrestriction,
String pubarchiving, List<String> pubrestriction,
@@ -57,6 +60,8 @@ public class SHERPAPublisher {
String paidaccessname, String paidaccessnotes,
List<String[]> copyright, String romeocolour, String datedded,
String dateupdated) {
this.id = id;
this.name = name;
this.alias = alias;
@@ -160,4 +165,11 @@ public class SHERPAPublisher {
return dateupdated;
}
/**
* Generic getter for the id
* @return the id value of this SHERPAPublisher
*/
public String getId() {
return id;
}
}

View File

@@ -14,6 +14,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -24,7 +25,9 @@ import org.w3c.dom.Element;
* @author Andrea Bollini
*/
public class SHERPAResponse {
private boolean error;
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SHERPAResponse.class);
private int numHits;
private String message;
@@ -57,12 +60,13 @@ public class SHERPAResponse {
Element publishersElement = XMLUtils.getSingleElement(xmlRoot,
"publishers");
message = XMLUtils.getElementValue(headersElement, "message");
if (StringUtils.isNotBlank(message)) {
error = true;
return;
String numhitsString = XMLUtils.getElementValue(headersElement, "numhits");
if (StringUtils.isNotBlank(numhitsString)) {
numHits = Integer.parseInt(numhitsString);
} else {
numHits = 0;
}
message = XMLUtils.getElementValue(headersElement, "message");
license = XMLUtils.getElementValue(headersElement, "license");
licenseURL = XMLUtils.getElementValue(headersElement, "licenseurl");
@@ -112,9 +116,8 @@ public class SHERPAResponse {
Element copyrightlinksElement = XMLUtils.getSingleElement(
publisherElement, "copyrightlinks");
publishers
.add(new SHERPAPublisher(XMLUtils.getElementValue(
.add(new SHERPAPublisher(publisherElement.getAttribute("id"), XMLUtils.getElementValue(
publisherElement, "name"),
XMLUtils.getElementValue(publisherElement,
"alias"), XMLUtils.getElementValue(
@@ -162,17 +165,12 @@ public class SHERPAResponse {
}
}
} catch (Exception e) {
error = true;
log.error("Error parsing SHERPA API Response", e);
}
}
public SHERPAResponse(String message) {
this.message = message;
this.error = true;
}
public boolean isError() {
return error;
}
public String getMessage() {
@@ -198,4 +196,8 @@ public class SHERPAResponse {
public List<SHERPAPublisher> getPublishers() {
return publishers;
}
public int getNumHits() {
return numHits;
}
}

View File

@@ -52,11 +52,11 @@ public class InitializeEntities {
private RelationshipService relationshipService;
private EntityTypeService entityTypeService;
private InitializeEntities() {
relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService();
relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService();
}
/**

View File

@@ -35,6 +35,7 @@ public abstract class AuthorityServiceFactory {
public abstract AuthorityService getAuthorityService();
public abstract List<AuthorityIndexerInterface> getAuthorityIndexers();
public static AuthorityServiceFactory getInstance() {

View File

@@ -1,191 +0,0 @@
/**
* 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.authority.orcid;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authority.AuthorityValue;
import org.dspace.authority.SolrAuthorityInterface;
import org.dspace.authority.orcid.xml.XMLtoBio;
import org.dspace.authority.rest.RESTConnector;
import org.json.JSONObject;
import org.orcid.jaxb.model.record_v2.Person;
/**
* @author Jonas Van Goolen (jonas at atmire dot com)
* This class contains all methods for retrieving "Person" objects calling the ORCID (version 2) endpoints.
* Additionally, this can also create AuthorityValues based on these returned Person objects
*/
public class Orcidv2 implements SolrAuthorityInterface {
private static Logger log = LogManager.getLogger(Orcidv2.class);
public RESTConnector restConnector;
private String OAUTHUrl;
private String clientId;
private String clientSecret;
private String accessToken;
/**
* Initialize the accessToken that is required for all subsequent calls to ORCID.
*
* @throws java.io.IOException passed through from HTTPclient.
*/
public void init() throws IOException {
if (StringUtils.isNotBlank(accessToken) && StringUtils.isNotBlank(clientSecret)) {
String authenticationParameters = "?client_id=" + clientId +
"&client_secret=" + clientSecret +
"&scope=/read-public&grant_type=client_credentials";
HttpPost httpPost = new HttpPost(OAUTHUrl + authenticationParameters);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
HttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse getResponse = httpClient.execute(httpPost);
InputStream is = getResponse.getEntity().getContent();
BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
JSONObject responseObject = null;
String inputStr;
while ((inputStr = streamReader.readLine()) != null && responseObject == null) {
if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token")) {
try {
responseObject = new JSONObject(inputStr);
} catch (Exception e) {
//Not as valid as I'd hoped, move along
responseObject = null;
}
}
}
if (responseObject != null && responseObject.has("access_token")) {
accessToken = (String) responseObject.get("access_token");
}
}
}
/**
* Makes an instance of the Orcidv2 class based on the provided parameters.
* This constructor is called through the spring bean initialization
*/
private Orcidv2(String url, String OAUTHUrl, String clientId, String clientSecret) {
this.restConnector = new RESTConnector(url);
this.OAUTHUrl = OAUTHUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* Makes an instance of the Orcidv2 class based on the provided parameters.
* This constructor is called through the spring bean initialization
*/
private Orcidv2(String url) {
this.restConnector = new RESTConnector(url);
}
/**
* Makes an instance of the AuthorityValue with the given information.
* @param text search string
* @return List<AuthorityValue>
*/
@Override
public List<AuthorityValue> queryAuthorities(String text, int max) {
List<Person> bios = queryBio(text, max);
List<AuthorityValue> result = new ArrayList<>();
for (Person person : bios) {
AuthorityValue orcidAuthorityValue = Orcidv2AuthorityValue.create(person);
if (orcidAuthorityValue != null) {
result.add(orcidAuthorityValue);
}
}
return result;
}
/**
* Create an AuthorityValue from a Person retrieved using the given orcid identifier.
* @param id orcid identifier
* @return AuthorityValue
*/
public AuthorityValue queryAuthorityID(String id) {
Person person = getBio(id);
AuthorityValue valueFromPerson = Orcidv2AuthorityValue.create(person);
return valueFromPerson;
}
/**
* Retrieve a Person object based on a given orcid identifier
* @param id orcid identifier
* @return Person
*/
public Person getBio(String id) {
log.debug("getBio called with ID=" + id);
if (!isValid(id)) {
return null;
}
InputStream bioDocument = restConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken);
XMLtoBio converter = new XMLtoBio();
Person person = converter.convertSinglePerson(bioDocument);
return person;
}
/**
* Retrieve a list of Person objects.
* @param text search string
* @param start offset to use
* @param rows how many rows to return
* @return List<Person>
*/
public List<Person> queryBio(String text, int start, int rows) {
if (rows > 100) {
throw new IllegalArgumentException("The maximum number of results to retrieve cannot exceed 100.");
}
String searchPath = "search?q=" + URLEncoder.encode(text) + "&start=" + start + "&rows=" + rows;
log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
InputStream bioDocument = restConnector.get(searchPath, accessToken);
XMLtoBio converter = new XMLtoBio();
List<Person> bios = converter.convert(bioDocument);
return bios;
}
/**
* Retrieve a list of Person objects.
* @param text search string
* @param max how many rows to return
* @return List<Person>
*/
public List<Person> queryBio(String text, int max) {
return queryBio(text, 0, max);
}
/**
* Check to see if the provided text has the correct ORCID syntax.
* Since only searching on ORCID id is allowed, this way, we filter out any queries that would return a
* blank result anyway
*/
private boolean isValid(String text) {
return StringUtils.isNotBlank(text) && text.matches(Orcidv2AuthorityValue.ORCID_ID_SYNTAX);
}
}

View File

@@ -1,342 +0,0 @@
/**
* 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.authority.orcid;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import org.dspace.authority.AuthorityValue;
import org.dspace.authority.AuthorityValueServiceImpl;
import org.dspace.authority.PersonAuthorityValue;
import org.dspace.utils.DSpace;
import org.orcid.jaxb.model.common_v2.ExternalId;
import org.orcid.jaxb.model.record_v2.ExternalIdentifiers;
import org.orcid.jaxb.model.record_v2.KeywordType;
import org.orcid.jaxb.model.record_v2.NameType;
import org.orcid.jaxb.model.record_v2.Person;
import org.orcid.jaxb.model.record_v2.ResearcherUrlType;
/**
* @author Jonas Van Goolen (jonas at atmire dot com)
*/
public class Orcidv2AuthorityValue extends PersonAuthorityValue {
/*
* The ORCID identifier
*/
private String orcid_id;
/*
* Map containing key-value pairs filled in by "setValues(Person person)".
* This represents all dynamic information of the object.
*/
private Map<String, List<String>> otherMetadata = new HashMap<String, List<String>>();
/**
* The syntax that the ORCID id needs to conform to
*/
public static final String ORCID_ID_SYNTAX = "\\d{4}-\\d{4}-\\d{4}-(\\d{3}X|\\d{4})";
/**
* Creates an instance of Orcidv2AuthorityValue with only uninitialized fields.
* This is meant to be filled in with values from an existing record.
* To create a brand new Orcidv2AuthorityValue, use create()
*/
public Orcidv2AuthorityValue() {
}
public Orcidv2AuthorityValue(SolrDocument document) {
super(document);
}
public String getOrcid_id() {
return orcid_id;
}
public void setOrcid_id(String orcid_id) {
this.orcid_id = orcid_id;
}
/**
* Create an empty authority.
* @return OrcidAuthorityValue
*/
public static Orcidv2AuthorityValue create() {
Orcidv2AuthorityValue orcidAuthorityValue = new Orcidv2AuthorityValue();
orcidAuthorityValue.setId(UUID.randomUUID().toString());
orcidAuthorityValue.updateLastModifiedDate();
orcidAuthorityValue.setCreationDate(new Date());
return orcidAuthorityValue;
}
/**
* Create an authority based on a given orcid bio
* @return OrcidAuthorityValue
*/
public static Orcidv2AuthorityValue create(Person person) {
if (person == null) {
return null;
}
Orcidv2AuthorityValue authority = Orcidv2AuthorityValue.create();
authority.setValues(person);
return authority;
}
/**
* Initialize this instance based on a Person object
* @param person Person
*/
protected void setValues(Person person) {
NameType name = person.getName();
if (!StringUtils.equals(name.getPath(), this.getOrcid_id())) {
this.setOrcid_id(name.getPath());
}
if (!StringUtils.equals(name.getFamilyName().getValue(), this.getLastName())) {
this.setLastName(name.getFamilyName().getValue());
}
if (!StringUtils.equals(name.getGivenNames().getValue(), this.getFirstName())) {
this.setFirstName(name.getGivenNames().getValue());
}
if (name.getCreditName() != null && StringUtils.isNotBlank(name.getCreditName().getValue())) {
if (!this.getNameVariants().contains(name.getCreditName().getValue())) {
this.addNameVariant(name.getCreditName().getValue());
}
}
if (person.getKeywords() != null) {
for (KeywordType keyword : person.getKeywords().getKeyword()) {
if (this.isNewMetadata("keyword", keyword.getContent())) {
this.addOtherMetadata("keyword", keyword.getContent());
}
}
}
ExternalIdentifiers externalIdentifiers = person.getExternalIdentifiers();
if (externalIdentifiers != null) {
for (ExternalId externalIdentifier : externalIdentifiers.getExternalIdentifier()) {
if (this.isNewMetadata("external_identifier", externalIdentifier.getExternalIdValue())) {
this.addOtherMetadata("external_identifier", externalIdentifier.getExternalIdValue());
}
}
}
if (person.getResearcherUrls() != null) {
for (ResearcherUrlType researcherUrl : person.getResearcherUrls().getResearcherUrl()) {
if (this.isNewMetadata("researcher_url", researcherUrl.getUrl().getValue())) {
this.addOtherMetadata("researcher_url", researcherUrl.getUrl().getValue());
}
}
}
if (person.getBiography() != null) {
if (this.isNewMetadata("biography", person.getBiography().getContent())) {
this.addOtherMetadata("biography", person.getBiography().getContent());
}
}
this.setValue(this.getName());
}
/**
* Makes an instance of the AuthorityValue with the given information.
* @param info string info
* @return AuthorityValue
*/
@Override
public AuthorityValue newInstance(String info) {
AuthorityValue authorityValue = null;
if (StringUtils.isNotBlank(info)) {
Orcidv2 orcid = new DSpace().getServiceManager().getServiceByName("AuthoritySource", Orcidv2.class);
authorityValue = orcid.queryAuthorityID(info);
} else {
authorityValue = this.create();
}
return authorityValue;
}
@Override
public void setValue(String value) {
super.setValue(value);
}
/**
* Check to see if the provided label / data pair is already present in the "otherMetadata" or not
* */
public boolean isNewMetadata(String label, String data) {
List<String> strings = getOtherMetadata().get(label);
boolean update;
if (strings == null) {
update = StringUtils.isNotBlank(data);
} else {
update = !strings.contains(data);
}
return update;
}
/**
* Add additional metadata to the otherMetadata map*/
public void addOtherMetadata(String label, String data) {
List<String> strings = otherMetadata.get(label);
if (strings == null) {
strings = new ArrayList<>();
}
strings.add(data);
otherMetadata.put(label, strings);
}
public Map<String, List<String>> getOtherMetadata() {
return otherMetadata;
}
/**
* Generate a solr record from this instance
* @return SolrInputDocument
*/
@Override
public SolrInputDocument getSolrInputDocument() {
SolrInputDocument doc = super.getSolrInputDocument();
if (StringUtils.isNotBlank(getOrcid_id())) {
doc.addField("orcid_id", getOrcid_id());
}
for (String t : otherMetadata.keySet()) {
List<String> data = otherMetadata.get(t);
for (String data_entry : data) {
doc.addField("label_" + t, data_entry);
}
}
return doc;
}
/**
* Information that can be used the choice ui
* @return map
*/
@Override
public Map<String, String> choiceSelectMap() {
Map<String, String> map = super.choiceSelectMap();
String orcid_id = getOrcid_id();
if (StringUtils.isNotBlank(orcid_id)) {
map.put("orcid", orcid_id);
}
return map;
}
@Override
public String getAuthorityType() {
return "orcid";
}
/**
* Provides a string that will allow this AuthorityType to be recognized and provides information to create a new
* instance to be created using public Orcidv2AuthorityValue newInstance(String info).
* @return see {@link org.dspace.authority.service.AuthorityValueService#GENERATE AuthorityValueService.GENERATE}
*/
@Override
public String generateString() {
String generateString = AuthorityValueServiceImpl.GENERATE + getAuthorityType() +
AuthorityValueServiceImpl.SPLIT;
if (StringUtils.isNotBlank(getOrcid_id())) {
generateString += getOrcid_id();
}
return generateString;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Orcidv2AuthorityValue that = (Orcidv2AuthorityValue) o;
if (orcid_id != null ? !orcid_id.equals(that.orcid_id) : that.orcid_id != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return orcid_id != null ? orcid_id.hashCode() : 0;
}
/**
* The regular equals() only checks if both AuthorityValues describe the same authority.
* This method checks if the AuthorityValues have different information
* E.g. it is used to decide when lastModified should be updated.
* @param o object
* @return true or false
*/
@Override
public boolean hasTheSameInformationAs(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.hasTheSameInformationAs(o)) {
return false;
}
Orcidv2AuthorityValue that = (Orcidv2AuthorityValue) o;
if (orcid_id != null ? !orcid_id.equals(that.orcid_id) : that.orcid_id != null) {
return false;
}
for (String key : otherMetadata.keySet()) {
if (otherMetadata.get(key) != null) {
List<String> metadata = otherMetadata.get(key);
List<String> otherMetadata = that.otherMetadata.get(key);
if (otherMetadata == null) {
return false;
} else {
HashSet<String> metadataSet = new HashSet<String>(metadata);
HashSet<String> otherMetadataSet = new HashSet<String>(otherMetadata);
if (!metadataSet.equals(otherMetadataSet)) {
return false;
}
}
} else {
if (that.otherMetadata.get(key) != null) {
return false;
}
}
}
return true;
}
}

View File

@@ -8,6 +8,7 @@
package org.dspace.authority.rest;
import org.dspace.authority.SolrAuthorityInterface;
import org.dspace.external.OrcidRestConnector;
/**
* @author Antoine Snyers (antoine at atmire.com)
@@ -17,9 +18,9 @@ import org.dspace.authority.SolrAuthorityInterface;
*/
public abstract class RestSource implements SolrAuthorityInterface {
protected RESTConnector restConnector;
protected OrcidRestConnector restConnector;
public RestSource(String url) {
this.restConnector = new RESTConnector(url);
this.restConnector = new OrcidRestConnector(url);
}
}

View File

@@ -1,59 +0,0 @@
/**
* 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.content.authority;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.message.BasicNameValuePair;
import org.dspace.content.Collection;
/**
* Sample Journal-name authority based on SHERPA/RoMEO
*
* WARNING: This is a very crude and incomplete implementation, done mainly
* as a proof-of-concept. Any site that actually wants to use it will
* probably have to refine it (and give patches back to dspace.org).
*
* @author Larry Stone
* @version $Revision $
* @see SHERPARoMEOProtocol
*/
public class SHERPARoMEOJournalTitle extends SHERPARoMEOProtocol {
private static final String RESULT = "journal";
private static final String LABEL = "jtitle";
private static final String AUTHORITY = "issn";
public SHERPARoMEOJournalTitle() {
super();
}
@Override
public Choices getMatches(String text, Collection collection, int start, int limit, String locale) {
// punt if there is no query text
if (text == null || text.trim().length() == 0) {
return new Choices(true);
}
// query args to add to SHERPA/RoMEO request URL
List<BasicNameValuePair> args = new ArrayList<BasicNameValuePair>();
args.add(new BasicNameValuePair("jtitle", text));
args.add(new BasicNameValuePair("qtype", "contains")); // OR: starts, exact
Choices result = query(RESULT, LABEL, AUTHORITY, args, start, limit);
if (result == null) {
result = new Choices(true);
}
return result;
}
@Override
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) {
return getMatches(text, collection, start, limit, locale);
}
}

View File

@@ -1,228 +0,0 @@
/**
* 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.content.authority;
import java.io.IOException;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.core.ConfigurationManager;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* Choice Authority based on SHERPA/RoMEO - for Publishers and Journals
* See the subclasses SHERPARoMEOPublisher and SHERPARoMEOJournalTitle
* for actual choice plugin implementations. This is a superclass
* containing all the common protocol logic.
*
* Reads these DSpace Config properties:
*
* # contact URL for server
* sherpa.romeo.url = http://www.sherpa.ac.uk/romeoapi11.php
*
* WARNING: This is a very crude and incomplete implementation, done mainly
* as a proof-of-concept. Any site that actually wants to use it will
* probably have to refine it (and give patches back to dspace.org).
*
* @author Larry Stone
* @version $Revision $
* @see SHERPARoMEOPublisher
* @see SHERPARoMEOJournalTitle
*/
public abstract class SHERPARoMEOProtocol implements ChoiceAuthority {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SHERPARoMEOProtocol.class);
// contact URL from configuration
private static String url = null;
public SHERPARoMEOProtocol() {
if (url == null) {
url = ConfigurationManager.getProperty("sherpa.romeo.url");
// sanity check
if (url == null) {
throw new IllegalStateException("Missing DSpace configuration keys for SHERPA/RoMEO Query");
}
}
}
// this implements the specific RoMEO API args and XML tag naming
public abstract Choices getMatches(String text, Collection collection, int start, int limit, String locale);
@Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) {
return getMatches(field, text, collection, 0, 2, locale);
}
// XXX FIXME just punt, returning value, never got around to
// implementing a reverse query.
@Override
public String getLabel(String field, String key, String locale) {
return key;
}
// NOTE - ignore limit and start for now
protected Choices query(String result, String label, String authority,
List<BasicNameValuePair> args, int start, int limit) {
HttpClient hc = new DefaultHttpClient();
String srUrl = url + "?" + URLEncodedUtils.format(args, "UTF8");
HttpGet get = new HttpGet(srUrl);
log.debug("Trying SHERPA/RoMEO Query, URL=" + srUrl);
try {
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
SRHandler handler = new SRHandler(result, label, authority);
// XXX FIXME: should turn off validation here explicitly, but
// it seems to be off by default.
xr.setFeature("http://xml.org/sax/features/namespaces", true);
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
xr.parse(new InputSource(response.getEntity().getContent()));
int confidence;
if (handler.total == 0) {
confidence = Choices.CF_NOTFOUND;
} else if (handler.total == 1) {
confidence = Choices.CF_UNCERTAIN;
} else {
confidence = Choices.CF_AMBIGUOUS;
}
return new Choices(handler.result, start, handler.total, confidence, false);
}
} catch (IOException e) {
log.error("SHERPA/RoMEO query failed: ", e);
return null;
} catch (ParserConfigurationException e) {
log.warn("Failed parsing SHERPA/RoMEO result: ", e);
return null;
} catch (SAXException e) {
log.warn("Failed parsing SHERPA/RoMEO result: ", e);
return null;
} finally {
get.releaseConnection();
}
return null;
}
// SAX handler to grab SHERPA/RoMEO (and eventually other details) from result
private static class SRHandler
extends DefaultHandler {
private Choice result[] = null;
int rindex = 0; // result index
int total = 0;
// name of element containing a result, e.g. <journal>
private String resultElement = null;
// name of element containing the label e.g. <name>
private String labelElement = null;
// name of element containing the authority value e.g. <issn>
private String authorityElement = null;
protected String textValue = null;
public SRHandler(String result, String label, String authority) {
super();
resultElement = result;
labelElement = label;
authorityElement = authority;
}
// NOTE: text value MAY be presented in multiple calls, even if
// it all one word, so be ready to splice it together.
// BEWARE: subclass's startElement method should call super()
// to null out 'value'. (Don't you miss the method combination
// options of a real object system like CLOS?)
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
String newValue = new String(ch, start, length);
if (newValue.length() > 0) {
if (textValue == null) {
textValue = newValue;
} else {
textValue += newValue;
}
}
}
// if this was the FIRST "numhits" element, it's size of results:
@Override
public void endElement(String namespaceURI, String localName,
String qName)
throws SAXException {
if (localName.equals("numhits")) {
String stotal = textValue.trim();
if (stotal.length() > 0) {
total = Integer.parseInt(stotal);
result = new Choice[total];
if (total > 0) {
result[0] = new Choice();
log.debug("Got " + total + " records in results.");
}
}
} else if (localName.equals(resultElement)) {
// after start of result element, get next hit ready
if (++rindex < result.length) {
result[rindex] = new Choice();
}
} else if (localName.equals(labelElement) && textValue != null) {
// plug in label value
result[rindex].value = textValue.trim();
result[rindex].label = result[rindex].value;
} else if (authorityElement != null && localName.equals(authorityElement) && textValue != null) {
// plug in authority value
result[rindex].authority = textValue.trim();
} else if (localName.equals("message") && textValue != null) {
// error message
log.warn("SHERPA/RoMEO response error message: " + textValue.trim());
}
}
// subclass overriding this MUST call it with super()
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts)
throws SAXException {
textValue = null;
}
@Override
public void error(SAXParseException exception)
throws SAXException {
throw new SAXException(exception);
}
@Override
public void fatalError(SAXParseException exception)
throws SAXException {
throw new SAXException(exception);
}
}
}

View File

@@ -1,61 +0,0 @@
/**
* 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.content.authority;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.message.BasicNameValuePair;
import org.dspace.content.Collection;
/**
* Sample Publisher name authority based on SHERPA/RoMEO
*
*
* WARNING: This is a very crude and incomplete implementation, done mainly
* as a proof-of-concept. Any site that actually wants to use it will
* probably have to refine it (and give patches back to dspace.org).
*
* @author Larry Stone
* @version $Revision $
* @see SHERPARoMEOProtocol
*/
public class SHERPARoMEOPublisher extends SHERPARoMEOProtocol {
protected static final String RESULT = "publisher";
protected static final String LABEL = "name";
// note: the publisher records have nothing we can use as authority code.
protected static final String AUTHORITY = null;
public SHERPARoMEOPublisher() {
super();
}
@Override
public Choices getMatches(String text, Collection collection, int start, int limit, String locale) {
// punt if there is no query text
if (text == null || text.trim().length() == 0) {
return new Choices(true);
}
// query args to add to SHERPA/RoMEO request URL
List<BasicNameValuePair> args = new ArrayList<BasicNameValuePair>();
args.add(new BasicNameValuePair("pub", text));
args.add(new BasicNameValuePair("qtype", "all")); // OR: starts, exact
Choices result = query(RESULT, LABEL, AUTHORITY, args, start, limit);
if (result == null) {
result = new Choices(true);
}
return result;
}
@Override
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) {
return getMatches(text, collection, start, limit, locale);
}
}

View File

@@ -22,6 +22,7 @@ import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.Site;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.packager.DSpaceAIPIngester;
import org.dspace.content.packager.METSManifest;
@@ -195,7 +196,7 @@ public class AIPTechMDCrosswalk implements IngestionCrosswalk, DisseminationCros
public Element disseminateElement(Context context, DSpaceObject dso)
throws CrosswalkException, IOException, SQLException,
AuthorizeException {
List<MockMetadataValue> dc = new ArrayList<>();
List<MetadataValueDTO> dc = new ArrayList<>();
if (dso.getType() == Constants.ITEM) {
Item item = (Item) dso;
EPerson is = item.getSubmitter();
@@ -282,8 +283,8 @@ public class AIPTechMDCrosswalk implements IngestionCrosswalk, DisseminationCros
return XSLTDisseminationCrosswalk.createDIM(dso, dc);
}
private static MockMetadataValue makeDC(String element, String qualifier, String value) {
MockMetadataValue dcv = new MockMetadataValue();
private static MetadataValueDTO makeDC(String element, String qualifier, String value) {
MetadataValueDTO dcv = new MetadataValueDTO();
dcv.setSchema("dc");
dcv.setLanguage(null);
dcv.setElement(element);

View File

@@ -29,6 +29,7 @@ import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.Site;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
@@ -327,7 +328,7 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin
private List<Element> disseminateListInternal(DSpaceObject dso, boolean addSchema)
throws CrosswalkException, IOException, SQLException, AuthorizeException {
List<MockMetadataValue> dcvs = null;
List<MetadataValueDTO> dcvs = null;
if (dso.getType() == Constants.ITEM) {
dcvs = item2Metadata((Item) dso);
} else if (dso.getType() == Constants.COLLECTION) {
@@ -344,7 +345,7 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin
List<Element> result = new ArrayList<Element>(dcvs.size());
for (MockMetadataValue dcv : dcvs) {
for (MetadataValueDTO dcv : dcvs) {
String qdc = dcv.getSchema() + "." + dcv.getElement();
if (dcv.getQualifier() != null) {
qdc += "." + dcv.getQualifier();
@@ -418,8 +419,8 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin
* @param site The site to derive metadata from
* @return list of metadata
*/
protected List<MockMetadataValue> site2Metadata(Site site) {
List<MockMetadataValue> metadata = new ArrayList<>();
protected List<MetadataValueDTO> site2Metadata(Site site) {
List<MetadataValueDTO> metadata = new ArrayList<>();
String identifier_uri = handleService.getCanonicalPrefix()
+ site.getHandle();
@@ -449,8 +450,8 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin
* @param community The community to derive metadata from
* @return list of metadata
*/
protected List<MockMetadataValue> community2Metadata(Community community) {
List<MockMetadataValue> metadata = new ArrayList<>();
protected List<MetadataValueDTO> community2Metadata(Community community) {
List<MetadataValueDTO> metadata = new ArrayList<>();
String description = communityService.getMetadata(community, "introductory_text");
String description_abstract = communityService.getMetadata(community, "short_description");
@@ -492,8 +493,8 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin
* @param collection The collection to derive metadata from
* @return list of metadata
*/
protected List<MockMetadataValue> collection2Metadata(Collection collection) {
List<MockMetadataValue> metadata = new ArrayList<>();
protected List<MetadataValueDTO> collection2Metadata(Collection collection) {
List<MetadataValueDTO> metadata = new ArrayList<>();
String description = collectionService.getMetadata(collection, "introductory_text");
String description_abstract = collectionService.getMetadata(collection, "short_description");
@@ -546,19 +547,19 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin
* @param item The item to derive metadata from
* @return list of metadata
*/
protected List<MockMetadataValue> item2Metadata(Item item) {
protected List<MetadataValueDTO> item2Metadata(Item item) {
List<MetadataValue> dcvs = itemService.getMetadata(item, Item.ANY, Item.ANY, Item.ANY,
Item.ANY);
List<MockMetadataValue> result = new ArrayList<>();
List<MetadataValueDTO> result = new ArrayList<>();
for (MetadataValue metadataValue : dcvs) {
result.add(new MockMetadataValue(metadataValue));
result.add(new MetadataValueDTO(metadataValue));
}
return result;
}
protected MockMetadataValue createDCValue(String element, String qualifier, String value) {
MockMetadataValue dcv = new MockMetadataValue();
protected MetadataValueDTO createDCValue(String element, String qualifier, String value) {
MetadataValueDTO dcv = new MetadataValueDTO();
dcv.setSchema("dc");
dcv.setElement(element);
dcv.setQualifier(qualifier);

View File

@@ -1,101 +0,0 @@
/**
* 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.content.crosswalk;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
import org.dspace.content.MetadataValue;
/**
* Metadata Value is bound to a database, the dissemination crosswalk require mock metadata just need for desimanation
* This class provides a wrapper for this.
* This class should only be used for the dissemniation metadata values that aren't to be written to the database
*
* @author kevinvandevelde at atmire.com
*/
public class MockMetadataValue {
private String schema;
private String element;
private String qualifier;
private String language;
private String value;
private String authority;
private int confidence;
public MockMetadataValue(MetadataValue metadataValue) {
MetadataField metadataField = metadataValue.getMetadataField();
MetadataSchema metadataSchema = metadataField.getMetadataSchema();
schema = metadataSchema.getName();
element = metadataField.getElement();
qualifier = metadataField.getQualifier();
language = metadataValue.getLanguage();
value = metadataValue.getValue();
authority = metadataValue.getAuthority();
confidence = metadataValue.getConfidence();
}
public MockMetadataValue() {
}
public String getSchema() {
return schema;
}
public String getElement() {
return element;
}
public String getQualifier() {
return qualifier;
}
public String getValue() {
return value;
}
public void setSchema(String schema) {
this.schema = schema;
}
public void setElement(String element) {
this.element = element;
}
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
}
public void setValue(String value) {
this.value = value;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
public int getConfidence() {
return confidence;
}
public void setConfidence(int confidence) {
this.confidence = confidence;
}
}

View File

@@ -31,6 +31,7 @@ import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.Site;
import org.dspace.content.authority.Choices;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
@@ -307,13 +308,13 @@ public class XSLTDisseminationCrosswalk
* @param dcvs list of metadata
* @return element
*/
public static Element createDIM(DSpaceObject dso, List<MockMetadataValue> dcvs) {
public static Element createDIM(DSpaceObject dso, List<MetadataValueDTO> dcvs) {
Element dim = new Element("dim", DIM_NS);
String type = Constants.typeText[dso.getType()];
dim.setAttribute("dspaceType", type);
for (int i = 0; i < dcvs.size(); i++) {
MockMetadataValue dcv = dcvs.get(i);
MetadataValueDTO dcv = dcvs.get(i);
Element field =
createField(dcv.getSchema(), dcv.getElement(), dcv.getQualifier(),
dcv.getLanguage(), dcv.getValue(), dcv.getAuthority(), dcv.getConfidence());
@@ -390,12 +391,12 @@ public class XSLTDisseminationCrosswalk
}
}
protected static List<MockMetadataValue> item2Metadata(Item item) {
protected static List<MetadataValueDTO> item2Metadata(Item item) {
List<MetadataValue> dcvs = itemService.getMetadata(item, Item.ANY, Item.ANY, Item.ANY,
Item.ANY);
List<MockMetadataValue> result = new ArrayList<>();
List<MetadataValueDTO> result = new ArrayList<>();
for (MetadataValue metadataValue : dcvs) {
result.add(new MockMetadataValue(metadataValue));
result.add(new MetadataValueDTO(metadataValue));
}
return result;

View File

@@ -0,0 +1,139 @@
/**
* 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.content.dto;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
import org.dspace.content.MetadataValue;
import org.dspace.content.authority.Choices;
/**
* This class acts as Data transfer object in which we can store data like in a regular MetadataValue object, but this
* one isn't saved in the DB. This can freely be used to represent Metadata without it being saved in the database,
* this will typically be used when transferring data
*
* @author kevinvandevelde at atmire.com
*/
public class MetadataValueDTO {
private String schema;
private String element;
private String qualifier;
private String language;
private String value;
private String authority;
private int confidence = Choices.CF_UNSET;
public MetadataValueDTO(MetadataValue metadataValue) {
MetadataField metadataField = metadataValue.getMetadataField();
MetadataSchema metadataSchema = metadataField.getMetadataSchema();
schema = metadataSchema.getName();
element = metadataField.getElement();
qualifier = metadataField.getQualifier();
language = metadataValue.getLanguage();
value = metadataValue.getValue();
authority = metadataValue.getAuthority();
confidence = metadataValue.getConfidence();
}
public MetadataValueDTO() {
}
/**
* Constructor for the MetadataValueDTO class
* @param schema The schema to be assigned to this MetadataValueDTO object
* @param element The element to be assigned to this MetadataValueDTO object
* @param qualifier The qualifier to be assigned to this MetadataValueDTO object
* @param language The language to be assigend to this MetadataValueDTO object
* @param value The value to be assigned to this MetadataValueDTO object
* @param authority The authority to be assigned to this MetadataValueDTO object
* @param confidence The confidence to be assigned to this MetadataValueDTO object
*/
public MetadataValueDTO(String schema, String element, String qualifier, String language, String value,
String authority, int confidence) {
this.schema = schema;
this.element = element;
this.qualifier = qualifier;
this.language = language;
this.value = value;
this.authority = authority;
this.confidence = confidence;
}
/**
* Constructor for the MetadataValueDTO class
* @param schema The schema to be assigned to this MetadataValueDTO object
* @param element The element to be assigned to this MetadataValueDTO object
* @param qualifier The qualifier to be assigned to this MetadataValueDTO object
* @param language The language to be assigend to this MetadataValueDTO object
* @param value The value to be assigned to this MetadataValueDTO object
*/
public MetadataValueDTO(String schema, String element, String qualifier, String language, String value) {
this.schema = schema;
this.element = element;
this.qualifier = qualifier;
this.language = language;
this.value = value;
}
public String getSchema() {
return schema;
}
public String getElement() {
return element;
}
public String getQualifier() {
return qualifier;
}
public String getValue() {
return value;
}
public void setSchema(String schema) {
this.schema = schema;
}
public void setElement(String element) {
this.element = element;
}
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
}
public void setValue(String value) {
this.value = value;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
public int getConfidence() {
return confidence;
}
public void setConfidence(int confidence) {
this.confidence = confidence;
}
}

View File

@@ -5,7 +5,7 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.authority.rest;
package org.dspace.external;
import java.io.InputStream;
import java.util.Scanner;
@@ -23,16 +23,16 @@ import org.apache.logging.log4j.Logger;
* @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com)
*/
public class RESTConnector {
public class OrcidRestConnector {
/**
* log4j logger
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RESTConnector.class);
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OrcidRestConnector.class);
private String url;
public RESTConnector(String url) {
public OrcidRestConnector(String url) {
this.url = url;
}

View File

@@ -0,0 +1,34 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.factory;
import org.dspace.external.service.ExternalDataService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* Abstract factory to get services for the External package. Use ExternalServiceFactory.getInstance() to retrieve
* an implementation
*/
public abstract class ExternalServiceFactory {
/**
* Calling this method will provide an ExternalDataService bean
* @return An implementation of the ExternalDataService
*/
public abstract ExternalDataService getExternalDataService();
/**
* This method will provide you with an implementation of this class to work with
* @return An implementation of this class to work with
*/
public static ExternalServiceFactory getInstance() {
return DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("externalServiceFactory", ExternalServiceFactory.class);
}
}

View File

@@ -0,0 +1,26 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.factory;
import org.dspace.external.service.ExternalDataService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Abstract factory to get services for the External package. Use ExternalServiceFactory.getInstance() to retrieve
* an implementation
*/
public class ExternalServiceFactoryImpl extends ExternalServiceFactory {
@Autowired(required = true)
private ExternalDataService externalDataService;
@Override
public ExternalDataService getExternalDataService() {
return externalDataService;
}
}

View File

@@ -0,0 +1,146 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.model;
import java.util.LinkedList;
import java.util.List;
import org.dspace.content.dto.MetadataValueDTO;
/**
* The representation model object for external data
*/
public class ExternalDataObject {
/**
* This field determines the ID for the ExternalDataObject
*/
private String id;
/**
* This field determines the value for the ExternalDataObject
*/
private String value;
/**
* This field determines where the ExternalData came from
*/
private String source;
/**
* The list of Metadata values. These our MetadataValueDTO because they won't exist in the DB
*/
private List<MetadataValueDTO> metadata = new LinkedList<>();
/**
* The display value of the ExternalDataObject
*/
private String displayValue;
/**
* Default constructor
*/
public ExternalDataObject() {
}
/**
* Constructor for the ExternalDataObject with as parameter the source of where it came from
* @param source The source where the ExternalDataObject came from
*/
public ExternalDataObject(String source) {
this.source = source;
}
/**
* Generic getter for the source
* @return The source
*/
public String getSource() {
return source;
}
/**
* Generic setter for the source
* @param source The source to be set
*/
public void setSource(String source) {
this.source = source;
}
/**
* Generic getter for the Metadata
* @return The metadata
*/
public List<MetadataValueDTO> getMetadata() {
return metadata;
}
/**
* Generic setter for the Metadata
* @param metadata The metadata to be set
*/
public void setMetadata(List<MetadataValueDTO> metadata) {
this.metadata = metadata;
}
/**
* This method will add a Metadata value to the list of metadata values
* @param metadataValueDTO The metadatavalue to be added
*/
public void addMetadata(MetadataValueDTO metadataValueDTO) {
if (metadata == null) {
metadata = new LinkedList<>();
}
metadata.add(metadataValueDTO);
}
/**
* Generic getter for the display value
* @return The display value
*/
public String getDisplayValue() {
return displayValue;
}
/**
* Generic setter for the display value
* @param displayValue The display value to be set
*/
public void setDisplayValue(String displayValue) {
this.displayValue = displayValue;
}
/**
* Generic getter for the ID
* @return The id
*/
public String getId() {
return id;
}
/**
* Generic setter for the ID
* @param id The id to be set
*/
public void setId(String id) {
this.id = id;
}
/**
* Generic getter for the value
* @return the value value of this ExternalDataObject
*/
public String getValue() {
return value;
}
/**
* Generic setter for the value
* @param value The value to be set on this ExternalDataObject
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,60 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.provider;
import java.util.List;
import java.util.Optional;
import org.dspace.external.model.ExternalDataObject;
/**
* This interface should be implemented by all providers that will deal with external data
*/
public interface ExternalDataProvider {
/**
* This method will return the SourceIdentifier for the ExternalDataProvider that implements the interface
* @return The source identifier as a String
*/
public String getSourceIdentifier();
/**
* This method will take a String id as a parameter and it'll call the ExternalDataProvider's endpoint or data
* source to retrieve and build the ExternalDataObject
* @param id The id on which will be searched
* @return An Optional object of ExternalDataObject. This is to indicate that this object may be null.
* This ExternalDataObject will return all the data returned by the ExternalDataProvider
*/
Optional<ExternalDataObject> getExternalDataObject(String id);
/**
* This method will query the ExternalDataProvider's endpoint or data source to retrieve and build a list of
* ExternalDataObjects through a search with the given parameters
* @param query The query for the search
* @param start The start of the search
* @param limit The max amount of records to be returned by the search
* @return A list of ExternalDataObjects that were retrieved and built by this search
*/
List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit);
/**
* This method will return a boolean indicating whether this ExternalDataProvider can deal with the given source
* or not
* @param source The source on which the check needs to be done
* @return A boolean indicating whether this ExternalDataProvider can deal with this source or not
*/
public boolean supports(String source);
/**
* Returns the total amount of results that this source can return for the given query
* @param query The query to be search on and give the total amount of results
* @return The total amount of results that the source can return for the given query
*/
public int getNumberOfResults(String query);
}

View File

@@ -5,27 +5,32 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.content.authority;
package org.dspace.external.provider.impl;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.DCPersonName;
import org.dspace.core.ConfigurationManager;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -51,16 +56,14 @@ import org.xml.sax.helpers.DefaultHandler;
*
* lcname.url = http://alcme.oclc.org/srw/search/lcnaf
*
* TODO: make # of results to ask for (and return) configurable.
*
* @author Larry Stone
* @version $Revision $
*/
public class LCNameAuthority implements ChoiceAuthority {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LCNameAuthority.class);
public class LCNameDataProvider implements ExternalDataProvider {
private static final Logger log = LogManager.getLogger(LCNameDataProvider.class);
// get these from configuration
protected static String url = null;
private String url;
private String sourceIdentifier;
// NS URI for SRU respones
protected static final String NS_SRU = "http://www.loc.gov/zing/srw/";
@@ -68,53 +71,53 @@ public class LCNameAuthority implements ChoiceAuthority {
// NS URI for MARC/XML
protected static final String NS_MX = "http://www.loc.gov/MARC21/slim";
// constructor does static init too..
public LCNameAuthority() {
if (url == null) {
url = ConfigurationManager.getProperty("lcname.url");
// sanity check
if (url == null) {
throw new IllegalStateException("Missing DSpace configuration keys for LCName Query");
}
}
public String getSourceIdentifier() {
return sourceIdentifier;
}
// punt! this is a poor implementation..
@Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) {
return getMatches(field, text, collection, 0, 2, locale);
public Optional<ExternalDataObject> getExternalDataObject(String id) {
StringBuilder query = new StringBuilder();
query.append("local.LCCN = \"").append(id).append("\"");
List<ExternalDataObject> list = doLookup(0, 10, query);
if (list.size() > 0) {
return Optional.of(list.get(0));
} else {
return Optional.empty();
}
}
/**
* Match a proposed value against name authority records
* Value is assumed to be in "Lastname, Firstname" format.
* Generic getter for the url
* @return the url value of this LCNameDataProvider
*/
@Override
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) {
Choices result = queryPerson(text, start, limit);
if (result == null) {
result = new Choices(true);
}
return result;
}
// punt; supposed to get the canonical display form of a metadata authority key
// XXX FIXME implement this with a query on the authority key, cache results
@Override
public String getLabel(String field, String key, String locale) {
return key;
public String getUrl() {
return url;
}
/**
* Guts of the implementation, returns a complete Choices result, or
* null for a failure.
* Generic setter for the url
* @param url The url to be set on this LCNameDataProvider
*/
private Choices queryPerson(String text, int start, int limit) {
public void setUrl(String url) {
this.url = url;
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this LCNameDataProvider
*/
public void setSourceIdentifier(String sourceIdentifier) {
this.sourceIdentifier = sourceIdentifier;
}
@Override
public List<ExternalDataObject> searchExternalDataObjects(String text, int start, int limit) {
// punt if there is no query text
if (text == null || text.trim().length() == 0) {
return new Choices(true);
return Collections.EMPTY_LIST;
}
// 1. build CQL query
@@ -123,12 +126,51 @@ public class LCNameAuthority implements ChoiceAuthority {
query.append("local.FirstName = \"").append(pn.getFirstNames()).
append("\" and local.FamilyName = \"").append(pn.getLastName()).
append("\"");
return doLookup(start, limit, query);
}
private List<ExternalDataObject> doLookup(int start, int limit, StringBuilder query) {
// XXX arbitrary default limit - should be configurable?
if (limit == 0) {
limit = 50;
}
HttpGet get = constructHttpGet(query, start, limit);
// 2. web request
try {
HttpClient hc = new DefaultHttpClient();
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SRUHandler handler = parseResponseToSRUHandler(response);
// this probably just means more results available..
if (handler.hits != handler.result.size()) {
log.warn("Discrepency in results, result.length=" + handler.result.size() +
", yet expected results=" + handler.hits);
}
return handler.result;
}
} catch (IOException e) {
log.error("SRU query failed: ", e);
return Collections.EMPTY_LIST;
} catch (ParserConfigurationException e) {
log.warn("Failed parsing SRU result: ", e);
return Collections.EMPTY_LIST;
} catch (SAXException e) {
log.warn("Failed parsing SRU result: ", e);
return Collections.EMPTY_LIST;
} finally {
get.releaseConnection();
}
return Collections.EMPTY_LIST;
}
public boolean supports(String source) {
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
}
private HttpGet constructHttpGet(StringBuilder query, int start, int limit) {
URI sruUri;
try {
URIBuilder builder = new URIBuilder(url);
@@ -141,66 +183,65 @@ public class LCNameAuthority implements ChoiceAuthority {
sruUri = builder.build();
} catch (URISyntaxException e) {
log.error("SRU query failed: ", e);
return new Choices(true);
return null;
}
HttpGet get = new HttpGet(sruUri);
log.debug("Trying SRU query, URL=" + sruUri);
return get;
}
@Override
public int getNumberOfResults(String query) {
// punt if there is no query text
if (query == null || query.trim().length() == 0) {
return 0;
}
// 1. build CQL query
DCPersonName pn = new DCPersonName(query);
StringBuilder queryStringBuilder = new StringBuilder();
queryStringBuilder.append("local.FirstName = \"").append(pn.getFirstNames()).
append("\" and local.FamilyName = \"").append(pn.getLastName()).
append("\"");
HttpGet get = constructHttpGet(queryStringBuilder, 0, 1);
// 2. web request
try {
HttpClient hc = new DefaultHttpClient();
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
SRUHandler handler = new SRUHandler();
// XXX FIXME: should turn off validation here explicitly, but
// it seems to be off by default.
xr.setFeature("http://xml.org/sax/features/namespaces", true);
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
HttpEntity responseBody = response.getEntity();
xr.parse(new InputSource(responseBody.getContent()));
// this probably just means more results available..
if (handler.hits != handler.result.size()) {
log.warn("Discrepency in results, result.length=" + handler.result.size() +
", yet expected results=" + handler.hits);
}
boolean more = handler.hits > (start + handler.result.size());
// XXX add non-auth option; perhaps the UI should do this?
// XXX it's really a policy matter if they allow unauth result.
// XXX good, stop it.
// handler.result.add(new Choice("", text, "Non-Authority: \""+text+"\""));
int confidence;
if (handler.hits == 0) {
confidence = Choices.CF_NOTFOUND;
} else if (handler.hits == 1) {
confidence = Choices.CF_UNCERTAIN;
} else {
confidence = Choices.CF_AMBIGUOUS;
}
return new Choices(handler.result.toArray(new Choice[handler.result.size()]),
start, handler.hits, confidence, more);
SRUHandler handler = parseResponseToSRUHandler(response);
return handler.hits;
}
} catch (IOException e) {
log.error("SRU query failed: ", e);
return new Choices(true);
} catch (ParserConfigurationException e) {
} catch (IOException | ParserConfigurationException | SAXException e) {
log.warn("Failed parsing SRU result: ", e);
return new Choices(true);
} catch (SAXException e) {
log.warn("Failed parsing SRU result: ", e);
return new Choices(true);
return 0;
} finally {
get.releaseConnection();
}
return new Choices(true);
return 0;
}
private SRUHandler parseResponseToSRUHandler(HttpResponse response)
throws ParserConfigurationException, SAXException, IOException {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
SRUHandler handler = new SRUHandler(sourceIdentifier);
// XXX FIXME: should turn off validation here explicitly, but
// it seems to be off by default.
xr.setFeature("http://xml.org/sax/features/namespaces", true);
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
HttpEntity responseBody = response.getEntity();
xr.parse(new InputSource(responseBody.getContent()));
return handler;
}
/**
@@ -212,14 +253,21 @@ public class LCNameAuthority implements ChoiceAuthority {
*/
private static class SRUHandler
extends DefaultHandler {
private List<Choice> result = new ArrayList<Choice>();
private String sourceIdentifier;
private List<ExternalDataObject> result = new ArrayList<ExternalDataObject>();
private int hits = -1;
private String textValue = null;
private String name = null;
private String birthDate = null;
private String lccn = null;
private String lastTag = null;
private String lastCode = null;
public SRUHandler(String sourceIdentifier) {
super();
this.sourceIdentifier = sourceIdentifier;
}
// NOTE: text value MAY be presented in multiple calls, even if
// it all one word, so be ready to splice it together.
// BEWARE: subclass's startElement method should call super()
@@ -259,14 +307,33 @@ public class LCNameAuthority implements ChoiceAuthority {
name = name.substring(0, name.length() - 1);
}
// XXX DEBUG
// log.debug("Got result, name="+name+", lccn="+lccn);
result.add(new Choice(lccn, name, name));
ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier);
externalDataObject.setDisplayValue(name);
externalDataObject.setValue(name);
externalDataObject.setId(lccn);
String[] names = name.split(", ");
String familyName = names[0];
String givenName = names.length > 1 ? names[1] : null;
if (StringUtils.isNotBlank(familyName)) {
externalDataObject
.addMetadata(new MetadataValueDTO("person", "familyName", null, null, familyName));
}
if (StringUtils.isNotBlank(givenName)) {
externalDataObject
.addMetadata(new MetadataValueDTO("person", "givenName", null, null, givenName));
}
if (StringUtils.isNotBlank(birthDate)) {
externalDataObject
.addMetadata(new MetadataValueDTO("person", "date", "birth", null, birthDate));
}
externalDataObject.addMetadata(new MetadataValueDTO("person", "identifier", "lccn", null, lccn));
result.add(externalDataObject);
} else {
log.warn("Got anomalous result, at least one of these null: lccn=" + lccn + ", name=" + name);
}
name = null;
lccn = null;
birthDate = null;
} else if (localName.equals("subfield") && namespaceURI.equals(NS_MX)) {
if (lastTag != null && lastCode != null) {
if (lastTag.equals("010") && lastCode.equals("a")) {
@@ -276,9 +343,8 @@ public class LCNameAuthority implements ChoiceAuthority {
// 100.a is the personal name
name = textValue;
}
if (lastTag.equals("100") && lastCode.equals("d") && (name != null)) {
name = name + " " + textValue;
if (lastTag.equals("100") && lastCode.equals("d")) {
birthDate = textValue;
}
}
}

View File

@@ -0,0 +1,280 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.provider.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.OrcidRestConnector;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
import org.dspace.external.provider.orcid.xml.XMLtoBio;
import org.json.JSONObject;
import org.orcid.jaxb.model.common_v2.OrcidId;
import org.orcid.jaxb.model.record_v2.Person;
import org.orcid.jaxb.model.search_v2.Result;
import org.springframework.beans.factory.annotation.Required;
/**
* This class is the implementation of the ExternalDataProvider interface that will deal with the OrcidV2 External
* Data lookup
*/
public class OrcidV2AuthorDataProvider implements ExternalDataProvider {
private static Logger log = LogManager.getLogger(OrcidV2AuthorDataProvider.class);
private OrcidRestConnector orcidRestConnector;
private String OAUTHUrl;
private String clientId;
private String clientSecret;
private String accessToken;
private String sourceIdentifier;
private String orcidUrl;
public static final String ORCID_ID_SYNTAX = "\\d{4}-\\d{4}-\\d{4}-(\\d{3}X|\\d{4})";
@Override
public String getSourceIdentifier() {
return sourceIdentifier;
}
/**
* Initialize the accessToken that is required for all subsequent calls to ORCID.
*
* @throws java.io.IOException passed through from HTTPclient.
*/
public void init() throws IOException {
if (StringUtils.isNotBlank(accessToken) && StringUtils.isNotBlank(clientSecret)) {
String authenticationParameters = "?client_id=" + clientId +
"&client_secret=" + clientSecret +
"&scope=/read-public&grant_type=client_credentials";
HttpPost httpPost = new HttpPost(OAUTHUrl + authenticationParameters);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
HttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse getResponse = httpClient.execute(httpPost);
JSONObject responseObject = null;
try (InputStream is = getResponse.getEntity().getContent();
BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
String inputStr;
while ((inputStr = streamReader.readLine()) != null && responseObject == null) {
if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token")) {
try {
responseObject = new JSONObject(inputStr);
} catch (Exception e) {
//Not as valid as I'd hoped, move along
responseObject = null;
}
}
}
}
if (responseObject != null && responseObject.has("access_token")) {
accessToken = (String) responseObject.get("access_token");
}
}
}
/**
* Makes an instance of the Orcidv2 class based on the provided parameters.
* This constructor is called through the spring bean initialization
*/
private OrcidV2AuthorDataProvider(String url) {
this.orcidRestConnector = new OrcidRestConnector(url);
}
@Override
public Optional<ExternalDataObject> getExternalDataObject(String id) {
Person person = getBio(id);
ExternalDataObject externalDataObject = convertToExternalDataObject(person);
return Optional.of(externalDataObject);
}
protected ExternalDataObject convertToExternalDataObject(Person person) {
ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier);
String lastName = "";
String firstName = "";
if (person.getName().getFamilyName() != null) {
lastName = person.getName().getFamilyName().getValue();
externalDataObject.addMetadata(new MetadataValueDTO("person", "familyName", null, null,
lastName));
}
if (person.getName().getGivenNames() != null) {
firstName = person.getName().getGivenNames().getValue();
externalDataObject.addMetadata(new MetadataValueDTO("person", "givenName", null, null,
firstName));
}
externalDataObject.setId(person.getName().getPath());
externalDataObject
.addMetadata(new MetadataValueDTO("dc", "identifier", "orcid", null, person.getName().getPath()));
externalDataObject
.addMetadata(new MetadataValueDTO("dc", "identifier", "uri", null, orcidUrl + person.getName().getPath()));
if (!StringUtils.isBlank(lastName) && !StringUtils.isBlank(firstName)) {
externalDataObject.setDisplayValue(lastName + ", " + firstName);
externalDataObject.setValue(lastName + ", " + firstName);
} else if (StringUtils.isBlank(firstName)) {
externalDataObject.setDisplayValue(lastName);
externalDataObject.setValue(lastName);
} else if (StringUtils.isBlank(lastName)) {
externalDataObject.setDisplayValue(firstName);
externalDataObject.setValue(firstName);
}
return externalDataObject;
}
/**
* Retrieve a Person object based on a given orcid identifier
* @param id orcid identifier
* @return Person
*/
public Person getBio(String id) {
log.debug("getBio called with ID=" + id);
if (!isValid(id)) {
return null;
}
InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken);
XMLtoBio converter = new XMLtoBio();
Person person = converter.convertSinglePerson(bioDocument);
try {
bioDocument.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return person;
}
/**
* Check to see if the provided text has the correct ORCID syntax.
* Since only searching on ORCID id is allowed, this way, we filter out any queries that would return a
* blank result anyway
*/
private boolean isValid(String text) {
return StringUtils.isNotBlank(text) && text.matches(ORCID_ID_SYNTAX);
}
@Override
public List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit) {
if (limit > 100) {
throw new IllegalArgumentException("The maximum number of results to retrieve cannot exceed 100.");
}
String searchPath = "search?q=" + URLEncoder.encode(query) + "&start=" + start + "&rows=" + limit;
log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
XMLtoBio converter = new XMLtoBio();
List<Result> results = converter.convert(bioDocument);
List<Person> bios = new LinkedList<>();
for (Result result : results) {
OrcidId orcidIdentifier = result.getOrcidIdentifier();
if (orcidIdentifier != null) {
log.debug("Found OrcidId=" + orcidIdentifier.toString());
String orcid = orcidIdentifier.getUriPath();
Person bio = getBio(orcid);
if (bio != null) {
bios.add(bio);
}
}
}
try {
bioDocument.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
if (bios == null) {
return Collections.emptyList();
} else {
return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList());
}
}
@Override
public boolean supports(String source) {
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
}
@Override
public int getNumberOfResults(String query) {
String searchPath = "search?q=" + URLEncoder.encode(query) + "&start=" + 0 + "&rows=" + 0;
log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
XMLtoBio converter = new XMLtoBio();
return converter.getNumberOfResultsFromXml(bioDocument);
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this OrcidV2AuthorDataProvider
*/
@Required
public void setSourceIdentifier(String sourceIdentifier) {
this.sourceIdentifier = sourceIdentifier;
}
/**
* Generic getter for the orcidUrl
* @return the orcidUrl value of this OrcidV2AuthorDataProvider
*/
public String getOrcidUrl() {
return orcidUrl;
}
/**
* Generic setter for the orcidUrl
* @param orcidUrl The orcidUrl to be set on this OrcidV2AuthorDataProvider
*/
@Required
public void setOrcidUrl(String orcidUrl) {
this.orcidUrl = orcidUrl;
}
/**
* Generic setter for the OAUTHUrl
* @param OAUTHUrl The OAUTHUrl to be set on this OrcidV2AuthorDataProvider
*/
public void setOAUTHUrl(String OAUTHUrl) {
this.OAUTHUrl = OAUTHUrl;
}
/**
* Generic setter for the clientId
* @param clientId The clientId to be set on this OrcidV2AuthorDataProvider
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Generic setter for the clientSecret
* @param clientSecret The clientSecret to be set on this OrcidV2AuthorDataProvider
*/
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
}

View File

@@ -0,0 +1,236 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.provider.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.logging.log4j.Logger;
import org.dspace.app.sherpa.SHERPAJournal;
import org.dspace.app.sherpa.SHERPAResponse;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
/**
* This class is the implementation of the ExternalDataProvider interface that will deal with SherpaJournal External
* data lookups
*/
public class SherpaJournalDataProvider implements ExternalDataProvider {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SherpaJournalDataProvider.class);
private String url;
private String sourceIdentifier;
private String apiKey;
private CloseableHttpClient client = null;
@Override
public String getSourceIdentifier() {
return sourceIdentifier;
}
/**
* Initialise the client that we need to call the endpoint
* @throws IOException If something goes wrong
*/
public void init() throws IOException {
HttpClientBuilder builder = HttpClientBuilder.create();
// httpclient 4.3+ doesn't appear to have any sensible defaults any more. Setting conservative defaults as
// not to hammer the SHERPA service too much.
client = builder
.disableAutomaticRetries()
.setMaxConnTotal(5)
.build();
}
@Override
public Optional<ExternalDataObject> getExternalDataObject(String id) {
HttpGet method = null;
SHERPAResponse sherpaResponse = null;
int timeout = 5000;
URIBuilder uriBuilder = null;
try {
uriBuilder = new URIBuilder(url);
uriBuilder.addParameter("jtitle", id);
if (StringUtils.isNotBlank(apiKey)) {
uriBuilder.addParameter("ak", apiKey);
}
method = new HttpGet(uriBuilder.build());
method.setConfig(RequestConfig.custom()
.setConnectionRequestTimeout(timeout)
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.build());
// Execute the method.
HttpResponse response = client.execute(method);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO return not OK status: "
+ statusCode);
}
HttpEntity responseBody = response.getEntity();
if (null != responseBody) {
sherpaResponse = new SHERPAResponse(responseBody.getContent());
} else {
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response");
}
} catch (Exception e) {
log.error("SHERPA/RoMEO query failed: ", e);
}
if (sherpaResponse == null) {
sherpaResponse = new SHERPAResponse(
"Error processing the SHERPA/RoMEO answer");
}
if (CollectionUtils.isNotEmpty(sherpaResponse.getJournals())) {
SHERPAJournal sherpaJournal = sherpaResponse.getJournals().get(0);
ExternalDataObject externalDataObject = constructExternalDataObjectFromSherpaJournal(sherpaJournal);
return Optional.of(externalDataObject);
}
return null;
}
private ExternalDataObject constructExternalDataObjectFromSherpaJournal(SHERPAJournal sherpaJournal) {
ExternalDataObject externalDataObject = new ExternalDataObject();
externalDataObject.setSource(sourceIdentifier);
externalDataObject.setId(sherpaJournal.getTitle());
externalDataObject
.addMetadata(new MetadataValueDTO("dc", "title", null, null, sherpaJournal.getTitle()));
externalDataObject
.addMetadata(new MetadataValueDTO("dc", "identifier", "issn", null, sherpaJournal.getIssn()));
externalDataObject.setValue(sherpaJournal.getTitle());
externalDataObject.setDisplayValue(sherpaJournal.getTitle());
return externalDataObject;
}
@Override
public List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit) {
// query args to add to SHERPA/RoMEO request URL
HttpGet get = constructHttpGet(query);
try {
HttpClient hc = new DefaultHttpClient();
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SHERPAResponse sherpaResponse = new SHERPAResponse(response.getEntity().getContent());
List<ExternalDataObject> list = sherpaResponse.getJournals().stream().map(
sherpaJournal -> constructExternalDataObjectFromSherpaJournal(sherpaJournal)).collect(
Collectors.toList());
// This is because Sherpa returns everything by default so we can't specifiy a start and limit
// in the query itself
return list.subList(start, Math.min(start + limit, list.size()));
}
} catch (IOException e) {
log.error("SHERPA/RoMEO query failed: ", e);
return null;
} finally {
get.releaseConnection();
}
return null;
}
private HttpGet constructHttpGet(String query) {
List<BasicNameValuePair> args = new ArrayList<BasicNameValuePair>();
args.add(new BasicNameValuePair("jtitle", query));
args.add(new BasicNameValuePair("qtype", "contains"));
args.add(new BasicNameValuePair("ak", apiKey));
String srUrl = url + "?" + URLEncodedUtils.format(args, "UTF8");
return new HttpGet(srUrl);
}
@Override
public boolean supports(String source) {
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
}
@Override
public int getNumberOfResults(String query) {
HttpGet get = constructHttpGet(query);
try {
HttpClient hc = new DefaultHttpClient();
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SHERPAResponse sherpaResponse = new SHERPAResponse(response.getEntity().getContent());
return sherpaResponse.getNumHits();
}
} catch (IOException e) {
log.error("SHERPA/RoMEO query failed: ", e);
return 0;
} finally {
get.releaseConnection();
}
return 0;
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this SherpaJournalDataProvider
*/
public void setSourceIdentifier(String sourceIdentifier) {
this.sourceIdentifier = sourceIdentifier;
}
/**
* Generic getter for the url
* @return the url value of this SherpaJournalDataProvider
*/
public String getUrl() {
return url;
}
/**
* Generic setter for the url
* @param url The url to be set on this SherpaJournalDataProvider
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Generic getter for the apiKey
* @return the apiKey value of this SherpaJournalDataProvider
*/
public String getApiKey() {
return apiKey;
}
/**
* Generic setter for the apiKey
* @param apiKey The apiKey to be set on this SherpaJournalDataProvider
*/
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
}

View File

@@ -0,0 +1,194 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.provider.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.logging.log4j.Logger;
import org.dspace.app.sherpa.SHERPAPublisher;
import org.dspace.app.sherpa.SHERPAResponse;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
/**
* This class is the implementation of the ExternalDataProvider interface that will deal with SherpaPublisher External
* data lookups
*/
public class SherpaPublisherDataProvider implements ExternalDataProvider {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SherpaPublisherDataProvider.class);
private String sourceIdentifier;
private String url;
private String apiKey;
@Override
public String getSourceIdentifier() {
return sourceIdentifier;
}
@Override
public Optional<ExternalDataObject> getExternalDataObject(String id) {
List<BasicNameValuePair> args = new ArrayList<BasicNameValuePair>();
args.add(new BasicNameValuePair("id", id));
args.add(new BasicNameValuePair("ak", apiKey));
HttpClient hc = new DefaultHttpClient();
String srUrl = url + "?" + URLEncodedUtils.format(args, "UTF8");
HttpGet get = new HttpGet(srUrl);
try {
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SHERPAResponse sherpaResponse = new SHERPAResponse(response.getEntity().getContent());
List<SHERPAPublisher> list = sherpaResponse.getPublishers();
if (CollectionUtils.isNotEmpty(list)) {
return Optional.of(constructExternalDataObjectFromSherpaPublisher(list.get(0)));
}
}
} catch (IOException e) {
log.error("SHERPA/RoMEO query failed: ", e);
return null;
} finally {
get.releaseConnection();
}
return null;
}
@Override
public List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit) {
HttpGet get = constructHttpGet(query);
try {
HttpClient hc = new DefaultHttpClient();
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SHERPAResponse sherpaResponse = new SHERPAResponse(response.getEntity().getContent());
List<ExternalDataObject> list = sherpaResponse.getPublishers().stream().map(
sherpaPublisher -> constructExternalDataObjectFromSherpaPublisher(sherpaPublisher)).collect(
Collectors.toList());
// This is because Sherpa returns everything by default so we can't specifiy a start and limit
// in the query itself
return list.subList(start, Math.min(start + limit, list.size()));
}
} catch (IOException e) {
log.error("SHERPA/RoMEO query failed: ", e);
return null;
} finally {
get.releaseConnection();
}
return null;
}
private HttpGet constructHttpGet(String query) {
List<BasicNameValuePair> args = new ArrayList<BasicNameValuePair>();
args.add(new BasicNameValuePair("pub", query));
args.add(new BasicNameValuePair("qtype", "all"));
args.add(new BasicNameValuePair("ak", apiKey));
String srUrl = url + "?" + URLEncodedUtils.format(args, "UTF8");
return new HttpGet(srUrl);
}
private ExternalDataObject constructExternalDataObjectFromSherpaPublisher(SHERPAPublisher sherpaPublisher) {
ExternalDataObject externalDataObject = new ExternalDataObject();
externalDataObject.setSource(sourceIdentifier);
//Text value == name
externalDataObject.addMetadata(new MetadataValueDTO("dc", "title", null, null, sherpaPublisher.getName()));
externalDataObject.setDisplayValue(sherpaPublisher.getName());
externalDataObject.setValue(sherpaPublisher.getName());
if (StringUtils.isNotBlank(sherpaPublisher.getId())) {
externalDataObject.setId(sherpaPublisher.getId());
externalDataObject
.addMetadata(
new MetadataValueDTO("dc", "identifier", "sherpaPublisher", null, sherpaPublisher.getId()));
}
//Text value == homeurl
externalDataObject
.addMetadata(new MetadataValueDTO("dc", "identifier", "other", null, sherpaPublisher.getHomeurl()));
return externalDataObject;
}
@Override
public boolean supports(String source) {
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
}
@Override
public int getNumberOfResults(String query) {
HttpGet get = constructHttpGet(query);
try {
HttpClient hc = new DefaultHttpClient();
HttpResponse response = hc.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
SHERPAResponse sherpaResponse = new SHERPAResponse(response.getEntity().getContent());
return sherpaResponse.getNumHits();
}
} catch (IOException e) {
log.error("SHERPA/RoMEO query failed: ", e);
return 0;
} finally {
get.releaseConnection();
}
return 0;
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this SherpaPublisherDataProvider
*/
public void setSourceIdentifier(String sourceIdentifier) {
this.sourceIdentifier = sourceIdentifier;
}
/**
* Generic getter for the url
* @return the url value of this SherpaPublisherDataProvider
*/
public String getUrl() {
return url;
}
/**
* Generic setter for the url
* @param url The url to be set on this SherpaPublisherDataProvider
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Generic getter for the apiKey
* @return the apiKey value of this SherpaPublisherDataProvider
*/
public String getApiKey() {
return apiKey;
}
/**
* Generic setter for the apiKey
* @param apiKey The apiKey to be set on this SherpaPublisherDataProvider
*/
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
}

View File

@@ -5,11 +5,10 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.authority.orcid.xml;
package org.dspace.external.provider.orcid.xml;
import java.io.InputStream;
import java.net.URISyntaxException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

View File

@@ -5,7 +5,7 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.authority.orcid.xml;
package org.dspace.external.provider.orcid.xml;
import java.io.InputStream;
import java.net.URISyntaxException;
@@ -13,9 +13,6 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.dspace.authority.orcid.Orcidv2;
import org.dspace.utils.DSpace;
import org.orcid.jaxb.model.common_v2.OrcidId;
import org.orcid.jaxb.model.record_v2.Person;
import org.orcid.jaxb.model.search_v2.Result;
import org.orcid.jaxb.model.search_v2.Search;
@@ -27,7 +24,7 @@ import org.xml.sax.SAXException;
* @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com)
*/
public class XMLtoBio extends Converter {
public class XMLtoBio extends Converter<List<Result>> {
/**
* log4j logger
@@ -35,29 +32,26 @@ public class XMLtoBio extends Converter {
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(XMLtoBio.class);
@Override
public List<Person> convert(InputStream xml) {
List<Person> bios = new ArrayList<>();
public List<Result> convert(InputStream xml) {
List<Result> bios = new ArrayList<>();
try {
Orcidv2 connector = new DSpace().getServiceManager().getServiceByName("AuthoritySource", Orcidv2.class);
Search search = (Search) unmarshall(xml, Search.class);
for (Result result : search.getResult()) {
OrcidId orcidIdentifier = result.getOrcidIdentifier();
if (orcidIdentifier != null) {
log.debug("Found OrcidId=" + orcidIdentifier.toString());
String orcid = orcidIdentifier.getUriPath();
Person bio = connector.getBio(orcid);
if (bio != null) {
bios.add(bio);
}
}
}
bios = search.getResult();
} catch (SAXException | URISyntaxException e) {
log.error(e);
}
return bios;
}
public int getNumberOfResultsFromXml(InputStream xml) {
try {
Search search = (Search) unmarshall(xml, Search.class);
return search.getNumFound().intValue();
} catch (SAXException | URISyntaxException e) {
log.error(e);
}
return 0;
}
public Person convertSinglePerson(InputStream xml) {
Person person = null;
try {

View File

@@ -0,0 +1,62 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.service;
import java.util.List;
import java.util.Optional;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
/**
* This is an interface that will deal with all Service level calls for External Data
*/
public interface ExternalDataService {
/**
* This method will return a list of ExternalDataProvider objects defined by all ExternalDataProvider spring beans
* @return A list of all ExternalDataProvider objects
*/
public List<ExternalDataProvider> getExternalDataProviders();
/**
* This method will return a single ExternalDataProvider which will support the given sourceIdentifier param
* @param sourceIdentifier The source identifier that the ExternalDataProvider that will be returned by this
* method has to support
* @return The ExternalDataProvider that supports the given source identifier
*/
public ExternalDataProvider getExternalDataProvider(String sourceIdentifier);
/**
* This method will return an Optional instance of ExternalDataObject for the given source and identifier
* It will try to retrieve one through an ExternalDataProvider as defined by the source with the given identifier
* @param source The source in which the lookup will be done
* @param identifier The identifier which will be looked up
* @return An Optional instance of ExternalDataObject
*/
public Optional<ExternalDataObject> getExternalDataObject(String source, String identifier);
/**
* This method will return a list of ExternalDataObjects as defined through the source in which they will be
* searched for, the given query start and limit parameters
* @param source The source that defines which ExternalDataProvider is to be used
* @param query The query for which the search will be done
* @param start The start of the search
* @param limit The maximum amount of records to be returned by the search
* @return A list of ExternalDataObjects that obey the rules in the parameters
*/
public List<ExternalDataObject> searchExternalDataObjects(String source, String query, int start, int limit);
/**
* This method wil return the total amount of results that will be found for the given query in the given source
* @param source The source in which the query will happen to return the number of results
* @param query The query to be ran in this source to retrieve the total amount of results
* @return The total amount of results that can be returned for this query in the given source
*/
public int getNumberOfResults(String source, String query);
}

View File

@@ -0,0 +1,68 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.service.impl;
import java.util.List;
import java.util.Optional;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
import org.dspace.external.service.ExternalDataService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link ExternalDataService}
*/
public class ExternalDataServiceImpl implements ExternalDataService {
@Autowired
private List<ExternalDataProvider> externalDataProviders;
@Override
public Optional<ExternalDataObject> getExternalDataObject(String source, String id) {
ExternalDataProvider provider = getExternalDataProvider(source);
if (provider == null) {
throw new IllegalArgumentException("Provider for: " + source + " couldn't be found");
}
return provider.getExternalDataObject(id);
}
@Override
public List<ExternalDataObject> searchExternalDataObjects(String source, String query, int start, int limit) {
ExternalDataProvider provider = getExternalDataProvider(source);
if (provider == null) {
throw new IllegalArgumentException("Provider for: " + source + " couldn't be found");
}
return provider.searchExternalDataObjects(query, start, limit);
}
@Override
public List<ExternalDataProvider> getExternalDataProviders() {
return externalDataProviders;
}
@Override
public ExternalDataProvider getExternalDataProvider(String sourceIdentifier) {
for (ExternalDataProvider externalDataProvider : externalDataProviders) {
if (externalDataProvider.supports(sourceIdentifier)) {
return externalDataProvider;
}
}
return null;
}
@Override
public int getNumberOfResults(String source, String query) {
ExternalDataProvider provider = getExternalDataProvider(source);
if (provider == null) {
throw new IllegalArgumentException("Provider for: " + source + " couldn't be found");
}
return provider.getNumberOfResults(query);
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean class="org.dspace.external.service.impl.ExternalDataServiceImpl"/>
<bean class="org.dspace.external.provider.impl.MockDataProvider" init-method="init">
<property name="sourceIdentifier" value="mock"/>
</bean>
</beans>

View File

@@ -0,0 +1,93 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.provider.impl;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
public class MockDataProvider implements ExternalDataProvider {
private Map<String, ExternalDataObject> mockLookupMap;
private String sourceIdentifier;
/**
* Generic getter for the sourceIdentifier
* @return the sourceIdentifier value of this MockDataProvider
*/
public String getSourceIdentifier() {
return sourceIdentifier;
}
public Optional<ExternalDataObject> getExternalDataObject(String id) {
ExternalDataObject externalDataObject = mockLookupMap.get(id);
if (externalDataObject == null) {
return Optional.empty();
} else {
return Optional.of(externalDataObject);
}
}
public List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit) {
List<ExternalDataObject> listToReturn = new LinkedList<>();
for (Map.Entry<String, ExternalDataObject> entry : mockLookupMap.entrySet()) {
if (StringUtils.containsIgnoreCase(entry.getKey(), query)) {
listToReturn.add(entry.getValue());
}
}
return listToReturn;
}
@Override
public boolean supports(String source) {
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
}
@Override
public int getNumberOfResults(String query) {
return searchExternalDataObjects(query, 0, 100).size();
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this MockDataProvider
*/
public void setSourceIdentifier(String sourceIdentifier) {
this.sourceIdentifier = sourceIdentifier;
}
public void init() throws IOException {
mockLookupMap = new HashMap<>();
List<String> externalDataObjectsToMake = new LinkedList<>();
externalDataObjectsToMake.add("one");
externalDataObjectsToMake.add("two");
externalDataObjectsToMake.add("three");
externalDataObjectsToMake.add("onetwo");
for (String id : externalDataObjectsToMake) {
ExternalDataObject externalDataObject = new ExternalDataObject("mock");
externalDataObject.setId(id);
externalDataObject.setValue(id);
externalDataObject.setDisplayValue(id);
List<MetadataValueDTO> list = new LinkedList<>();
list.add(new MetadataValueDTO("dc", "contributor", "author", null, "Donald, Smith"));
externalDataObject.setMetadata(list);
mockLookupMap.put(id, externalDataObject);
}
}
}

View File

@@ -0,0 +1,96 @@
/**
* 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 org.dspace.app.rest.link.HalLinkService;
import org.dspace.app.rest.model.ExternalSourceEntryRest;
import org.dspace.app.rest.model.hateoas.ExternalSourceEntryResource;
import org.dspace.app.rest.repository.ExternalSourceRestRepository;
import org.dspace.app.rest.utils.Utils;
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.PagedResources;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* This RestController takes care of the retrieval of External data from various endpoints and providers depending
* on the calls it receives
*/
@RestController
@RequestMapping("/api/integration/externalsources/{externalSourceName}")
public class ExternalSourcesRestController {
@Autowired
private ExternalSourceRestRepository externalSourceRestRepository;
@Autowired
protected Utils utils;
@Autowired
HalLinkService linkService;
/**
* This method will retrieve all the ExternalSourceEntries for the ExternalSource for the given externalSourceName
* param
*
* curl -X GET http://<dspace.restUrl>/api/integration/externalsources/orcidV2/entries
*
* @param externalSourceName The externalSourceName that defines which ExternalDataProvider is used
* @param query The query used in the lookup
* @param parent The parent used in the lookup
* @param pageable The pagination object
* @param assembler The assembler object
* @return A paginated list of ExternalSourceEntryResource objects that comply with the params
*/
@RequestMapping(method = RequestMethod.GET, value = "/entries")
public PagedResources<ExternalSourceEntryResource> getExternalSourceEntries(
@PathVariable("externalSourceName") String externalSourceName,
@RequestParam(name = "query") String query,
@RequestParam(name = "parent", required = false) String parent,
Pageable pageable, PagedResourcesAssembler assembler) {
Page<ExternalSourceEntryRest> externalSourceEntryRestPage = externalSourceRestRepository
.getExternalSourceEntries(externalSourceName, query, parent, pageable);
Page<ExternalSourceEntryResource> externalSourceEntryResources = externalSourceEntryRestPage
.map(externalSourceEntryRest -> new ExternalSourceEntryResource(externalSourceEntryRest));
externalSourceEntryResources.forEach(linkService::addLinks);
PagedResources<ExternalSourceEntryResource> result = assembler.toResource(externalSourceEntryResources);
return result;
}
/**
* This method will retrieve one ExternalSourceEntryResource based on the ExternalSource for the given
* externalSourceName and with the given entryId
*
* curl -X GET http://<dspace.restUrl>/api/integration/externalsources/orcidV2/entries/0000-0000-0000-0000
*
* @param externalSourceName The externalSourceName that defines which ExternalDataProvider is used
* @param entryId The entryId used for the lookup
* @return An ExternalSourceEntryResource that complies with the above params
*/
@RequestMapping(method = RequestMethod.GET, value = "/entryValues/{entryId}")
public ExternalSourceEntryResource getExternalSourceEntryValue(@PathVariable("externalSourceName") String
externalSourceName,
@PathVariable("entryId") String entryId) {
ExternalSourceEntryRest externalSourceEntryRest = externalSourceRestRepository
.getExternalSourceEntryValue(externalSourceName, entryId);
ExternalSourceEntryResource externalSourceEntryResource = new ExternalSourceEntryResource(
externalSourceEntryRest);
linkService.addLinks(externalSourceEntryResource);
return externalSourceEntryResource;
}
}

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.app.rest.converter;
import org.dspace.app.rest.model.ExternalSourceEntryRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.external.model.ExternalDataObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* This converter deals with the conversion between ExternalDataObjects and ExternalSourceEntryRest objects
*/
@Component
public class ExternalSourceEntryRestConverter implements DSpaceConverter<ExternalDataObject, ExternalSourceEntryRest> {
@Autowired
private MetadataValueDTOListConverter metadataConverter;
public ExternalSourceEntryRest convert(ExternalDataObject modelObject, Projection projection) {
ExternalSourceEntryRest externalSourceEntryRest = new ExternalSourceEntryRest();
externalSourceEntryRest.setId(modelObject.getId());
externalSourceEntryRest.setExternalSource(modelObject.getSource());
externalSourceEntryRest.setDisplay(modelObject.getDisplayValue());
externalSourceEntryRest.setValue(modelObject.getValue());
externalSourceEntryRest.setExternalSource(modelObject.getSource());
externalSourceEntryRest.setMetadata(metadataConverter.convert(modelObject.getMetadata()));
return externalSourceEntryRest;
}
public Class<ExternalDataObject> getModelClass() {
return ExternalDataObject.class;
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.converter;
import org.dspace.app.rest.model.ExternalSourceRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.external.provider.ExternalDataProvider;
import org.springframework.stereotype.Component;
/**
* This converter deals with the conversion between ExternalDataProvider objects and ExternalSourceRest objects
*/
@Component
public class ExternalSourceRestConverter implements DSpaceConverter<ExternalDataProvider, ExternalSourceRest> {
public ExternalSourceRest convert(ExternalDataProvider modelObject, Projection projection) {
ExternalSourceRest externalSourceRest = new ExternalSourceRest();
externalSourceRest.setId(modelObject.getSourceIdentifier());
externalSourceRest.setName(modelObject.getSourceIdentifier());
externalSourceRest.setHierarchical(false);
return externalSourceRest;
}
public Class<ExternalDataProvider> getModelClass() {
return ExternalDataProvider.class;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.converter;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.content.MetadataValue;
import org.dspace.content.dto.MetadataValueDTO;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* Converter to translate between domain {@link MetadataValue}s and {@link MetadataValueRest} representations.
*/
@Component
public class MetadataValueDTOConverter implements Converter<MetadataValueDTO, MetadataValueRest> {
/**
* Gets a rest representation of the given domain metadata value.
*
* @param metadataValue the domain value.
* @return the rest representation.
*/
@Override
public MetadataValueRest convert(MetadataValueDTO metadataValue) {
MetadataValueRest metadataValueRest = new MetadataValueRest();
metadataValueRest.setValue(metadataValue.getValue());
metadataValueRest.setLanguage(metadataValue.getLanguage());
metadataValueRest.setAuthority(metadataValue.getAuthority());
metadataValueRest.setConfidence(metadataValue.getConfidence());
return metadataValueRest;
}
}

View File

@@ -0,0 +1,68 @@
/**
* 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.converter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.content.MetadataValue;
import org.dspace.content.dto.MetadataValueDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* Converter to translate between lists of domain {@link MetadataValue}s and {@link MetadataRest} representations.
*/
@Component
public class MetadataValueDTOListConverter implements Converter<List<MetadataValueDTO>, MetadataRest> {
@Autowired
private MetadataValueDTOConverter valueConverter;
/**
* Gets a rest representation of the given list of domain metadata values.
*
* @param metadataValueList the domain values.
* @return the rest representation.
*/
@Override
public MetadataRest convert(List<MetadataValueDTO> metadataValueList) {
// Convert each value to a DTO while retaining place order in a map of key -> SortedSet
Map<String, List<MetadataValueRest>> mapOfLists = new HashMap<>();
for (MetadataValueDTO metadataValue : metadataValueList) {
String key = metadataValue.getSchema() + "." + metadataValue.getElement();
if (StringUtils.isNotBlank(metadataValue.getQualifier())) {
key += "." + metadataValue.getQualifier();
}
List<MetadataValueRest> list = mapOfLists.get(key);
if (list == null) {
list = new LinkedList();
mapOfLists.put(key, list);
}
list.add(valueConverter.convert(metadataValue));
}
MetadataRest metadataRest = new MetadataRest();
// Populate MetadataRest's map of key -> List while respecting SortedSet's order
Map<String, List<MetadataValueRest>> metadataRestMap = metadataRest.getMap();
for (Map.Entry<String, List<MetadataValueRest>> entry : mapOfLists.entrySet()) {
metadataRestMap.put(entry.getKey(), entry.getValue().stream().collect(Collectors.toList()));
}
return metadataRest;
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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.link.externalsources;
import java.util.LinkedList;
import org.dspace.app.rest.ExternalSourcesRestController;
import org.dspace.app.rest.link.HalLinkFactory;
import org.dspace.app.rest.model.hateoas.ExternalSourceEntryResource;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
/**
* This HalLinkFactory adds links to the ExternalSourceEntryResource object
*/
@Component
public class ExternalSourceEntryHalLinkFactory
extends HalLinkFactory<ExternalSourceEntryResource, ExternalSourcesRestController> {
@Override
protected void addLinks(ExternalSourceEntryResource halResource, Pageable pageable, LinkedList<Link> list)
throws Exception {
list.add(buildLink(Link.REL_SELF,
getMethodOn().getExternalSourceEntryValue(halResource.getContent().getExternalSource(),
halResource.getContent().getId())));
}
@Override
protected Class<ExternalSourcesRestController> getControllerClass() {
return ExternalSourcesRestController.class;
}
protected Class<ExternalSourceEntryResource> getResourceClass() {
return ExternalSourceEntryResource.class;
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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.link.externalsources;
import java.util.LinkedList;
import org.dspace.app.rest.ExternalSourcesRestController;
import org.dspace.app.rest.link.HalLinkFactory;
import org.dspace.app.rest.model.hateoas.ExternalSourceResource;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
/**
* This HalLinkFactory adds links to the ExternalSourceResource object
*/
@Component
public class ExternalSourceHalLinkFactory extends
HalLinkFactory<ExternalSourceResource, ExternalSourcesRestController> {
@Override
protected void addLinks(ExternalSourceResource halResource, Pageable pageable, LinkedList<Link> list)
throws Exception {
list.add(buildLink("entries", getMethodOn()
.getExternalSourceEntries(halResource.getContent().getName(), "", null, null, null)));
}
@Override
protected Class<ExternalSourcesRestController> getControllerClass() {
return ExternalSourcesRestController.class;
}
@Override
protected Class<ExternalSourceResource> getResourceClass() {
return ExternalSourceResource.class;
}
}

View File

@@ -0,0 +1,121 @@
/**
* 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.model;
import org.dspace.app.rest.ExternalSourcesRestController;
/**
* This class serves as a REST representation for an entry of external data
*/
public class ExternalSourceEntryRest extends BaseObjectRest<String> {
public static final String NAME = "externalSourceEntry";
public static final String PLURAL_NAME = "externalSourceEntries";
public static final String CATEGORY = RestAddressableModel.INTEGRATION;
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return ExternalSourcesRestController.class;
}
@Override
public String getType() {
return NAME;
}
private String id;
private String display;
private String value;
private String externalSource;
private MetadataRest metadata = new MetadataRest();
/**
* Generic getter for the id
* @return the id value of this ExternalSourceEntryRest
*/
public String getId() {
return id;
}
/**
* Generic setter for the id
* @param id The id to be set on this ExternalSourceEntryRest
*/
public void setId(String id) {
this.id = id;
}
/**
* Generic getter for the display
* @return the display value of this ExternalSourceEntryRest
*/
public String getDisplay() {
return display;
}
/**
* Generic setter for the display
* @param display The display to be set on this ExternalSourceEntryRest
*/
public void setDisplay(String display) {
this.display = display;
}
/**
* Generic getter for the value
* @return the value value of this ExternalSourceEntryRest
*/
public String getValue() {
return value;
}
/**
* Generic setter for the value
* @param value The value to be set on this ExternalSourceEntryRest
*/
public void setValue(String value) {
this.value = value;
}
/**
* Generic getter for the externalSource
* @return the externalSource value of this ExternalSourceEntryRest
*/
public String getExternalSource() {
return externalSource;
}
/**
* Generic setter for the externalSource
* @param externalSource The externalSource to be set on this ExternalSourceEntryRest
*/
public void setExternalSource(String externalSource) {
this.externalSource = externalSource;
}
/**
* Generic getter for the metadata
* @return the metadata value of this ExternalSourceEntryRest
*/
public MetadataRest getMetadata() {
return metadata;
}
/**
* Generic setter for the metadata
* @param metadata The metadata to be set on this ExternalSourceEntryRest
*/
public void setMetadata(MetadataRest metadata) {
this.metadata = metadata;
}
}

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.app.rest.model;
import org.dspace.app.rest.RestResourceController;
/**
* This class serves as a REST representation for an External Source
*/
public class ExternalSourceRest extends BaseObjectRest<String> {
public static final String NAME = "externalsource";
public static final String PLURAL_NAME = "externalsources";
public static final String CATEGORY = RestAddressableModel.INTEGRATION;
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return RestResourceController.class;
}
@Override
public String getType() {
return NAME;
}
private String id;
private String name;
private boolean hierarchical;
/**
* Generic getter for the id
* @return the id value of this ExternalSourceRest
*/
public String getId() {
return id;
}
/**
* Generic setter for the id
* @param id The id to be set on this ExternalSourceRest
*/
public void setId(String id) {
this.id = id;
}
/**
* Generic getter for the name
* @return the name value of this ExternalSourceRest
*/
public String getName() {
return name;
}
/**
* Generic setter for the name
* @param name The name to be set on this ExternalSourceRest
*/
public void setName(String name) {
this.name = name;
}
/**
* Generic getter for the hierarchical
* @return the hierarchical value of this ExternalSourceRest
*/
public boolean isHierarchical() {
return hierarchical;
}
/**
* Generic setter for the hierarchical
* @param hierarchical The hierarchical to be set on this ExternalSourceRest
*/
public void setHierarchical(boolean hierarchical) {
this.hierarchical = hierarchical;
}
}

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.app.rest.model.hateoas;
import org.dspace.app.rest.model.ExternalSourceEntryRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
/**
* This class serves as the HAL Resource for an ExternalSourceEntryRest object
*/
@RelNameDSpaceResource(ExternalSourceEntryRest.NAME)
public class ExternalSourceEntryResource extends HALResource<ExternalSourceEntryRest> {
public ExternalSourceEntryResource(ExternalSourceEntryRest content) {
super(content);
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.model.hateoas;
import org.dspace.app.rest.model.ExternalSourceRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* This class serves as the HAL Resource for the ExternalSourceRest object
*/
@RelNameDSpaceResource(ExternalSourceRest.NAME)
public class ExternalSourceResource extends DSpaceResource<ExternalSourceRest> {
public ExternalSourceResource(ExternalSourceRest externalSourceRest, Utils utils) {
super(externalSourceRest, utils);
}
}

View File

@@ -0,0 +1,99 @@
/**
* 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.repository;
import java.util.List;
import java.util.Optional;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.model.ExternalSourceEntryRest;
import org.dspace.app.rest.model.ExternalSourceRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.core.Context;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
import org.dspace.external.service.ExternalDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.stereotype.Component;
/**
* This is the Repository that is responsible for the functionality and implementations coming from
* {@link org.dspace.app.rest.ExternalSourcesRestController}
*/
@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.NAME)
public class ExternalSourceRestRepository extends DSpaceRestRepository<ExternalSourceRest, String> {
@Autowired
private ExternalDataService externalDataService;
@Autowired
ConverterService converter;
/**
* This method will retrieve one ExternalSourceEntryResource based on the ExternalSource for the given
* externalSourceName and with the given entryId
* @param externalSourceName The externalSourceName that defines which ExternalDataProvider is used
* @param entryId The entryId used for the lookup
* @return An ExternalSourceEntryRest object that complies with the above params
*/
public ExternalSourceEntryRest getExternalSourceEntryValue(String externalSourceName, String entryId) {
if (externalDataService.getExternalDataProvider(externalSourceName) == null) {
throw new ResourceNotFoundException("The externalSource for: " + externalSourceName + " couldn't be found");
}
Optional<ExternalDataObject> externalDataObject = externalDataService.getExternalDataObject(externalSourceName,
entryId);
ExternalDataObject dataObject = externalDataObject.orElseThrow(() -> new ResourceNotFoundException(
"Couldn't find an ExternalSource for source: " + externalSourceName + " and ID: " + entryId));
return converter.toRest(dataObject, Projection.DEFAULT);
}
/**
* This method will retrieve all the ExternalSourceEntries for the ExternalSource for the given externalSourceName
* param
* @param externalSourceName The externalSourceName that defines which ExternalDataProvider is used
* @param query The query used in the lookup
* @param parent The parent used in the lookup
* @param pageable The pagination object
* @return A paginated list of ExternalSourceEntryResource objects that comply with the params
*/
public Page<ExternalSourceEntryRest> getExternalSourceEntries(String externalSourceName, String query,
String parent, Pageable pageable) {
if (externalDataService.getExternalDataProvider(externalSourceName) == null) {
throw new ResourceNotFoundException("The externalSource for: " + externalSourceName + " couldn't be found");
}
List<ExternalDataObject> externalDataObjects = externalDataService
.searchExternalDataObjects(externalSourceName, query, pageable.getOffset(), pageable.getPageSize());
int numberOfResults = externalDataService.getNumberOfResults(externalSourceName, query);
return converter.toRestPage(externalDataObjects, pageable, numberOfResults,
utils.obtainProjection(true));
}
@Override
public ExternalSourceRest findOne(Context context, String externalSourceName) {
ExternalDataProvider externalDataProvider = externalDataService.getExternalDataProvider(externalSourceName);
if (externalDataProvider == null) {
throw new ResourceNotFoundException("ExternalDataProvider for: " +
externalSourceName + " couldn't be found");
}
return converter.toRest(externalDataProvider, Projection.DEFAULT);
}
@Override
public Page<ExternalSourceRest> findAll(Context context, Pageable pageable) {
List<ExternalDataProvider> externalSources = externalDataService.getExternalDataProviders();
return converter.toRestPage(externalSources, pageable, externalSources.size(),
utils.obtainProjection(true));
}
public Class<ExternalSourceRest> getDomainClass() {
return ExternalSourceRest.class;
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean class="org.dspace.external.service.impl.ExternalDataServiceImpl"/>
<bean class="org.dspace.external.provider.impl.MockDataProvider" init-method="init">
<property name="sourceIdentifier" value="mock"/>
</bean>
</beans>

View File

@@ -0,0 +1,119 @@
/**
* 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.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.rest.matcher.ExternalSourceEntryMatcher;
import org.dspace.app.rest.matcher.ExternalSourceMatcher;
import org.dspace.app.rest.matcher.PageMatcher;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.hamcrest.Matchers;
import org.junit.Test;
public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrationTest {
@Test
public void findAllExternalSources() throws Exception {
getClient().perform(get("/api/integration/externalsources"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItem(
ExternalSourceMatcher.matchExternalSource("mock", "mock", false)
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Test
public void findOneExternalSourcesExistingSources() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ExternalSourceMatcher.matchExternalSource("mock", "mock", false)
)));
}
@Test
public void findOneExternalSourcesNotExistingSources() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock2"))
.andExpect(status().isNotFound());
}
@Test
public void findOneExternalSourceEntryValue() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock/entryValues/one"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ExternalSourceEntryMatcher.matchExternalSourceEntry("one", "one", "one", "mock")
)));
}
@Test
public void findOneExternalSourceEntryValueInvalidEntryId() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock/entryValues/entryIdInvalid"))
.andExpect(status().isNotFound());
}
@Test
public void findOneExternalSourceEntryValueInvalidSource() throws Exception {
getClient().perform(get("/api/integration/externalsources/mocktwo/entryValues/one"))
.andExpect(status().isNotFound());
}
@Test
public void findOneExternalSourceEntriesInvalidSource() throws Exception {
getClient().perform(get("/api/integration/externalsources/mocktwo/entries")
.param("query", "test"))
.andExpect(status().isNotFound());
}
@Test
public void findOneExternalSourceEntriesApplicableQuery() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock/entries")
.param("query", "one"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries", Matchers.containsInAnyOrder(
ExternalSourceEntryMatcher.matchExternalSourceEntry("one", "one", "one", "mock"),
ExternalSourceEntryMatcher.matchExternalSourceEntry("onetwo", "onetwo", "onetwo", "mock")
)))
.andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2)));
}
@Test
public void findOneExternalSourceEntriesApplicableQueryPagination() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock/entries")
.param("query", "one").param("size", "1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries", Matchers.hasItem(
ExternalSourceEntryMatcher.matchExternalSourceEntry("onetwo", "onetwo", "onetwo", "mock")
)))
.andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(0, 1, 2, 2)));
getClient().perform(get("/api/integration/externalsources/mock/entries")
.param("query", "one").param("size", "1").param("page", "1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries", Matchers.hasItem(
ExternalSourceEntryMatcher.matchExternalSourceEntry("one", "one", "one", "mock")
)))
.andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(1, 1, 2, 2)));
}
@Test
public void findOneExternalSourceEntriesNoReturnQuery() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock/entries")
.param("query", "randomqueryfornoresults"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded").doesNotExist());
}
@Test
public void findOneExternalSourceEntriesNoQuery() throws Exception {
getClient().perform(get("/api/integration/externalsources/mock/entries"))
.andExpect(status().isUnprocessableEntity());
}
}

View File

@@ -0,0 +1,30 @@
/**
* 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.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import org.hamcrest.Matcher;
public class ExternalSourceEntryMatcher {
private ExternalSourceEntryMatcher() { }
public static Matcher<? super Object> matchExternalSourceEntry(String id, String displayValue,
String value, String source) {
return allOf(
hasJsonPath("$.id", is(id)),
hasJsonPath("$.display", is(displayValue)),
hasJsonPath("$.value", is(value)),
hasJsonPath("$.externalSource", is(source))
);
}
}

View File

@@ -0,0 +1,28 @@
/**
* 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.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import org.hamcrest.Matcher;
public class ExternalSourceMatcher {
private ExternalSourceMatcher() {
}
public static Matcher<? super Object> matchExternalSource(String id, String name, boolean hierarchical) {
return allOf(
hasJsonPath("$.id", is(id)),
hasJsonPath("$.name", is(name)),
hasJsonPath("$.hierarchical", is(hierarchical))
);
}
}

View File

@@ -0,0 +1,93 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.external.provider.impl;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.ExternalDataProvider;
public class MockDataProvider implements ExternalDataProvider {
private Map<String, ExternalDataObject> mockLookupMap;
private String sourceIdentifier;
/**
* Generic getter for the sourceIdentifier
* @return the sourceIdentifier value of this MockDataProvider
*/
public String getSourceIdentifier() {
return sourceIdentifier;
}
public Optional<ExternalDataObject> getExternalDataObject(String id) {
ExternalDataObject externalDataObject = mockLookupMap.get(id);
if (externalDataObject == null) {
return Optional.empty();
} else {
return Optional.of(externalDataObject);
}
}
public List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit) {
List<ExternalDataObject> listToReturn = new LinkedList<>();
for (Map.Entry<String, ExternalDataObject> entry : mockLookupMap.entrySet()) {
if (StringUtils.containsIgnoreCase(entry.getKey(), query)) {
listToReturn.add(entry.getValue());
}
}
return listToReturn;
}
@Override
public boolean supports(String source) {
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
}
@Override
public int getNumberOfResults(String query) {
return searchExternalDataObjects(query, 0, 100).size();
}
/**
* Generic setter for the sourceIdentifier
* @param sourceIdentifier The sourceIdentifier to be set on this MockDataProvider
*/
public void setSourceIdentifier(String sourceIdentifier) {
this.sourceIdentifier = sourceIdentifier;
}
public void init() throws IOException {
mockLookupMap = new HashMap<>();
List<String> externalDataObjectsToMake = new LinkedList<>();
externalDataObjectsToMake.add("one");
externalDataObjectsToMake.add("two");
externalDataObjectsToMake.add("three");
externalDataObjectsToMake.add("onetwo");
for (String id : externalDataObjectsToMake) {
ExternalDataObject externalDataObject = new ExternalDataObject("mock");
externalDataObject.setId(id);
externalDataObject.setValue(id);
externalDataObject.setDisplayValue(id);
List<MetadataValueDTO> list = new LinkedList<>();
list.add(new MetadataValueDTO("dc", "contributor", "author", null, "Donald, Smith"));
externalDataObject.setMetadata(list);
mockLookupMap.put(id, externalDataObject);
}
}
}

View File

@@ -1449,20 +1449,13 @@ sitemap.engineurls = http://www.google.com/webmasters/sitemaps/ping?sitemap=
# the SHERPA/RoMEO endpoint
sherpa.romeo.url = http://www.sherpa.ac.uk/romeo/api29.php
# to disable the sherpa/romeo integration
# uncomment the follow line
# webui.submission.sherparomeo-policy-enabled = false
# please register for a free api access key to get many benefits
# http://www.sherpa.ac.uk/news/romeoapikeys.htm
# sherpa.romeo.apikey = YOUR-API-KEY
sherpa.romeo.apikey =
##### Authority Control Settings #####
#plugin.named.org.dspace.content.authority.ChoiceAuthority = \
# org.dspace.content.authority.SampleAuthority = Sample, \
# org.dspace.content.authority.LCNameAuthority = LCNameAuthority, \
# org.dspace.content.authority.SHERPARoMEOPublisher = SRPublisher, \
# org.dspace.content.authority.SHERPARoMEOJournalTitle = SRJournalTitle, \
# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority
#Uncomment to enable ORCID authority control
@@ -1472,6 +1465,7 @@ sherpa.romeo.url = http://www.sherpa.ac.uk/romeo/api29.php
# URL of ORCID API
# Defaults to using the Public API (pub.orcid.org)
orcid.api.url = https://pub.orcid.org/v2.1
orcid.url = https://orcid.org/
## The DCInputAuthority plugin is automatically configured with every
## value-pairs element in input-forms.xml, namely:
@@ -1493,7 +1487,7 @@ plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \
org.dspace.content.authority.DSpaceControlledVocabulary
## configure LC Names plugin
#lcname.url = http://alcme.oclc.org/srw/search/lcnaf
lcname.url = http://alcme.oclc.org/srw/search/lcnaf
##
## This sets the default lowest confidence level at which a metadata value is included
@@ -1518,10 +1512,6 @@ authority.minconfidence = ambiguous
#
#authority.author.indexer.field.1=dc.contributor.author
## demo: use LC plugin for author
#choices.plugin.dc.contributor.author = LCNameAuthority
#choices.presentation.dc.contributor.author = lookup
#authority.controlled.dc.contributor.author = true
##
## This sets the lowest confidence level at which a metadata value is included
## in an authority-controlled browse (and search) index. It is a symbolic
@@ -1535,15 +1525,6 @@ authority.minconfidence = ambiguous
#vocabulary.plugin.srsc.hierarchy.suggest = true
#vocabulary.plugin.srsc.delimiter = "::"
## Demo: publisher name lookup through SHERPA/RoMEO:
#choices.plugin.dc.publisher = SRPublisher
#choices.presentation.dc.publisher = suggest
## demo: journal title lookup, with ISSN as authority
#choices.plugin.dc.title.alternative = SRJournalTitle
#choices.presentation.dc.title.alternative = suggest
#authority.controlled.dc.title.alternative = true
# Change number of choices shown in the select in Choices lookup popup
#xmlui.lookup.select.size = 12

View File

@@ -17,6 +17,7 @@
<bean id="authenticateServiceFactory" class="org.dspace.authenticate.factory.AuthenticateServiceFactoryImpl"/>
<bean id="authorityServiceFactory" class="org.dspace.authority.factory.AuthorityServiceFactoryImpl"/>
<bean id="externalServiceFactory" class="org.dspace.external.factory.ExternalServiceFactoryImpl"/>
<bean id="contentAuthorityServiceFactory" class="org.dspace.content.authority.factory.ContentAuthorityServiceFactoryImpl"/>
<bean id="authorizeServiceFactory" class="org.dspace.authorize.factory.AuthorizeServiceFactoryImpl"/>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean class="org.dspace.external.service.impl.ExternalDataServiceImpl"/>
<bean class="org.dspace.external.provider.impl.SherpaJournalDataProvider" init-method="init">
<property name="sourceIdentifier" value="sherpaJournal"/>
<property name="url" value="${sherpa.romeo.url}"/>
<!-- please register for a free api access key to get many benefits -->
<property name="apiKey" value="${sherpa.romeo.apikey}"/>
</bean>
<bean class="org.dspace.external.provider.impl.SherpaPublisherDataProvider">
<property name="sourceIdentifier" value="sherpaPublisher"/>
<property name="url" value="${sherpa.romeo.url}"/>
<!-- please register for a free api access key to get many benefits -->
<property name="apiKey" value="${sherpa.romeo.apikey}"/>
</bean>
<bean class="org.dspace.external.provider.impl.OrcidV2AuthorDataProvider" init-method="init">
<constructor-arg value="${orcid.api.url}"/>
<property name="sourceIdentifier" value="orcidV2"/>
<property name="orcidUrl" value="${orcid.url}" />
</bean>
<bean class="org.dspace.external.provider.impl.LCNameDataProvider">
<property name="url" value="${lcname.url}"/>
<property name="sourceIdentifier" value="lcname"/>
</bean>
</beans>

View File

@@ -18,7 +18,6 @@
<bean name="AuthorityTypes" class="org.dspace.authority.AuthorityTypes">
<property name="types">
<list>
<bean class="org.dspace.authority.orcid.Orcidv2AuthorityValue"/>
<bean class="org.dspace.authority.PersonAuthorityValue"/>
</list>
</property>
@@ -31,10 +30,4 @@
</property>
</bean>
<alias name="OrcidSource" alias="AuthoritySource"/>
<bean name="OrcidSource" class="org.dspace.authority.orcid.Orcidv2" init-method="init">
<constructor-arg value="${orcid.api.url}"/>
</bean>
</beans>