mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-16 06:23:10 +00:00
Merge pull request #8184 from 4Science/CST-5249
Add a correction service to dspace to enhance the data quality (was Enrich local data via the OpenAIRE Graph)
This commit is contained in:
@@ -1021,6 +1021,61 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection retrieveCollectionWithSubmitByEntityType(Context context, Item item,
|
||||
String entityType) throws SQLException {
|
||||
Collection ownCollection = item.getOwningCollection();
|
||||
return retrieveWithSubmitCollectionByEntityType(context, ownCollection.getCommunities(), entityType);
|
||||
}
|
||||
|
||||
private Collection retrieveWithSubmitCollectionByEntityType(Context context, List<Community> communities,
|
||||
String entityType) {
|
||||
|
||||
for (Community community : communities) {
|
||||
Collection collection = retrieveCollectionWithSubmitByCommunityAndEntityType(context, community,
|
||||
entityType);
|
||||
if (collection != null) {
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
for (Community community : communities) {
|
||||
List<Community> parentCommunities = community.getParentCommunities();
|
||||
Collection collection = retrieveWithSubmitCollectionByEntityType(context, parentCommunities, entityType);
|
||||
if (collection != null) {
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
return retrieveCollectionWithSubmitByCommunityAndEntityType(context, null, entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context context, Community community,
|
||||
String entityType) {
|
||||
context.turnOffAuthorisationSystem();
|
||||
List<Collection> collections;
|
||||
try {
|
||||
collections = findCollectionsWithSubmit(null, context, community, entityType, 0, 1);
|
||||
} catch (SQLException | SearchServiceException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
context.restoreAuthSystemState();
|
||||
if (collections != null && collections.size() > 0) {
|
||||
return collections.get(0);
|
||||
}
|
||||
if (community != null) {
|
||||
for (Community subCommunity : community.getSubcommunities()) {
|
||||
Collection collection = retrieveCollectionWithSubmitByCommunityAndEntityType(context,
|
||||
subCommunity, entityType);
|
||||
if (collection != null) {
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Collection> findCollectionsWithSubmit(String q, Context context, Community community, String entityType,
|
||||
int offset, int limit) throws SQLException, SearchServiceException {
|
||||
|
@@ -77,6 +77,7 @@ import org.dspace.orcid.service.OrcidQueueService;
|
||||
import org.dspace.orcid.service.OrcidSynchronizationService;
|
||||
import org.dspace.orcid.service.OrcidTokenService;
|
||||
import org.dspace.profile.service.ResearcherProfileService;
|
||||
import org.dspace.qaevent.dao.QAEventsDAO;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.versioning.service.VersioningService;
|
||||
import org.dspace.workflow.WorkflowItemService;
|
||||
@@ -170,6 +171,9 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
@Autowired(required = true)
|
||||
protected SubscribeService subscribeService;
|
||||
|
||||
@Autowired
|
||||
private QAEventsDAO qaEventsDao;
|
||||
|
||||
protected ItemServiceImpl() {
|
||||
super();
|
||||
}
|
||||
@@ -819,6 +823,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
orcidToken.setProfileItem(null);
|
||||
}
|
||||
|
||||
List<QAEventProcessed> qaEvents = qaEventsDao.findByItem(context, item);
|
||||
for (QAEventProcessed qaEvent : qaEvents) {
|
||||
qaEventsDao.delete(context, qaEvent);
|
||||
}
|
||||
|
||||
//Only clear collections after we have removed everything else from the item
|
||||
item.clearCollections();
|
||||
item.setOwningCollection(null);
|
||||
|
213
dspace-api/src/main/java/org/dspace/content/QAEvent.java
Normal file
213
dspace-api/src/main/java/org/dspace/content/QAEvent.java
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
|
||||
import org.dspace.qaevent.service.dto.QAMessageDTO;
|
||||
import org.dspace.util.RawJsonDeserializer;
|
||||
|
||||
/**
|
||||
* This class represent the Quality Assurance broker data as loaded in our solr
|
||||
* qaevent core
|
||||
*
|
||||
*/
|
||||
public class QAEvent {
|
||||
public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
|
||||
'f' };
|
||||
public static final String ACCEPTED = "accepted";
|
||||
public static final String REJECTED = "rejected";
|
||||
public static final String DISCARDED = "discarded";
|
||||
|
||||
public static final String OPENAIRE_SOURCE = "openaire";
|
||||
|
||||
private String source;
|
||||
|
||||
private String eventId;
|
||||
/**
|
||||
* contains the targeted dspace object,
|
||||
* ie: oai:www.openstarts.units.it:123456789/1120 contains the handle
|
||||
* of the DSpace pbject in its final part 123456789/1120
|
||||
* */
|
||||
private String originalId;
|
||||
|
||||
/**
|
||||
* evaluated with the targeted dspace object id
|
||||
*
|
||||
* */
|
||||
private String target;
|
||||
|
||||
private String related;
|
||||
|
||||
private String title;
|
||||
|
||||
private String topic;
|
||||
|
||||
private double trust;
|
||||
|
||||
@JsonDeserialize(using = RawJsonDeserializer.class)
|
||||
private String message;
|
||||
|
||||
private Date lastUpdate;
|
||||
|
||||
private String status = "PENDING";
|
||||
|
||||
public QAEvent() {
|
||||
}
|
||||
|
||||
public QAEvent(String source, String originalId, String target, String title,
|
||||
String topic, double trust, String message, Date lastUpdate) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.originalId = originalId;
|
||||
this.target = target;
|
||||
this.title = title;
|
||||
this.topic = topic;
|
||||
this.trust = trust;
|
||||
this.message = message;
|
||||
this.lastUpdate = lastUpdate;
|
||||
try {
|
||||
computedEventId();
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getOriginalId() {
|
||||
return originalId;
|
||||
}
|
||||
|
||||
public void setOriginalId(String originalId) {
|
||||
this.originalId = originalId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
public void setTopic(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
public double getTrust() {
|
||||
return trust;
|
||||
}
|
||||
|
||||
public void setTrust(double trust) {
|
||||
this.trust = trust;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getEventId() {
|
||||
if (eventId == null) {
|
||||
try {
|
||||
computedEventId();
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return eventId;
|
||||
}
|
||||
|
||||
public void setEventId(String eventId) {
|
||||
this.eventId = eventId;
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return lastUpdate;
|
||||
}
|
||||
|
||||
public void setLastUpdate(Date lastUpdate) {
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
public void setRelated(String related) {
|
||||
this.related = related;
|
||||
}
|
||||
|
||||
public String getRelated() {
|
||||
return related;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source != null ? source : OPENAIRE_SOURCE;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/*
|
||||
* DTO constructed via Jackson use empty constructor. In this case, the eventId
|
||||
* must be compute on the get method. This method create a signature based on
|
||||
* the event fields and store it in the eventid attribute.
|
||||
*/
|
||||
private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
MessageDigest digester = MessageDigest.getInstance("MD5");
|
||||
String dataToString = "source=" + source + ",originalId=" + originalId + ", title=" + title + ", topic="
|
||||
+ topic + ", trust=" + trust + ", message=" + message;
|
||||
digester.update(dataToString.getBytes("UTF-8"));
|
||||
byte[] signature = digester.digest();
|
||||
char[] arr = new char[signature.length << 1];
|
||||
for (int i = 0; i < signature.length; i++) {
|
||||
int b = signature[i];
|
||||
int idx = i << 1;
|
||||
arr[idx] = HEX_DIGITS[(b >> 4) & 0xf];
|
||||
arr[idx + 1] = HEX_DIGITS[b & 0xf];
|
||||
}
|
||||
eventId = new String(arr);
|
||||
|
||||
}
|
||||
|
||||
public Class<? extends QAMessageDTO> getMessageDtoClass() {
|
||||
switch (getSource()) {
|
||||
case OPENAIRE_SOURCE:
|
||||
return OpenaireMessageDTO.class;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown event's source: " + getSource());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.content;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.dspace.eperson.EPerson;
|
||||
|
||||
/**
|
||||
* This class represent the stored information about processed notification
|
||||
* broker events
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "qaevent_processed")
|
||||
public class QAEventProcessed implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3427340199132007814L;
|
||||
|
||||
@Id
|
||||
@Column(name = "qaevent_id")
|
||||
private String eventId;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "qaevent_timestamp")
|
||||
private Date eventTimestamp;
|
||||
|
||||
@JoinColumn(name = "eperson_uuid")
|
||||
@ManyToOne
|
||||
private EPerson eperson;
|
||||
|
||||
@JoinColumn(name = "item_uuid")
|
||||
@ManyToOne
|
||||
private Item item;
|
||||
|
||||
public String getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
public void setEventId(String eventId) {
|
||||
this.eventId = eventId;
|
||||
}
|
||||
|
||||
public Date getEventTimestamp() {
|
||||
return eventTimestamp;
|
||||
}
|
||||
|
||||
public void setEventTimestamp(Date eventTimestamp) {
|
||||
this.eventTimestamp = eventTimestamp;
|
||||
}
|
||||
|
||||
public EPerson getEperson() {
|
||||
return eperson;
|
||||
}
|
||||
|
||||
public void setEperson(EPerson eperson) {
|
||||
this.eperson = eperson;
|
||||
}
|
||||
|
||||
public Item getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public void setItem(Item item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
}
|
@@ -417,6 +417,34 @@ public interface CollectionService
|
||||
public List<Collection> findCollectionsWithSubmit(String q, Context context, Community community,
|
||||
int offset, int limit) throws SQLException, SearchServiceException;
|
||||
|
||||
/**
|
||||
* Retrieve the first collection in the community or its descending that support
|
||||
* the provided entityType
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param community the root from where the search start
|
||||
* @param entityType the requested entity type
|
||||
* @return the first collection in the community or its descending
|
||||
* that support the provided entityType
|
||||
*/
|
||||
public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context context, Community community,
|
||||
String entityType);
|
||||
|
||||
/**
|
||||
* Retrieve the close collection to the item for which the current user has
|
||||
* 'submit' privileges that support the provided entityType. Close mean the
|
||||
* collection that can be reach with the minimum steps starting from the item
|
||||
* (owningCollection, brothers collections, etc)
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item from where the search start
|
||||
* @param entityType the requested entity type
|
||||
* @return the first collection in the community or its descending
|
||||
* that support the provided entityType
|
||||
*/
|
||||
public Collection retrieveCollectionWithSubmitByEntityType(Context context, Item item, String entityType)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Counts the number of Collection for which the current user has 'submit' privileges.
|
||||
* NOTE: for better performance, this method retrieves its results from an index (cache)
|
||||
|
@@ -33,6 +33,7 @@ import org.dspace.content.DSpaceObjectServiceImpl;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.QAEventProcessed;
|
||||
import org.dspace.content.WorkspaceItem;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.ItemService;
|
||||
@@ -47,6 +48,7 @@ import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.eperson.service.SubscribeService;
|
||||
import org.dspace.event.Event;
|
||||
import org.dspace.orcid.service.OrcidTokenService;
|
||||
import org.dspace.qaevent.dao.QAEventsDAO;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.util.UUIDUtils;
|
||||
import org.dspace.versioning.Version;
|
||||
@@ -106,6 +108,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
protected ConfigurationService configurationService;
|
||||
@Autowired
|
||||
protected OrcidTokenService orcidTokenService;
|
||||
@Autowired
|
||||
protected QAEventsDAO qaEventsDao;
|
||||
|
||||
protected EPersonServiceImpl() {
|
||||
super();
|
||||
@@ -487,6 +491,11 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
// Remove any subscriptions
|
||||
subscribeService.deleteByEPerson(context, ePerson);
|
||||
|
||||
List<QAEventProcessed> qaEvents = qaEventsDao.findByEPerson(context, ePerson);
|
||||
for (QAEventProcessed qaEvent : qaEvents) {
|
||||
qaEventsDao.delete(context, qaEvent);
|
||||
}
|
||||
|
||||
// Remove ourself
|
||||
ePersonDAO.delete(context, ePerson);
|
||||
|
||||
|
@@ -40,20 +40,20 @@ import org.json.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* based on OrcidRestConnector it's a rest connector for OpenAIRE API providing
|
||||
* based on OrcidRestConnector it's a rest connector for Openaire API providing
|
||||
* ways to perform searches and token grabbing
|
||||
*
|
||||
* @author paulo-graca
|
||||
*
|
||||
*/
|
||||
public class OpenAIRERestConnector {
|
||||
public class OpenaireRestConnector {
|
||||
/**
|
||||
* log4j logger
|
||||
*/
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIRERestConnector.class);
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenaireRestConnector.class);
|
||||
|
||||
/**
|
||||
* OpenAIRE API Url
|
||||
* Openaire API Url
|
||||
* and can be configured with: openaire.api.url
|
||||
*/
|
||||
private String url = "https://api.openaire.eu";
|
||||
@@ -65,30 +65,30 @@ public class OpenAIRERestConnector {
|
||||
boolean tokenEnabled = false;
|
||||
|
||||
/**
|
||||
* OpenAIRE Authorization and Authentication Token Service URL
|
||||
* Openaire Authorization and Authentication Token Service URL
|
||||
* and can be configured with: openaire.token.url
|
||||
*/
|
||||
private String tokenServiceUrl;
|
||||
|
||||
/**
|
||||
* OpenAIRE clientId
|
||||
* Openaire clientId
|
||||
* and can be configured with: openaire.token.clientId
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* OpenAIRERest access token
|
||||
* OpenaireRest access token
|
||||
*/
|
||||
private OpenAIRERestToken accessToken;
|
||||
private OpenaireRestToken accessToken;
|
||||
|
||||
/**
|
||||
* OpenAIRE clientSecret
|
||||
* Openaire clientSecret
|
||||
* and can be configured with: openaire.token.clientSecret
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
|
||||
public OpenAIRERestConnector(String url) {
|
||||
public OpenaireRestConnector(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class OpenAIRERestConnector {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public OpenAIRERestToken grabNewAccessToken() throws IOException {
|
||||
public OpenaireRestToken grabNewAccessToken() throws IOException {
|
||||
|
||||
if (StringUtils.isBlank(tokenServiceUrl) || StringUtils.isBlank(clientId)
|
||||
|| StringUtils.isBlank(clientSecret)) {
|
||||
@@ -145,13 +145,13 @@ public class OpenAIRERestConnector {
|
||||
throw new IOException("Unable to grab the access token using provided service url, client id and secret");
|
||||
}
|
||||
|
||||
return new OpenAIRERestToken(responseObject.get("access_token").toString(),
|
||||
return new OpenaireRestToken(responseObject.get("access_token").toString(),
|
||||
Long.valueOf(responseObject.get("expires_in").toString()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a GET request to the OpenAIRE API
|
||||
* Perform a GET request to the Openaire API
|
||||
*
|
||||
* @param file
|
||||
* @param accessToken
|
||||
@@ -218,12 +218,12 @@ public class OpenAIRERestConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an OpenAIRE Project Search By Keywords
|
||||
* Perform an Openaire Project Search By Keywords
|
||||
*
|
||||
* @param page
|
||||
* @param size
|
||||
* @param keywords
|
||||
* @return OpenAIRE Response
|
||||
* @return Openaire Response
|
||||
*/
|
||||
public Response searchProjectByKeywords(int page, int size, String... keywords) {
|
||||
String path = "search/projects?keywords=" + String.join("+", keywords);
|
||||
@@ -231,13 +231,13 @@ public class OpenAIRERestConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an OpenAIRE Project Search By ID and by Funder
|
||||
* Perform an Openaire Project Search By ID and by Funder
|
||||
*
|
||||
* @param projectID
|
||||
* @param projectFunder
|
||||
* @param page
|
||||
* @param size
|
||||
* @return OpenAIRE Response
|
||||
* @return Openaire Response
|
||||
*/
|
||||
public Response searchProjectByIDAndFunder(String projectID, String projectFunder, int page, int size) {
|
||||
String path = "search/projects?grantID=" + projectID + "&funder=" + projectFunder;
|
||||
@@ -245,12 +245,12 @@ public class OpenAIRERestConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an OpenAIRE Search request
|
||||
* Perform an Openaire Search request
|
||||
*
|
||||
* @param path
|
||||
* @param page
|
||||
* @param size
|
||||
* @return OpenAIRE Response
|
||||
* @return Openaire Response
|
||||
*/
|
||||
public Response search(String path, int page, int size) {
|
||||
String[] queryStringPagination = { "page=" + page, "size=" + size };
|
@@ -8,13 +8,13 @@
|
||||
package org.dspace.external;
|
||||
|
||||
/**
|
||||
* OpenAIRE rest API token to be used when grabbing an accessToken.<br/>
|
||||
* Openaire rest API token to be used when grabbing an accessToken.<br/>
|
||||
* Based on https://develop.openaire.eu/basic.html
|
||||
*
|
||||
* @author paulo-graca
|
||||
*
|
||||
*/
|
||||
public class OpenAIRERestToken {
|
||||
public class OpenaireRestToken {
|
||||
|
||||
/**
|
||||
* Stored access token
|
||||
@@ -32,7 +32,7 @@ public class OpenAIRERestToken {
|
||||
* @param accessToken
|
||||
* @param expiresIn
|
||||
*/
|
||||
public OpenAIRERestToken(String accessToken, Long expiresIn) {
|
||||
public OpenaireRestToken(String accessToken, Long expiresIn) {
|
||||
this.accessToken = accessToken;
|
||||
this.setExpirationDate(expiresIn);
|
||||
}
|
@@ -31,7 +31,7 @@ import eu.openaire.oaf.model.base.Project;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.content.dto.MetadataValueDTO;
|
||||
import org.dspace.external.OpenAIRERestConnector;
|
||||
import org.dspace.external.OpenaireRestConnector;
|
||||
import org.dspace.external.model.ExternalDataObject;
|
||||
import org.dspace.external.provider.AbstractExternalDataProvider;
|
||||
import org.dspace.importer.external.metadatamapping.MetadataFieldConfig;
|
||||
@@ -39,13 +39,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* This class is the implementation of the ExternalDataProvider interface that
|
||||
* will deal with the OpenAIRE External Data lookup
|
||||
* will deal with the Openaire External Data lookup
|
||||
*
|
||||
* @author paulo-graca
|
||||
*/
|
||||
public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
public class OpenaireFundingDataProvider extends AbstractExternalDataProvider {
|
||||
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIREFundingDataProvider.class);
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenaireFundingDataProvider.class);
|
||||
|
||||
/**
|
||||
* GrantAgreement prefix
|
||||
@@ -75,7 +75,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
/**
|
||||
* Connector to handle token and requests
|
||||
*/
|
||||
protected OpenAIRERestConnector connector;
|
||||
protected OpenaireRestConnector connector;
|
||||
|
||||
protected Map<String, MetadataFieldConfig> metadataFields;
|
||||
|
||||
@@ -93,7 +93,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
// characters that must be escaped for the <:entry-id>
|
||||
String decodedId = new String(Base64.getDecoder().decode(id));
|
||||
if (!isValidProjectURI(decodedId)) {
|
||||
log.error("Invalid ID for OpenAIREFunding - " + id);
|
||||
log.error("Invalid ID for OpenaireFunding - " + id);
|
||||
return Optional.empty();
|
||||
}
|
||||
Response response = searchByProjectURI(decodedId);
|
||||
@@ -101,7 +101,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
try {
|
||||
if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) {
|
||||
Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject();
|
||||
ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider
|
||||
ExternalDataObject externalDataObject = new OpenaireFundingDataProvider
|
||||
.ExternalDataObjectBuilder(project)
|
||||
.setId(generateProjectURI(project))
|
||||
.setSource(sourceIdentifier)
|
||||
@@ -123,7 +123,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
limit = LIMIT_DEFAULT;
|
||||
}
|
||||
|
||||
// OpenAIRE uses pages and first page starts with 1
|
||||
// Openaire uses pages and first page starts with 1
|
||||
int page = (start / limit) + 1;
|
||||
|
||||
// escaping query
|
||||
@@ -148,7 +148,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
|
||||
if (projects.size() > 0) {
|
||||
return projects.stream()
|
||||
.map(project -> new OpenAIREFundingDataProvider
|
||||
.map(project -> new OpenaireFundingDataProvider
|
||||
.ExternalDataObjectBuilder(project)
|
||||
.setId(generateProjectURI(project))
|
||||
.setSource(sourceIdentifier)
|
||||
@@ -176,24 +176,24 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
* Generic setter for the sourceIdentifier
|
||||
*
|
||||
* @param sourceIdentifier The sourceIdentifier to be set on this
|
||||
* OpenAIREFunderDataProvider
|
||||
* OpenaireFunderDataProvider
|
||||
*/
|
||||
@Autowired(required = true)
|
||||
public void setSourceIdentifier(String sourceIdentifier) {
|
||||
this.sourceIdentifier = sourceIdentifier;
|
||||
}
|
||||
|
||||
public OpenAIRERestConnector getConnector() {
|
||||
public OpenaireRestConnector getConnector() {
|
||||
return connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for OpenAIRERestConnector
|
||||
* Generic setter for OpenaireRestConnector
|
||||
*
|
||||
* @param connector
|
||||
*/
|
||||
@Autowired(required = true)
|
||||
public void setConnector(OpenAIRERestConnector connector) {
|
||||
public void setConnector(OpenaireRestConnector connector) {
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an URI based on OpenAIRE 3.0 guidelines
|
||||
* This method returns an URI based on Openaire 3.0 guidelines
|
||||
* https://guidelines.openaire.eu/en/latest/literature/field_projectid.html that
|
||||
* can be used as an ID if is there any missing part, that part it will be
|
||||
* replaced by the character '+'
|
||||
@@ -281,7 +281,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenAIRE Funding External Data Builder Class
|
||||
* Openaire Funding External Data Builder Class
|
||||
*
|
||||
* @author pgraca
|
||||
*/
|
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.qaevent;
|
||||
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.event.Consumer;
|
||||
import org.dspace.event.Event;
|
||||
import org.dspace.qaevent.service.QAEventService;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Consumer to delete qaevents from solr due to the target item deletion
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAEventsDeleteCascadeConsumer implements Consumer {
|
||||
|
||||
private QAEventService qaEventService;
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
qaEventService = new DSpace().getSingletonService(QAEventService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(Context context) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Context context, Event event) throws Exception {
|
||||
if (event.getEventType() == Event.DELETE) {
|
||||
if (event.getSubjectType() == Constants.ITEM && event.getSubjectID() != null) {
|
||||
qaEventService.deleteEventsByTargetId(event.getSubjectID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void end(Context context) throws Exception {
|
||||
}
|
||||
|
||||
}
|
46
dspace-api/src/main/java/org/dspace/qaevent/QASource.java
Normal file
46
dspace-api/src/main/java/org/dspace/qaevent/QASource.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.qaevent;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This model class represent the source/provider of the QA events (as Openaire).
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4Science)
|
||||
*
|
||||
*/
|
||||
public class QASource {
|
||||
private String name;
|
||||
private long totalEvents;
|
||||
private Date lastEvent;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getTotalEvents() {
|
||||
return totalEvents;
|
||||
}
|
||||
|
||||
public void setTotalEvents(long totalEvents) {
|
||||
this.totalEvents = totalEvents;
|
||||
}
|
||||
|
||||
public Date getLastEvent() {
|
||||
return lastEvent;
|
||||
}
|
||||
|
||||
public void setLastEvent(Date lastEvent) {
|
||||
this.lastEvent = lastEvent;
|
||||
}
|
||||
}
|
47
dspace-api/src/main/java/org/dspace/qaevent/QATopic.java
Normal file
47
dspace-api/src/main/java/org/dspace/qaevent/QATopic.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.qaevent;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This model class represent the quality assurance broker topic concept. A
|
||||
* topic represents a type of event and is therefore used to group events.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QATopic {
|
||||
private String key;
|
||||
private long totalEvents;
|
||||
private Date lastEvent;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public long getTotalEvents() {
|
||||
return totalEvents;
|
||||
}
|
||||
|
||||
public void setTotalEvents(long totalEvents) {
|
||||
this.totalEvents = totalEvents;
|
||||
}
|
||||
|
||||
public Date getLastEvent() {
|
||||
return lastEvent;
|
||||
}
|
||||
|
||||
public void setLastEvent(Date lastEvent) {
|
||||
this.lastEvent = lastEvent;
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.qaevent;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.qaevent.service.dto.QAMessageDTO;
|
||||
|
||||
/**
|
||||
* Interface for classes that perform a correction on the given item.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public interface QualityAssuranceAction {
|
||||
|
||||
/**
|
||||
* Perform a correction on the given item.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to correct
|
||||
* @param relatedItem the related item, if any
|
||||
* @param message the message with the correction details
|
||||
*/
|
||||
public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message);
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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.qaevent.action;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.EntityType;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.RelationshipType;
|
||||
import org.dspace.content.WorkspaceItem;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
import org.dspace.content.service.EntityTypeService;
|
||||
import org.dspace.content.service.InstallItemService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.content.service.RelationshipService;
|
||||
import org.dspace.content.service.RelationshipTypeService;
|
||||
import org.dspace.content.service.WorkspaceItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.qaevent.QualityAssuranceAction;
|
||||
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
|
||||
import org.dspace.qaevent.service.dto.QAMessageDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QualityAssuranceAction} that handle the relationship between the
|
||||
* item to correct and a related item.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAEntityOpenaireMetadataAction implements QualityAssuranceAction {
|
||||
private String relation;
|
||||
private String entityType;
|
||||
private Map<String, String> entityMetadata;
|
||||
|
||||
@Autowired
|
||||
private InstallItemService installItemService;
|
||||
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
private EntityTypeService entityTypeService;
|
||||
|
||||
@Autowired
|
||||
private RelationshipService relationshipService;
|
||||
|
||||
@Autowired
|
||||
private RelationshipTypeService relationshipTypeService;
|
||||
|
||||
@Autowired
|
||||
private WorkspaceItemService workspaceItemService;
|
||||
|
||||
@Autowired
|
||||
private CollectionService collectionService;
|
||||
|
||||
public void setItemService(ItemService itemService) {
|
||||
this.itemService = itemService;
|
||||
}
|
||||
|
||||
public String getRelation() {
|
||||
return relation;
|
||||
}
|
||||
|
||||
public void setRelation(String relation) {
|
||||
this.relation = relation;
|
||||
}
|
||||
|
||||
public String[] splitMetadata(String metadata) {
|
||||
String[] result = new String[3];
|
||||
String[] split = metadata.split("\\.");
|
||||
result[0] = split[0];
|
||||
result[1] = split[1];
|
||||
if (split.length == 3) {
|
||||
result[2] = split[2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getEntityType() {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
public void setEntityType(String entityType) {
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
public Map<String, String> getEntityMetadata() {
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
public void setEntityMetadata(Map<String, String> entityMetadata) {
|
||||
this.entityMetadata = entityMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) {
|
||||
try {
|
||||
if (relatedItem != null) {
|
||||
link(context, item, relatedItem);
|
||||
} else {
|
||||
|
||||
Collection collection = collectionService.retrieveCollectionWithSubmitByEntityType(context,
|
||||
item, entityType);
|
||||
if (collection == null) {
|
||||
throw new IllegalStateException("No collection found by entity type: " + collection);
|
||||
}
|
||||
|
||||
WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, true);
|
||||
relatedItem = workspaceItem.getItem();
|
||||
|
||||
for (String key : entityMetadata.keySet()) {
|
||||
String value = getValue(message, key);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
String[] targetMetadata = splitMetadata(entityMetadata.get(key));
|
||||
itemService.addMetadata(context, relatedItem, targetMetadata[0], targetMetadata[1],
|
||||
targetMetadata[2], null, value);
|
||||
}
|
||||
}
|
||||
installItemService.installItem(context, workspaceItem);
|
||||
itemService.update(context, relatedItem);
|
||||
link(context, item, relatedItem);
|
||||
}
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new relationship between the two given item, based on the configured
|
||||
* relation.
|
||||
*/
|
||||
private void link(Context context, Item item, Item relatedItem) throws SQLException, AuthorizeException {
|
||||
EntityType project = entityTypeService.findByEntityType(context, entityType);
|
||||
RelationshipType relType = relationshipTypeService.findByEntityType(context, project).stream()
|
||||
.filter(r -> StringUtils.equals(r.getRightwardType(), relation)).findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No relationshipType named " + relation
|
||||
+ " was found for the entity type " + entityType
|
||||
+ ". A proper configuration is required to use the QAEntitiyMetadataAction."
|
||||
+ " If you don't manage funding in your repository please skip this topic in"
|
||||
+ " the qaevents.cfg"));
|
||||
// Create the relationship
|
||||
relationshipService.create(context, item, relatedItem, relType, -1, -1);
|
||||
}
|
||||
|
||||
private String getValue(QAMessageDTO message, String key) {
|
||||
if (!(message instanceof OpenaireMessageDTO)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenaireMessageDTO openaireMessage = (OpenaireMessageDTO) message;
|
||||
|
||||
if (StringUtils.equals(key, "acronym")) {
|
||||
return openaireMessage.getAcronym();
|
||||
} else if (StringUtils.equals(key, "code")) {
|
||||
return openaireMessage.getCode();
|
||||
} else if (StringUtils.equals(key, "funder")) {
|
||||
return openaireMessage.getFunder();
|
||||
} else if (StringUtils.equals(key, "fundingProgram")) {
|
||||
return openaireMessage.getFundingProgram();
|
||||
} else if (StringUtils.equals(key, "jurisdiction")) {
|
||||
return openaireMessage.getJurisdiction();
|
||||
} else if (StringUtils.equals(key, "openaireId")) {
|
||||
return openaireMessage.getOpenaireId();
|
||||
} else if (StringUtils.equals(key, "title")) {
|
||||
return openaireMessage.getTitle();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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.qaevent.action;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.qaevent.QualityAssuranceAction;
|
||||
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
|
||||
import org.dspace.qaevent.service.dto.QAMessageDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QualityAssuranceAction} that add a specific metadata on the given
|
||||
* item based on the OPENAIRE message type.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAOpenaireMetadataMapAction implements QualityAssuranceAction {
|
||||
public static final String DEFAULT = "default";
|
||||
|
||||
private Map<String, String> types;
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
public void setItemService(ItemService itemService) {
|
||||
this.itemService = itemService;
|
||||
}
|
||||
|
||||
public Map<String, String> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
public void setTypes(Map<String, String> types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the correction on one metadata field of the given item based on the
|
||||
* openaire message type.
|
||||
*/
|
||||
@Override
|
||||
public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) {
|
||||
|
||||
if (!(message instanceof OpenaireMessageDTO)) {
|
||||
throw new IllegalArgumentException("Unsupported message type: " + message.getClass());
|
||||
}
|
||||
|
||||
OpenaireMessageDTO openaireMessage = (OpenaireMessageDTO) message;
|
||||
|
||||
try {
|
||||
String targetMetadata = types.get(openaireMessage.getType());
|
||||
if (targetMetadata == null) {
|
||||
targetMetadata = types.get(DEFAULT);
|
||||
}
|
||||
String[] metadata = splitMetadata(targetMetadata);
|
||||
itemService.addMetadata(context, item, metadata[0], metadata[1], metadata[2], null,
|
||||
openaireMessage.getValue());
|
||||
itemService.update(context, item);
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String[] splitMetadata(String metadata) {
|
||||
String[] result = new String[3];
|
||||
String[] split = metadata.split("\\.");
|
||||
result[0] = split[0];
|
||||
result[1] = split[1];
|
||||
if (split.length == 3) {
|
||||
result[2] = split[2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.qaevent.action;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.qaevent.QualityAssuranceAction;
|
||||
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
|
||||
import org.dspace.qaevent.service.dto.QAMessageDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QualityAssuranceAction} that add a simple metadata to the given
|
||||
* item.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAOpenaireSimpleMetadataAction implements QualityAssuranceAction {
|
||||
private String metadata;
|
||||
private String metadataSchema;
|
||||
private String metadataElement;
|
||||
private String metadataQualifier;
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
public void setItemService(ItemService itemService) {
|
||||
this.itemService = itemService;
|
||||
}
|
||||
|
||||
public String getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(String metadata) {
|
||||
this.metadata = metadata;
|
||||
String[] split = metadata.split("\\.");
|
||||
this.metadataSchema = split[0];
|
||||
this.metadataElement = split[1];
|
||||
if (split.length == 3) {
|
||||
this.metadataQualifier = split[2];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) {
|
||||
try {
|
||||
itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null,
|
||||
((OpenaireMessageDTO) message).getAbstracts());
|
||||
itemService.update(context, item);
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.qaevent.dao;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.QAEventProcessed;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.GenericDAO;
|
||||
import org.dspace.eperson.EPerson;
|
||||
|
||||
/**
|
||||
* DAO that handle processed QA Events.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public interface QAEventsDAO extends GenericDAO<QAEventProcessed> {
|
||||
|
||||
/**
|
||||
* Returns all the stored QAEventProcessed entities.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @return the found entities
|
||||
* @throws SQLException if an SQL error occurs
|
||||
*/
|
||||
public List<QAEventProcessed> findAll(Context context) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the stored QAEventProcessed entities by item.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to search for
|
||||
* @return the found entities
|
||||
* @throws SQLException if an SQL error occurs
|
||||
*/
|
||||
public List<QAEventProcessed> findByItem(Context context, Item item) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the stored QAEventProcessed entities by eperson.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param ePerson the ePerson to search for
|
||||
* @return the found entities
|
||||
* @throws SQLException if an SQL error occurs
|
||||
*/
|
||||
public List<QAEventProcessed> findByEPerson(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Search a page of quality assurance broker events by notification ID.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param eventId the event id
|
||||
* @param start the start index
|
||||
* @param size the size to be applied
|
||||
* @return the processed events
|
||||
* @throws SQLException if an SQL error occurs
|
||||
*/
|
||||
public List<QAEventProcessed> searchByEventId(Context context, String eventId, Integer start, Integer size)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Check if an event with the given checksum is already stored.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param checksum the checksum to search for
|
||||
* @return true if the given checksum is related to an already
|
||||
* stored event, false otherwise
|
||||
* @throws SQLException if an SQL error occurs
|
||||
*/
|
||||
public boolean isEventStored(Context context, String checksum) throws SQLException;
|
||||
|
||||
/**
|
||||
* Store an event related to the given checksum.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param checksum the checksum of the event to be store
|
||||
* @param eperson the eperson who handle the event
|
||||
* @param item the item related to the event
|
||||
* @return true if the creation is completed with success, false
|
||||
* otherwise
|
||||
*/
|
||||
boolean storeEvent(Context context, String checksum, EPerson eperson, Item item);
|
||||
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.qaevent.dao.impl;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.QAEventProcessed;
|
||||
import org.dspace.core.AbstractHibernateDAO;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.qaevent.dao.QAEventsDAO;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QAEventsDAO} that store processed events using an
|
||||
* SQL DBMS.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAEventsDAOImpl extends AbstractHibernateDAO<QAEventProcessed> implements QAEventsDAO {
|
||||
|
||||
@Override
|
||||
public List<QAEventProcessed> findAll(Context context) throws SQLException {
|
||||
return findAll(context, QAEventProcessed.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean storeEvent(Context context, String checksum, EPerson eperson, Item item) {
|
||||
QAEventProcessed qaEvent = new QAEventProcessed();
|
||||
qaEvent.setEperson(eperson);
|
||||
qaEvent.setEventId(checksum);
|
||||
qaEvent.setItem(item);
|
||||
qaEvent.setEventTimestamp(new Date());
|
||||
try {
|
||||
create(context, qaEvent);
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEventStored(Context context, String checksum) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT count(eventId) FROM QAEventProcessed qaevent WHERE qaevent.eventId = :event_id ");
|
||||
query.setParameter("event_id", checksum);
|
||||
return count(query) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QAEventProcessed> searchByEventId(Context context, String eventId, Integer start, Integer size)
|
||||
throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT * FROM QAEventProcessed qaevent WHERE qaevent.qaevent_id = :event_id ");
|
||||
query.setFirstResult(start);
|
||||
query.setMaxResults(size);
|
||||
query.setParameter("event_id", eventId);
|
||||
return findMany(context, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QAEventProcessed> findByItem(Context context, Item item) throws SQLException {
|
||||
Query query = createQuery(context, ""
|
||||
+ " SELECT qaevent "
|
||||
+ " FROM QAEventProcessed qaevent "
|
||||
+ " WHERE qaevent.item = :item ");
|
||||
query.setParameter("item", item);
|
||||
return findMany(context, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QAEventProcessed> findByEPerson(Context context, EPerson ePerson) throws SQLException {
|
||||
Query query = createQuery(context, ""
|
||||
+ " SELECT qaevent "
|
||||
+ " FROM QAEventProcessed qaevent "
|
||||
+ " WHERE qaevent.eperson = :eperson ");
|
||||
query.setParameter("eperson", ePerson);
|
||||
return findMany(context, query);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* 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.qaevent.script;
|
||||
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.substringAfter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import eu.dnetlib.broker.BrokerClient;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.dspace.content.QAEvent;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.qaevent.service.OpenaireClientFactory;
|
||||
import org.dspace.qaevent.service.QAEventService;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Implementation of {@link DSpaceRunnable} to perfom a QAEvents import from a
|
||||
* json file. The JSON file contains an array of JSON Events, where each event
|
||||
* has the following structure. The message attribute follows the structure
|
||||
* documented at
|
||||
* @see <a href="https://graph.openaire.eu/docs/category/entities" target="_blank"> see </a>
|
||||
*
|
||||
* <code> <br/>
|
||||
* { <br/>
|
||||
* "originalId": "oai:www.openstarts.units.it:10077/21838",<br/>
|
||||
* "title": "Egypt, crossroad of translations and literary interweavings", <br/>
|
||||
* "topic": "ENRICH/MORE/PROJECT", <br/>
|
||||
* "trust": 1.0, <br/>
|
||||
* "message": { <br/>
|
||||
* "projects[0].acronym": "PAThs", <br/>
|
||||
* "projects[0].code": "687567", <br/>
|
||||
* "projects[0].funder": "EC",<br/>
|
||||
* "projects[0].fundingProgram": "H2020", <br/>
|
||||
* "projects[0].jurisdiction": "EU",<br/>
|
||||
* "projects[0].openaireId": "40|corda__h2020::6e32f5eb912688f2424c68b851483ea4", <br/>
|
||||
* "projects[0].title": "Tracking Papyrus and Parchment Paths" <br/>
|
||||
* } <br/>
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4Science.it)
|
||||
*
|
||||
*/
|
||||
public class OpenaireEventsImport
|
||||
extends DSpaceRunnable<OpenaireEventsImportScriptConfiguration<OpenaireEventsImport>> {
|
||||
|
||||
private QAEventService qaEventService;
|
||||
|
||||
private String[] topicsToImport;
|
||||
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
private BrokerClient brokerClient;
|
||||
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
private URL openaireBrokerURL;
|
||||
|
||||
private String fileLocation;
|
||||
|
||||
private String email;
|
||||
|
||||
private Context context;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public OpenaireEventsImportScriptConfiguration getScriptConfiguration() {
|
||||
OpenaireEventsImportScriptConfiguration configuration = new DSpace().getServiceManager()
|
||||
.getServiceByName("import-openaire-events", OpenaireEventsImportScriptConfiguration.class);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
|
||||
jsonMapper = new JsonMapper();
|
||||
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
qaEventService = new DSpace().getSingletonService(QAEventService.class);
|
||||
configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
brokerClient = OpenaireClientFactory.getInstance().getBrokerClient();
|
||||
|
||||
topicsToImport = configurationService.getArrayProperty("qaevents.openaire.import.topic");
|
||||
openaireBrokerURL = getOpenaireBrokerUri();
|
||||
|
||||
fileLocation = commandLine.getOptionValue("f");
|
||||
email = commandLine.getOptionValue("e");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
|
||||
if (StringUtils.isAllBlank(fileLocation, email)) {
|
||||
throw new IllegalArgumentException("One parameter between the location of the file and the email "
|
||||
+ "must be entered to proceed with the import.");
|
||||
}
|
||||
|
||||
if (StringUtils.isNoneBlank(fileLocation, email)) {
|
||||
throw new IllegalArgumentException("Only one parameter between the location of the file and the email "
|
||||
+ "must be entered to proceed with the import.");
|
||||
}
|
||||
|
||||
context = new Context();
|
||||
assignCurrentUserInContext();
|
||||
|
||||
try {
|
||||
importOpenaireEvents();
|
||||
} catch (Exception ex) {
|
||||
handler.logError("A not recoverable error occurs during OPENAIRE events import: " + getMessage(ex), ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the OPENAIRE events from the given JSON file or directly from the
|
||||
* OPENAIRE broker and try to store them.
|
||||
*/
|
||||
private void importOpenaireEvents() throws Exception {
|
||||
|
||||
if (StringUtils.isNotBlank(fileLocation)) {
|
||||
handler.logInfo("Trying to read the QA events from the provided file");
|
||||
importOpenaireEventsFromFile();
|
||||
} else {
|
||||
handler.logInfo("Trying to read the QA events from the OPENAIRE broker");
|
||||
importOpenaireEventsFromBroker();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the OPENAIRE events from the given file location and try to store them.
|
||||
*/
|
||||
private void importOpenaireEventsFromFile() throws Exception {
|
||||
|
||||
InputStream eventsFileInputStream = getQAEventsFileInputStream();
|
||||
List<QAEvent> qaEvents = readOpenaireQAEventsFromJson(eventsFileInputStream);
|
||||
|
||||
handler.logInfo("Found " + qaEvents.size() + " events in the given file");
|
||||
|
||||
storeOpenaireQAEvents(qaEvents);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the OPENAIRE events from the Broker using the subscription related to
|
||||
* the given email and try to store them.
|
||||
*/
|
||||
private void importOpenaireEventsFromBroker() {
|
||||
|
||||
List<String> subscriptionIds = listEmailSubscriptions();
|
||||
|
||||
handler.logInfo("Found " + subscriptionIds.size() + " subscriptions related to the given email");
|
||||
|
||||
for (String subscriptionId : subscriptionIds) {
|
||||
|
||||
List<QAEvent> events = readOpenaireQAEventsFromBroker(subscriptionId);
|
||||
|
||||
handler.logInfo("Found " + events.size() + " events from the subscription " + subscriptionId);
|
||||
|
||||
storeOpenaireQAEvents(events);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an InputStream from the runnable instance.
|
||||
*/
|
||||
private InputStream getQAEventsFileInputStream() throws Exception {
|
||||
return handler.getFileStream(context, fileLocation)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Error reading file, the file couldn't be "
|
||||
+ "found for filename: " + fileLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the QAEvent from the OPENAIRE Broker related to the subscription
|
||||
* with the given id.
|
||||
*/
|
||||
private List<QAEvent> readOpenaireQAEventsFromBroker(String subscriptionId) {
|
||||
|
||||
try {
|
||||
InputStream eventsInputStream = getEventsBySubscriptions(subscriptionId);
|
||||
return readOpenaireQAEventsFromJson(eventsInputStream);
|
||||
} catch (Exception ex) {
|
||||
handler.logError("An error occurs downloading the events related to the subscription "
|
||||
+ subscriptionId + ": " + getMessage(ex), ex);
|
||||
}
|
||||
|
||||
return List.of();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the QAEvent present in the given input stream.
|
||||
*
|
||||
* @return the QA events to be imported
|
||||
*/
|
||||
private List<QAEvent> readOpenaireQAEventsFromJson(InputStream inputStream) throws Exception {
|
||||
return jsonMapper.readValue(inputStream, new TypeReference<List<QAEvent>>() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given QAEvents.
|
||||
*
|
||||
* @param events the event to be stored
|
||||
*/
|
||||
private void storeOpenaireQAEvents(List<QAEvent> events) {
|
||||
for (QAEvent event : events) {
|
||||
try {
|
||||
storeOpenaireQAEvent(event);
|
||||
} catch (RuntimeException e) {
|
||||
handler.logWarning("An error occurs storing the event with id "
|
||||
+ event.getEventId() + ": " + getMessage(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given QAEvent, skipping it if it is not supported.
|
||||
*
|
||||
* @param event the event to be stored
|
||||
*/
|
||||
private void storeOpenaireQAEvent(QAEvent event) {
|
||||
|
||||
if (!StringUtils.equalsAny(event.getTopic(), topicsToImport)) {
|
||||
handler.logWarning("Event for topic " + event.getTopic() + " is not allowed in the qaevents.cfg");
|
||||
return;
|
||||
}
|
||||
|
||||
event.setSource(QAEvent.OPENAIRE_SOURCE);
|
||||
|
||||
qaEventService.store(context, event);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the events related to the given subscription from the OPENAIRE broker.
|
||||
*
|
||||
* @param subscriptionId the subscription id
|
||||
* @return an input stream from which to read the events in json format
|
||||
*/
|
||||
private InputStream getEventsBySubscriptions(String subscriptionId) throws Exception {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
brokerClient.downloadEvents(openaireBrokerURL, subscriptionId, outputStream);
|
||||
return new ByteArrayInputStream(outputStream.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all the subscription related to the given email from the OPENAIRE
|
||||
* broker.
|
||||
*/
|
||||
private List<String> listEmailSubscriptions() {
|
||||
try {
|
||||
return brokerClient.listSubscriptions(openaireBrokerURL, email);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("An error occurs retriving the subscriptions "
|
||||
+ "from the OPENAIRE broker: " + getMessage(ex), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private URL getOpenaireBrokerUri() {
|
||||
try {
|
||||
return new URL(configurationService.getProperty("qaevents.openaire.broker-url", "http://api.openaire.eu/broker"));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalStateException("The configured OPENAIRE broker URL is not valid.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root exception message from the given exception.
|
||||
*/
|
||||
private String getMessage(Exception ex) {
|
||||
String message = ExceptionUtils.getRootCauseMessage(ex);
|
||||
// Remove the Exception name from the message
|
||||
return isNotBlank(message) ? substringAfter(message, ":").trim() : "";
|
||||
}
|
||||
|
||||
private void assignCurrentUserInContext() throws SQLException {
|
||||
UUID uuid = getEpersonIdentifier();
|
||||
if (uuid != null) {
|
||||
EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid);
|
||||
context.setCurrentUser(ePerson);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.qaevent.script;
|
||||
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Extensions of {@link OpenaireEventsImport} to run the script on console.
|
||||
*
|
||||
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class OpenaireEventsImportCli extends OpenaireEventsImport {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public OpenaireEventsImportCliScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("import-openaire-events", OpenaireEventsImportCliScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
super.setup();
|
||||
|
||||
// in case of CLI we show the help prompt
|
||||
if (commandLine.hasOption('h')) {
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("Import Notification event json file", getScriptConfiguration().getOptions());
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.qaevent.script;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
/**
|
||||
* Extension of {@link OpenaireEventsImportScriptConfiguration} to run the script on
|
||||
* console.
|
||||
*
|
||||
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class OpenaireEventsImportCliScriptConfiguration<T extends OpenaireEventsImport>
|
||||
extends OpenaireEventsImportScriptConfiguration<T> {
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = super.getOptions();
|
||||
options.addOption("h", "help", false, "help");
|
||||
options.getOption("h").setType(boolean.class);
|
||||
super.options = options;
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.qaevent.script;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* Extension of {@link ScriptConfiguration} to perfom a QAEvents import from
|
||||
* file.
|
||||
*
|
||||
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class OpenaireEventsImportScriptConfiguration<T extends OpenaireEventsImport> extends ScriptConfiguration<T> {
|
||||
|
||||
/*
|
||||
private AuthorizeService authorizeService;
|
||||
*/
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this OpenaireEventsImportScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
/*
|
||||
@Override
|
||||
public boolean isAllowedToExecute(Context context) {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
if (options == null) {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("f", "file", true, "Import data from Openaire quality assurance broker JSON file."
|
||||
+ " This parameter is mutually exclusive to the email parameter.");
|
||||
options.getOption("f").setType(InputStream.class);
|
||||
options.getOption("f").setRequired(false);
|
||||
|
||||
options.addOption("e", "email", true, "Email related to the subscriptions to import data from Openaire "
|
||||
+ "broker. This parameter is mutually exclusive to the file parameter.");
|
||||
options.getOption("e").setType(String.class);
|
||||
options.getOption("e").setRequired(false);
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.qaevent.service;
|
||||
|
||||
import eu.dnetlib.broker.BrokerClient;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Factory for the {@link BrokerClient}.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public interface OpenaireClientFactory {
|
||||
|
||||
/**
|
||||
* Returns an instance of the {@link BrokerClient}.
|
||||
*
|
||||
* @return the client instance
|
||||
*/
|
||||
public BrokerClient getBrokerClient();
|
||||
|
||||
public static OpenaireClientFactory getInstance() {
|
||||
return new DSpace().getServiceManager().getServiceByName("openaireClientFactory", OpenaireClientFactory.class);
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.qaevent.service;
|
||||
|
||||
import org.dspace.content.QAEvent;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
/**
|
||||
* Service that handle the actions that can be done related to an
|
||||
* {@link QAEvent}.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public interface QAEventActionService {
|
||||
|
||||
/**
|
||||
* Accept the given event.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param qaevent the event to be accepted
|
||||
*/
|
||||
public void accept(Context context, QAEvent qaevent);
|
||||
|
||||
/**
|
||||
* Discard the given event.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param qaevent the event to be discarded
|
||||
*/
|
||||
public void discard(Context context, QAEvent qaevent);
|
||||
|
||||
/**
|
||||
* Reject the given event.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param qaevent the event to be rejected
|
||||
*/
|
||||
public void reject(Context context, QAEvent qaevent);
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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.qaevent.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.dspace.content.QAEvent;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.qaevent.QASource;
|
||||
import org.dspace.qaevent.QATopic;
|
||||
|
||||
/**
|
||||
* Service that handles {@link QAEvent}.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public interface QAEventService {
|
||||
|
||||
/**
|
||||
* Find all the event's topics.
|
||||
*
|
||||
* @param offset the offset to apply
|
||||
* @return the topics list
|
||||
*/
|
||||
public List<QATopic> findAllTopics(long offset, long count, String orderField, boolean ascending);
|
||||
/**
|
||||
* Find all the event's topics related to the given source.
|
||||
*
|
||||
* @param source the source to search for
|
||||
* @param offset the offset to apply
|
||||
* @param count the page size
|
||||
* @return the topics list
|
||||
*/
|
||||
public List<QATopic> findAllTopicsBySource(String source, long offset, long count,
|
||||
String orderField, boolean ascending);
|
||||
|
||||
/**
|
||||
* Count all the event's topics.
|
||||
*
|
||||
* @return the count result
|
||||
*/
|
||||
public long countTopics();
|
||||
|
||||
/**
|
||||
* Count all the event's topics related to the given source.
|
||||
*
|
||||
* @param source the source to search for
|
||||
* @return the count result
|
||||
*/
|
||||
public long countTopicsBySource(String source);
|
||||
|
||||
/**
|
||||
* Find all the events by topic.
|
||||
*
|
||||
* @param topic the topic to search for
|
||||
* @param offset the offset to apply
|
||||
* @param pageSize the page size
|
||||
* @param orderField the field to order for
|
||||
* @param ascending true if the order should be ascending, false otherwise
|
||||
* @return the events
|
||||
*/
|
||||
public List<QAEvent> findEventsByTopicAndPage(String topic, long offset, int pageSize,
|
||||
String orderField, boolean ascending);
|
||||
|
||||
/**
|
||||
* Find all the events by topic.
|
||||
*
|
||||
* @param topic the topic to search for
|
||||
* @return the events
|
||||
*/
|
||||
public List<QAEvent> findEventsByTopic(String topic);
|
||||
|
||||
/**
|
||||
* Find all the events by topic.
|
||||
*
|
||||
* @param topic the topic to search for
|
||||
* @return the events count
|
||||
*/
|
||||
public long countEventsByTopic(String topic);
|
||||
|
||||
/**
|
||||
* Find an event by the given id.
|
||||
*
|
||||
* @param id the id of the event to search for
|
||||
* @return the event
|
||||
*/
|
||||
public QAEvent findEventByEventId(String id);
|
||||
|
||||
/**
|
||||
* Store the given event.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param event the event to store
|
||||
*/
|
||||
public void store(Context context, QAEvent event);
|
||||
|
||||
/**
|
||||
* Delete an event by the given id.
|
||||
*
|
||||
* @param id the id of the event to delete
|
||||
*/
|
||||
public void deleteEventByEventId(String id);
|
||||
|
||||
/**
|
||||
* Delete events by the given target id.
|
||||
*
|
||||
* @param targetId the id of the target id
|
||||
*/
|
||||
public void deleteEventsByTargetId(UUID targetId);
|
||||
|
||||
/**
|
||||
* Find a specific topid by the given id.
|
||||
*
|
||||
* @param topicId the topic id to search for
|
||||
* @return the topic
|
||||
*/
|
||||
public QATopic findTopicByTopicId(String topicId);
|
||||
|
||||
/**
|
||||
* Find a specific source by the given name.
|
||||
*
|
||||
* @param source the source name
|
||||
* @return the source
|
||||
*/
|
||||
public QASource findSource(String source);
|
||||
|
||||
/**
|
||||
* Find all the event's sources.
|
||||
*
|
||||
* @param offset the offset to apply
|
||||
* @param pageSize the page size
|
||||
* @return the sources list
|
||||
*/
|
||||
public List<QASource> findAllSources(long offset, int pageSize);
|
||||
|
||||
/**
|
||||
* Count all the event's sources.
|
||||
*
|
||||
* @return the count result
|
||||
*/
|
||||
public long countSources();
|
||||
|
||||
/**
|
||||
* Check if the given QA event supports a related item.
|
||||
*
|
||||
* @param qaevent the event to be verified
|
||||
* @return true if the event supports a related item, false otherwise.
|
||||
*/
|
||||
public boolean isRelatedItemSupported(QAEvent qaevent);
|
||||
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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.qaevent.service.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QAMessageDTO} that model message coming from OPENAIRE.
|
||||
* @see <a href="https://graph.openaire.eu/docs/category/entities" target="_blank"> see </a>
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class OpenaireMessageDTO implements QAMessageDTO {
|
||||
|
||||
@JsonProperty("pids[0].value")
|
||||
private String value;
|
||||
|
||||
@JsonProperty("pids[0].type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("instances[0].hostedby")
|
||||
private String instanceHostedBy;
|
||||
|
||||
@JsonProperty("instances[0].instancetype")
|
||||
private String instanceInstanceType;
|
||||
|
||||
@JsonProperty("instances[0].license")
|
||||
private String instanceLicense;
|
||||
|
||||
@JsonProperty("instances[0].url")
|
||||
private String instanceUrl;
|
||||
|
||||
@JsonProperty("abstracts[0]")
|
||||
private String abstracts;
|
||||
|
||||
@JsonProperty("projects[0].acronym")
|
||||
private String acronym;
|
||||
|
||||
@JsonProperty("projects[0].code")
|
||||
private String code;
|
||||
|
||||
@JsonProperty("projects[0].funder")
|
||||
private String funder;
|
||||
|
||||
@JsonProperty("projects[0].fundingProgram")
|
||||
private String fundingProgram;
|
||||
|
||||
@JsonProperty("projects[0].jurisdiction")
|
||||
private String jurisdiction;
|
||||
|
||||
@JsonProperty("projects[0].openaireId")
|
||||
private String openaireId;
|
||||
|
||||
@JsonProperty("projects[0].title")
|
||||
private String title;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getInstanceHostedBy() {
|
||||
return instanceHostedBy;
|
||||
}
|
||||
|
||||
public void setInstanceHostedBy(String instanceHostedBy) {
|
||||
this.instanceHostedBy = instanceHostedBy;
|
||||
}
|
||||
|
||||
public String getInstanceInstanceType() {
|
||||
return instanceInstanceType;
|
||||
}
|
||||
|
||||
public void setInstanceInstanceType(String instanceInstanceType) {
|
||||
this.instanceInstanceType = instanceInstanceType;
|
||||
}
|
||||
|
||||
public String getInstanceLicense() {
|
||||
return instanceLicense;
|
||||
}
|
||||
|
||||
public void setInstanceLicense(String instanceLicense) {
|
||||
this.instanceLicense = instanceLicense;
|
||||
}
|
||||
|
||||
public String getInstanceUrl() {
|
||||
return instanceUrl;
|
||||
}
|
||||
|
||||
public void setInstanceUrl(String instanceUrl) {
|
||||
this.instanceUrl = instanceUrl;
|
||||
}
|
||||
|
||||
public String getAbstracts() {
|
||||
return abstracts;
|
||||
}
|
||||
|
||||
public void setAbstracts(String abstracts) {
|
||||
this.abstracts = abstracts;
|
||||
}
|
||||
|
||||
public String getAcronym() {
|
||||
return acronym;
|
||||
}
|
||||
|
||||
public void setAcronym(String acronym) {
|
||||
this.acronym = acronym;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getFunder() {
|
||||
return funder;
|
||||
}
|
||||
|
||||
public void setFunder(String funder) {
|
||||
this.funder = funder;
|
||||
}
|
||||
|
||||
public String getFundingProgram() {
|
||||
return fundingProgram;
|
||||
}
|
||||
|
||||
public void setFundingProgram(String fundingProgram) {
|
||||
this.fundingProgram = fundingProgram;
|
||||
}
|
||||
|
||||
public String getJurisdiction() {
|
||||
return jurisdiction;
|
||||
}
|
||||
|
||||
public void setJurisdiction(String jurisdiction) {
|
||||
this.jurisdiction = jurisdiction;
|
||||
}
|
||||
|
||||
public String getOpenaireId() {
|
||||
return openaireId;
|
||||
}
|
||||
|
||||
public void setOpenaireId(String openaireId) {
|
||||
this.openaireId = openaireId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
}
|
@@ -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.qaevent.service.dto;
|
||||
|
||||
import org.dspace.content.QAEvent;
|
||||
|
||||
/**
|
||||
* Interface for classes that contains the details related to a {@link QAEvent}.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public interface QAMessageDTO {
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.qaevent.service.impl;
|
||||
|
||||
import eu.dnetlib.broker.BrokerClient;
|
||||
import org.dspace.qaevent.service.OpenaireClientFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation of {@link OpenaireClientFactory} that returns the instance of
|
||||
* {@link BrokerClient} managed by the Spring context.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class OpenaireClientFactoryImpl implements OpenaireClientFactory {
|
||||
|
||||
@Autowired
|
||||
private BrokerClient brokerClient;
|
||||
|
||||
@Override
|
||||
public BrokerClient getBrokerClient() {
|
||||
return brokerClient;
|
||||
}
|
||||
|
||||
public void setBrokerClient(BrokerClient brokerClient) {
|
||||
this.brokerClient = brokerClient;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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.qaevent.service.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.QAEvent;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.qaevent.QualityAssuranceAction;
|
||||
import org.dspace.qaevent.service.QAEventActionService;
|
||||
import org.dspace.qaevent.service.QAEventService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QAEventActionService}.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAEventActionServiceImpl implements QAEventActionService {
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventActionServiceImpl.class);
|
||||
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
@Autowired
|
||||
private QAEventService qaEventService;
|
||||
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
private Map<String, QualityAssuranceAction> topicsToActions;
|
||||
|
||||
public void setTopicsToActions(Map<String, QualityAssuranceAction> topicsToActions) {
|
||||
this.topicsToActions = topicsToActions;
|
||||
}
|
||||
|
||||
public Map<String, QualityAssuranceAction> getTopicsToActions() {
|
||||
return topicsToActions;
|
||||
}
|
||||
|
||||
public QAEventActionServiceImpl() {
|
||||
jsonMapper = new JsonMapper();
|
||||
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Context context, QAEvent qaevent) {
|
||||
Item item = null;
|
||||
Item related = null;
|
||||
try {
|
||||
item = itemService.find(context, UUID.fromString(qaevent.getTarget()));
|
||||
if (qaevent.getRelated() != null) {
|
||||
related = itemService.find(context, UUID.fromString(qaevent.getRelated()));
|
||||
}
|
||||
topicsToActions.get(qaevent.getTopic()).applyCorrection(context, item, related,
|
||||
jsonMapper.readValue(qaevent.getMessage(), qaevent.getMessageDtoClass()));
|
||||
qaEventService.deleteEventByEventId(qaevent.getEventId());
|
||||
makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.ACCEPTED);
|
||||
} catch (SQLException | JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discard(Context context, QAEvent qaevent) {
|
||||
qaEventService.deleteEventByEventId(qaevent.getEventId());
|
||||
makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.DISCARDED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(Context context, QAEvent qaevent) {
|
||||
qaEventService.deleteEventByEventId(qaevent.getEventId());
|
||||
makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.REJECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make acknowledgement to the configured urls for the event status.
|
||||
*/
|
||||
private void makeAcknowledgement(String eventId, String source, String status) {
|
||||
String[] ackwnoledgeCallbacks = configurationService
|
||||
.getArrayProperty("qaevents." + source + ".acknowledge-url");
|
||||
if (ackwnoledgeCallbacks != null) {
|
||||
for (String ackwnoledgeCallback : ackwnoledgeCallbacks) {
|
||||
if (StringUtils.isNotBlank(ackwnoledgeCallback)) {
|
||||
ObjectNode node = jsonMapper.createObjectNode();
|
||||
node.put("eventId", eventId);
|
||||
node.put("status", status);
|
||||
StringEntity requestEntity = new StringEntity(node.toString(), ContentType.APPLICATION_JSON);
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpPost postMethod = new HttpPost(ackwnoledgeCallback);
|
||||
postMethod.setEntity(requestEntity);
|
||||
try {
|
||||
httpclient.execute(postMethod);
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,467 @@
|
||||
/**
|
||||
* 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.qaevent.service.impl;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.apache.commons.lang3.StringUtils.endsWith;
|
||||
import static org.dspace.content.QAEvent.OPENAIRE_SOURCE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.SolrQuery;
|
||||
import org.apache.solr.client.solrj.SolrQuery.ORDER;
|
||||
import org.apache.solr.client.solrj.SolrServerException;
|
||||
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
||||
import org.apache.solr.client.solrj.request.UpdateRequest;
|
||||
import org.apache.solr.client.solrj.response.FacetField;
|
||||
import org.apache.solr.client.solrj.response.FacetField.Count;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.common.SolrDocumentList;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.apache.solr.common.params.FacetParams;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.QAEvent;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.handle.service.HandleService;
|
||||
import org.dspace.qaevent.QASource;
|
||||
import org.dspace.qaevent.QATopic;
|
||||
import org.dspace.qaevent.dao.QAEventsDAO;
|
||||
import org.dspace.qaevent.dao.impl.QAEventsDAOImpl;
|
||||
import org.dspace.qaevent.service.QAEventService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QAEventService} that use Solr to store events. When
|
||||
* the user performs an action on the event (such as accepting the suggestion or
|
||||
* rejecting it) then the event is removed from solr and saved in the database
|
||||
* (see {@link QAEventsDAO}) so that it is no longer proposed.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class QAEventServiceImpl implements QAEventService {
|
||||
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
|
||||
@Autowired(required = true)
|
||||
protected ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
private HandleService handleService;
|
||||
|
||||
@Autowired
|
||||
private QAEventsDAOImpl qaEventsDao;
|
||||
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
public QAEventServiceImpl() {
|
||||
jsonMapper = new JsonMapper();
|
||||
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-Static CommonsHttpSolrServer for processing indexing events.
|
||||
*/
|
||||
protected SolrClient solr = null;
|
||||
|
||||
public static final String SOURCE = "source";
|
||||
public static final String ORIGINAL_ID = "original_id";
|
||||
public static final String TITLE = "title";
|
||||
public static final String TOPIC = "topic";
|
||||
public static final String TRUST = "trust";
|
||||
public static final String MESSAGE = "message";
|
||||
public static final String EVENT_ID = "event_id";
|
||||
public static final String RESOURCE_UUID = "resource_uuid";
|
||||
public static final String LAST_UPDATE = "last_update";
|
||||
public static final String RELATED_UUID = "related_uuid";
|
||||
|
||||
protected SolrClient getSolr() {
|
||||
if (solr == null) {
|
||||
String solrService = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getProperty("qaevents.solr.server", "http://localhost:8983/solr/qaevent");
|
||||
return new HttpSolrClient.Builder(solrService).build();
|
||||
}
|
||||
return solr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTopics() {
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setRows(0);
|
||||
solrQuery.setQuery("*:*");
|
||||
solrQuery.setFacet(true);
|
||||
solrQuery.setFacetMinCount(1);
|
||||
solrQuery.addFacetField(TOPIC);
|
||||
QueryResponse response;
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return response.getFacetField(TOPIC).getValueCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTopicsBySource(String source) {
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setRows(0);
|
||||
solrQuery.setQuery("*:*");
|
||||
solrQuery.setFacet(true);
|
||||
solrQuery.setFacetMinCount(1);
|
||||
solrQuery.addFacetField(TOPIC);
|
||||
solrQuery.addFilterQuery("source:" + source);
|
||||
QueryResponse response;
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return response.getFacetField(TOPIC).getValueCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteEventByEventId(String id) {
|
||||
try {
|
||||
getSolr().deleteById(id);
|
||||
getSolr().commit();
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteEventsByTargetId(UUID targetId) {
|
||||
try {
|
||||
getSolr().deleteByQuery(RESOURCE_UUID + ":" + targetId.toString());
|
||||
getSolr().commit();
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QATopic findTopicByTopicId(String topicId) {
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setRows(0);
|
||||
solrQuery.setQuery(TOPIC + ":" + topicId.replaceAll("!", "/"));
|
||||
solrQuery.setFacet(true);
|
||||
solrQuery.setFacetMinCount(1);
|
||||
solrQuery.addFacetField(TOPIC);
|
||||
QueryResponse response;
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
FacetField facetField = response.getFacetField(TOPIC);
|
||||
for (Count c : facetField.getValues()) {
|
||||
if (c.getName().equals(topicId.replace("!", "/"))) {
|
||||
QATopic topic = new QATopic();
|
||||
topic.setKey(c.getName());
|
||||
topic.setTotalEvents(c.getCount());
|
||||
topic.setLastEvent(new Date());
|
||||
return topic;
|
||||
}
|
||||
}
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QATopic> findAllTopics(long offset, long count, String orderField, boolean ascending) {
|
||||
return findAllTopicsBySource(null, offset, count, orderField, ascending);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QATopic> findAllTopicsBySource(String source, long offset, long count,
|
||||
String orderField, boolean ascending) {
|
||||
|
||||
if (source != null && isNotSupportedSource(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setRows(0);
|
||||
solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc);
|
||||
solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX);
|
||||
solrQuery.setQuery("*:*");
|
||||
solrQuery.setFacet(true);
|
||||
solrQuery.setFacetMinCount(1);
|
||||
solrQuery.setFacetLimit((int) (offset + count));
|
||||
solrQuery.addFacetField(TOPIC);
|
||||
if (source != null) {
|
||||
solrQuery.addFilterQuery(SOURCE + ":" + source);
|
||||
}
|
||||
QueryResponse response;
|
||||
List<QATopic> topics = new ArrayList<>();
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
FacetField facetField = response.getFacetField(TOPIC);
|
||||
topics = new ArrayList<>();
|
||||
int idx = 0;
|
||||
for (Count c : facetField.getValues()) {
|
||||
if (idx < offset) {
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
QATopic topic = new QATopic();
|
||||
topic.setKey(c.getName());
|
||||
topic.setTotalEvents(c.getCount());
|
||||
topic.setLastEvent(new Date());
|
||||
topics.add(topic);
|
||||
idx++;
|
||||
}
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return topics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(Context context, QAEvent dto) {
|
||||
|
||||
if (isNotSupportedSource(dto.getSource())) {
|
||||
throw new IllegalArgumentException("The source of the given event is not supported: " + dto.getSource());
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(dto.getTopic())) {
|
||||
throw new IllegalArgumentException("A topic is mandatory for an event");
|
||||
}
|
||||
|
||||
String checksum = dto.getEventId();
|
||||
try {
|
||||
if (!qaEventsDao.isEventStored(context, checksum)) {
|
||||
|
||||
SolrInputDocument doc = createSolrDocument(context, dto, checksum);
|
||||
|
||||
UpdateRequest updateRequest = new UpdateRequest();
|
||||
|
||||
updateRequest.add(doc);
|
||||
updateRequest.process(getSolr());
|
||||
|
||||
getSolr().commit();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QAEvent findEventByEventId(String eventId) {
|
||||
SolrQuery param = new SolrQuery(EVENT_ID + ":" + eventId);
|
||||
QueryResponse response;
|
||||
try {
|
||||
response = getSolr().query(param);
|
||||
if (response != null) {
|
||||
SolrDocumentList list = response.getResults();
|
||||
if (list != null && list.size() == 1) {
|
||||
SolrDocument doc = list.get(0);
|
||||
return getQAEventFromSOLR(doc);
|
||||
}
|
||||
}
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException("Exception querying Solr", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QAEvent> findEventsByTopicAndPage(String topic, long offset,
|
||||
int pageSize, String orderField, boolean ascending) {
|
||||
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setStart(((Long) offset).intValue());
|
||||
if (pageSize != -1) {
|
||||
solrQuery.setRows(pageSize);
|
||||
}
|
||||
solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc);
|
||||
solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/"));
|
||||
|
||||
QueryResponse response;
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
if (response != null) {
|
||||
SolrDocumentList list = response.getResults();
|
||||
List<QAEvent> responseItem = new ArrayList<>();
|
||||
for (SolrDocument doc : list) {
|
||||
QAEvent item = getQAEventFromSOLR(doc);
|
||||
responseItem.add(item);
|
||||
}
|
||||
return responseItem;
|
||||
}
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QAEvent> findEventsByTopic(String topic) {
|
||||
return findEventsByTopicAndPage(topic, 0, -1, TRUST, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countEventsByTopic(String topic) {
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setRows(0);
|
||||
solrQuery.setQuery(TOPIC + ":" + topic.replace("!", "/"));
|
||||
QueryResponse response = null;
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
return response.getResults().getNumFound();
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QASource findSource(String sourceName) {
|
||||
|
||||
if (isNotSupportedSource(sourceName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SolrQuery solrQuery = new SolrQuery("*:*");
|
||||
solrQuery.setRows(0);
|
||||
solrQuery.addFilterQuery(SOURCE + ":" + sourceName);
|
||||
solrQuery.setFacet(true);
|
||||
solrQuery.setFacetMinCount(1);
|
||||
solrQuery.addFacetField(SOURCE);
|
||||
|
||||
QueryResponse response;
|
||||
try {
|
||||
response = getSolr().query(solrQuery);
|
||||
FacetField facetField = response.getFacetField(SOURCE);
|
||||
for (Count c : facetField.getValues()) {
|
||||
if (c.getName().equalsIgnoreCase(sourceName)) {
|
||||
QASource source = new QASource();
|
||||
source.setName(c.getName());
|
||||
source.setTotalEvents(c.getCount());
|
||||
source.setLastEvent(new Date());
|
||||
return source;
|
||||
}
|
||||
}
|
||||
} catch (SolrServerException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
QASource source = new QASource();
|
||||
source.setName(sourceName);
|
||||
source.setTotalEvents(0L);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QASource> findAllSources(long offset, int pageSize) {
|
||||
return Arrays.stream(getSupportedSources())
|
||||
.map((sourceName) -> findSource(sourceName))
|
||||
.sorted(comparing(QASource::getTotalEvents).reversed())
|
||||
.skip(offset)
|
||||
.limit(pageSize)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countSources() {
|
||||
return getSupportedSources().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelatedItemSupported(QAEvent qaevent) {
|
||||
// Currently only PROJECT topics related to OPENAIRE supports related items
|
||||
return qaevent.getSource().equals(OPENAIRE_SOURCE) && endsWith(qaevent.getTopic(), "/PROJECT");
|
||||
}
|
||||
|
||||
private SolrInputDocument createSolrDocument(Context context, QAEvent dto, String checksum) throws Exception {
|
||||
SolrInputDocument doc = new SolrInputDocument();
|
||||
doc.addField(SOURCE, dto.getSource());
|
||||
doc.addField(EVENT_ID, checksum);
|
||||
doc.addField(ORIGINAL_ID, dto.getOriginalId());
|
||||
doc.addField(TITLE, dto.getTitle());
|
||||
doc.addField(TOPIC, dto.getTopic());
|
||||
doc.addField(TRUST, dto.getTrust());
|
||||
doc.addField(MESSAGE, dto.getMessage());
|
||||
doc.addField(LAST_UPDATE, new Date());
|
||||
final String resourceUUID = getResourceUUID(context, dto.getOriginalId());
|
||||
if (resourceUUID == null) {
|
||||
throw new IllegalArgumentException("Skipped event " + checksum +
|
||||
" related to the oai record " + dto.getOriginalId() + " as the record was not found");
|
||||
}
|
||||
doc.addField(RESOURCE_UUID, resourceUUID);
|
||||
doc.addField(RELATED_UUID, dto.getRelated());
|
||||
return doc;
|
||||
}
|
||||
|
||||
private String getResourceUUID(Context context, String originalId) throws Exception {
|
||||
String id = getHandleFromOriginalId(originalId);
|
||||
if (id != null) {
|
||||
Item item = (Item) handleService.resolveToObject(context, id);
|
||||
if (item != null) {
|
||||
final String itemUuid = item.getID().toString();
|
||||
context.uncacheEntity(item);
|
||||
return itemUuid;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Malformed originalId " + originalId);
|
||||
}
|
||||
}
|
||||
|
||||
// oai:www.openstarts.units.it:10077/21486
|
||||
private String getHandleFromOriginalId(String originalId) {
|
||||
int startPosition = originalId.lastIndexOf(':');
|
||||
if (startPosition != -1) {
|
||||
return originalId.substring(startPosition + 1, originalId.length());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private QAEvent getQAEventFromSOLR(SolrDocument doc) {
|
||||
QAEvent item = new QAEvent();
|
||||
item.setSource((String) doc.get(SOURCE));
|
||||
item.setEventId((String) doc.get(EVENT_ID));
|
||||
item.setLastUpdate((Date) doc.get(LAST_UPDATE));
|
||||
item.setMessage((String) doc.get(MESSAGE));
|
||||
item.setOriginalId((String) doc.get(ORIGINAL_ID));
|
||||
item.setTarget((String) doc.get(RESOURCE_UUID));
|
||||
item.setTitle((String) doc.get(TITLE));
|
||||
item.setTopic((String) doc.get(TOPIC));
|
||||
item.setTrust((double) doc.get(TRUST));
|
||||
item.setRelated((String) doc.get(RELATED_UUID));
|
||||
return item;
|
||||
}
|
||||
|
||||
private boolean isNotSupportedSource(String source) {
|
||||
return !ArrayUtils.contains(getSupportedSources(), source);
|
||||
}
|
||||
|
||||
private String[] getSupportedSources() {
|
||||
return configurationService.getArrayProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE });
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Extension of {@link JsonDeserializer} that convert a json to a String.
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class RawJsonDeserializer extends JsonDeserializer<String> {
|
||||
|
||||
@Override
|
||||
public String deserialize(JsonParser jp, DeserializationContext ctxt)
|
||||
throws IOException, JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
||||
JsonNode node = mapper.readTree(jp);
|
||||
return mapper.writeValueAsString(node);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user