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:
Tim Donohue
2023-12-18 13:51:24 -06:00
committed by GitHub
120 changed files with 7701 additions and 196 deletions

View File

@@ -121,6 +121,8 @@ services:
cp -r /opt/solr/server/solr/configsets/search/* search
precreate-core statistics /opt/solr/server/solr/configsets/statistics
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
exec solr -f
volumes:
assetstore:

View File

@@ -820,6 +820,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>eu.openaire</groupId>
<artifactId>broker-client</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-junit-rule</artifactId>

View File

@@ -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 {

View File

@@ -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);

View 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());
}
}
}

View File

@@ -0,0 +1,82 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.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;
}
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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 };

View File

@@ -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);
}

View File

@@ -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
*/

View File

@@ -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 {
}
}

View 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;
}
}

View File

@@ -0,0 +1,47 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,21 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.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 {
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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 });
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,16 @@
--
-- 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/
--
CREATE TABLE qaevent_processed (
qaevent_id VARCHAR(255) NOT NULL,
qaevent_timestamp TIMESTAMP NULL,
eperson_uuid UUID NULL REFERENCES eperson(uuid),
item_uuid uuid NOT NULL REFERENCES item(uuid)
);
CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid);

View File

@@ -0,0 +1,19 @@
--
-- 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/
--
CREATE TABLE qaevent_processed (
qaevent_id VARCHAR(255) NOT NULL,
qaevent_timestamp TIMESTAMP NULL,
eperson_uuid UUID NULL,
item_uuid UUID NULL,
CONSTRAINT qaevent_pk PRIMARY KEY (qaevent_id),
CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid),
CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid)
);
CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid);

View File

@@ -95,14 +95,14 @@ loglevel.dspace = INFO
# IIIF TEST SETTINGS #
########################
iiif.enabled = true
event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif
event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif, qaeventsdelete
###########################################
# CUSTOM UNIT / INTEGRATION TEST SETTINGS #
###########################################
# custom dispatcher to be used by dspace-api IT that doesn't need SOLR
event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher
event.dispatcher.exclude-discovery.consumers = versioning, eperson
event.dispatcher.exclude-discovery.consumers = versioning, eperson, qaeventsdelete
# Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest)
# (This overrides default, commented out settings in dspace.cfg)

View File

@@ -7,7 +7,7 @@
http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean id="mockOpenAIRERestConnector" class="org.dspace.external.MockOpenAIRERestConnector">
<bean id="mockOpenaireRestConnector" class="org.dspace.external.MockOpenaireRestConnector">
<constructor-arg value="${openaire.api.url:https://api.openaire.eu}"/>
<property name="tokenEnabled" value="${openaire.token.enabled:false}"/>
<property name="tokenServiceUrl" value="${openaire.token.url:https://aai.openaire.eu/oidc/token}"/>
@@ -15,10 +15,10 @@
<property name="clientSecret" value="${openaire.token.clientSecret}"/>
</bean>
<bean
class="org.dspace.external.provider.impl.OpenAIREFundingDataProvider"
class="org.dspace.external.provider.impl.OpenaireFundingDataProvider"
init-method="init">
<property name="sourceIdentifier" value="openAIREFunding" />
<property name="connector" ref="mockOpenAIRERestConnector" />
<property name="sourceIdentifier" value="openaireFunding" />
<property name="connector" ref="mockOpenaireRestConnector" />
<property name="metadataFields" ref="mapOfmetadata"/>
<property name="supportedEntityTypes">
<list>

View File

@@ -286,7 +286,7 @@
</property>
</bean>
<!-- An example of an OpenAIRE compliance filter based on the same rules in xoai.xml
<!-- An example of an Openaire compliance filter based on the same rules in xoai.xml
some sub-statements are defined within this bean, and some are referenced from earlier definitions
-->
<bean id="openaire_filter" class="org.dspace.content.logic.DefaultFilter">
@@ -329,7 +329,7 @@
</list>
</property>
</bean>
<!-- AND the dc.relation is a valid OpenAIRE identifier
<!-- AND the dc.relation is a valid Openaire identifier
(starts with "info:eu-repo/grantAgreement/") -->
<bean id="has-openaire-relation_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">

View File

@@ -65,6 +65,11 @@
<property name="dspaceRunnableClass" value="org.dspace.orcid.script.OrcidBulkPush"/>
</bean>
<bean id="import-openaire-events" class="org.dspace.qaevent.script.OpenaireEventsImportCliScriptConfiguration" primary="true">
<property name="description" value="Import new openaire quality assurance broker events"/>
<property name="dspaceRunnableClass" value="org.dspace.qaevent.script.OpenaireEventsImportCli"/>
</bean>
<bean id="process-cleaner" class="org.dspace.administer.ProcessCleanerCliConfiguration">
<property name="description" value="Cleanup all the old processes in the specified state"/>
<property name="dspaceRunnableClass" value="org.dspace.administer.ProcessCleanerCli"/>

View File

@@ -48,6 +48,11 @@
class="org.dspace.statistics.MockSolrStatisticsCore"
autowire-candidate="true"/>
<!-- qa events -->
<bean class="org.dspace.qaevent.MockQAEventService"
id="org.dspace.qaevent.service.QAEventService" />
<bean class="org.dspace.statistics.GeoIpService" autowire-candidate="true"/>
</beans>

View File

@@ -29,6 +29,8 @@ import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.kernel.ServiceManager;
import org.dspace.qaevent.MockQAEventService;
import org.dspace.qaevent.service.QAEventService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.statistics.MockSolrLoggerServiceImpl;
import org.dspace.statistics.MockSolrStatisticsCore;
@@ -196,6 +198,10 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati
.getServiceByName(AuthoritySearchService.class.getName(), MockAuthoritySolrServiceImpl.class);
authorityService.reset();
MockQAEventService qaEventService = serviceManager
.getServiceByName(QAEventService.class.getName(), MockQAEventService.class);
qaEventService.reset();
// Reload our ConfigurationService (to reset configs to defaults again)
DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig();

View File

@@ -61,6 +61,12 @@ public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler
errorMessages.add(message);
}
@Override
public void logError(String message, Throwable throwable) {
super.logError(message, throwable);
errorMessages.add(message);
}
public List<String> getInfoMessages() {
return infoMessages;
}

View File

@@ -49,6 +49,7 @@ import org.dspace.orcid.factory.OrcidServiceFactory;
import org.dspace.orcid.service.OrcidHistoryService;
import org.dspace.orcid.service.OrcidQueueService;
import org.dspace.orcid.service.OrcidTokenService;
import org.dspace.qaevent.service.QAEventService;
import org.dspace.scripts.factory.ScriptServiceFactory;
import org.dspace.scripts.service.ProcessService;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -56,6 +57,7 @@ import org.dspace.submit.factory.SubmissionServiceFactory;
import org.dspace.submit.service.SubmissionConfigService;
import org.dspace.supervision.factory.SupervisionOrderServiceFactory;
import org.dspace.supervision.service.SupervisionOrderService;
import org.dspace.utils.DSpace;
import org.dspace.versioning.factory.VersionServiceFactory;
import org.dspace.versioning.service.VersionHistoryService;
import org.dspace.versioning.service.VersioningService;
@@ -113,7 +115,7 @@ public abstract class AbstractBuilder<T, S> {
static SubmissionConfigService submissionConfigService;
static SubscribeService subscribeService;
static SupervisionOrderService supervisionOrderService;
static QAEventService qaEventService;
protected Context context;
@@ -182,6 +184,7 @@ public abstract class AbstractBuilder<T, S> {
}
subscribeService = ContentServiceFactory.getInstance().getSubscribeService();
supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService();
qaEventService = new DSpace().getSingletonService(QAEventService.class);
}
@@ -219,6 +222,7 @@ public abstract class AbstractBuilder<T, S> {
submissionConfigService = null;
subscribeService = null;
supervisionOrderService = null;
qaEventService = null;
}

View File

@@ -0,0 +1,141 @@
/**
* 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.builder;
import java.util.Date;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.QAEvent;
import org.dspace.core.Context;
import org.dspace.qaevent.service.QAEventService;
/**
* Builder to construct Quality Assurance Broker Event objects
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
public class QAEventBuilder extends AbstractBuilder<QAEvent, QAEventService> {
private Item item;
private QAEvent target;
private String source = QAEvent.OPENAIRE_SOURCE;
/**
* the title of the DSpace object
* */
private String title;
/**
* the name of the Quality Assurance Event Topic
* */
private String topic;
/**
* thr original QA Event imported
* */
private String message;
/**
* uuid of the targeted DSpace object
* */
private String relatedItem;
private double trust = 0.5;
private Date lastUpdate = new Date();
protected QAEventBuilder(Context context) {
super(context);
}
public static QAEventBuilder createTarget(final Context context, final Collection col, final String name) {
QAEventBuilder builder = new QAEventBuilder(context);
return builder.create(context, col, name);
}
public static QAEventBuilder createTarget(final Context context, final Item item) {
QAEventBuilder builder = new QAEventBuilder(context);
return builder.create(context, item);
}
private QAEventBuilder create(final Context context, final Collection col, final String name) {
this.context = context;
try {
ItemBuilder itemBuilder = ItemBuilder.createItem(context, col).withTitle(name);
item = itemBuilder.build();
this.title = name;
context.dispatchEvents();
indexingService.commit();
} catch (Exception e) {
return handleException(e);
}
return this;
}
private QAEventBuilder create(final Context context, final Item item) {
this.context = context;
this.item = item;
return this;
}
public QAEventBuilder withTopic(final String topic) {
this.topic = topic;
return this;
}
public QAEventBuilder withSource(final String source) {
this.source = source;
return this;
}
public QAEventBuilder withTitle(final String title) {
this.title = title;
return this;
}
public QAEventBuilder withMessage(final String message) {
this.message = message;
return this;
}
public QAEventBuilder withTrust(final double trust) {
this.trust = trust;
return this;
}
public QAEventBuilder withLastUpdate(final Date lastUpdate) {
this.lastUpdate = lastUpdate;
return this;
}
public QAEventBuilder withRelatedItem(String relatedItem) {
this.relatedItem = relatedItem;
return this;
}
@Override
public QAEvent build() {
target = new QAEvent(source, "oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic,
trust, message, lastUpdate);
target.setRelated(relatedItem);
try {
qaEventService.store(context, target);
} catch (Exception e) {
e.printStackTrace();
}
return target;
}
@Override
public void cleanup() throws Exception {
qaEventService.deleteEventByEventId(target.getEventId());
}
@Override
protected QAEventService getService() {
return qaEventService;
}
@Override
public void delete(Context c, QAEvent dso) throws Exception {
qaEventService.deleteEventByEventId(target.getEventId());
// qaEventService.deleteTarget(dso);
}
}

View File

@@ -1200,4 +1200,71 @@ public class CollectionTest extends AbstractDSpaceObjectTest {
equalTo(owningCommunity));
}
/**
* Test of retrieveCollectionWithSubmitByEntityType method getting the closest
* collection of non-item type starting from an item
*/
@Test
public void testRetrieveCollectionWithSubmitByEntityType() throws SQLException, AuthorizeException {
context.setDispatcher("default");
context.turnOffAuthorisationSystem();
Community com = communityService.create(null, context);
Group submitters = groupService.create(context);
Collection collection = collectionService.create(context, com);
collectionService.addMetadata(context, collection, "dspace", "entity", "type",
null, "Publication");
com.addCollection(collection);
WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false);
Item item = installItemService.installItem(context, workspaceItem);
EPerson epersonA = ePersonService.create(context);
Collection collectionPerson = collectionService.create(context, com);
collectionService.addMetadata(context, collectionPerson, "dspace", "entity", "type",
null, "Person");
collectionPerson.setSubmitters(submitters);
groupService.addMember(context, submitters, epersonA);
context.setCurrentUser(epersonA);
context.commit();
context.restoreAuthSystemState();
Collection resultCollection = collectionService.retrieveCollectionWithSubmitByEntityType
(context, item, "Person");
assertThat("testRetrieveCollectionWithSubmitByEntityType 0", resultCollection, notNullValue());
assertThat("testRetrieveCollectionWithSubmitByEntityType 1", resultCollection, equalTo(collectionPerson));
context.setDispatcher("exclude-discovery");
}
/**
* Test of rretrieveCollectionWithSubmitByCommunityAndEntityType method getting the closest
* collection of non-community type starting from an community
*/
@Test
public void testRetrieveCollectionWithSubmitByCommunityAndEntityType() throws SQLException, AuthorizeException {
context.setDispatcher("default");
context.turnOffAuthorisationSystem();
Community com = communityService.create(null, context);
Group submitters = groupService.create(context);
Collection collection = collectionService.create(context, com);
collectionService.addMetadata(context, collection, "dspace", "entity", "type",
null, "Publication");
com.addCollection(collection);
WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false);
Item item = installItemService.installItem(context, workspaceItem);
EPerson epersonA = ePersonService.create(context);
Collection collectionPerson = collectionService.create(context, com);
collectionService.addMetadata(context, collectionPerson, "dspace", "entity", "type",
null, "Person");
collectionPerson.setSubmitters(submitters);
groupService.addMember(context, submitters, epersonA);
context.setCurrentUser(epersonA);
context.commit();
context.restoreAuthSystemState();
Collection resultCollection = collectionService.retrieveCollectionWithSubmitByCommunityAndEntityType
(context, com, "Person");
assertThat("testRetrieveCollectionWithSubmitByEntityType 0", resultCollection, notNullValue());
assertThat("testRetrieveCollectionWithSubmitByEntityType 1", resultCollection, equalTo(collectionPerson));
context.setDispatcher("exclude-discovery");
}
}

View File

@@ -14,15 +14,15 @@ import eu.openaire.jaxb.helper.OpenAIREHandler;
import eu.openaire.jaxb.model.Response;
/**
* Mock the OpenAIRE rest connector for unit testing<br>
* Mock the Openaire rest connector for unit testing<br>
* will be resolved against static test xml files
*
* @author pgraca
*
*/
public class MockOpenAIRERestConnector extends OpenAIRERestConnector {
public class MockOpenaireRestConnector extends OpenaireRestConnector {
public MockOpenAIRERestConnector(String url) {
public MockOpenaireRestConnector(String url) {
super(url);
}

View File

@@ -23,15 +23,15 @@ import org.junit.Before;
import org.junit.Test;
/**
* Unit tests for OpenAIREFundingDataProvider
* Unit tests for OpenaireFundingDataProvider
*
* @author pgraca
*
*/
public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest {
public class OpenaireFundingDataProviderTest extends AbstractDSpaceTest {
ExternalDataService externalDataService;
ExternalDataProvider openAIREFundingDataProvider;
ExternalDataProvider openaireFundingDataProvider;
/**
* This method will be run before every test as per @Before. It will initialize
@@ -44,38 +44,38 @@ public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest {
public void init() {
// Set up External Service Factory and set data providers
externalDataService = ExternalServiceFactory.getInstance().getExternalDataService();
openAIREFundingDataProvider = externalDataService.getExternalDataProvider("openAIREFunding");
openaireFundingDataProvider = externalDataService.getExternalDataProvider("openaireFunding");
}
@Test
public void testNumberOfResultsWSingleKeyword() {
assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider);
assertEquals("openAIREFunding.numberOfResults.query:mock", 77,
openAIREFundingDataProvider.getNumberOfResults("mock"));
assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider);
assertEquals("openaireFunding.numberOfResults.query:mock", 77,
openaireFundingDataProvider.getNumberOfResults("mock"));
}
@Test
public void testNumberOfResultsWKeywords() {
assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider);
assertEquals("openAIREFunding.numberOfResults.query:mock+test", 77,
openAIREFundingDataProvider.getNumberOfResults("mock+test"));
assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider);
assertEquals("openaireFunding.numberOfResults.query:mock+test", 77,
openaireFundingDataProvider.getNumberOfResults("mock+test"));
}
@Test
public void testQueryResultsWSingleKeyword() {
assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider);
List<ExternalDataObject> results = openAIREFundingDataProvider.searchExternalDataObjects("mock", 0, 10);
assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size());
assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider);
List<ExternalDataObject> results = openaireFundingDataProvider.searchExternalDataObjects("mock", 0, 10);
assertEquals("openaireFunding.searchExternalDataObjects.size", 10, results.size());
}
@Test
public void testQueryResultsWKeywords() {
String value = "Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system";
assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider);
List<ExternalDataObject> results = openAIREFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10);
assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size());
assertTrue("openAIREFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue()));
assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider);
List<ExternalDataObject> results = openaireFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10);
assertEquals("openaireFunding.searchExternalDataObjects.size", 10, results.size());
assertTrue("openaireFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue()));
}
@Test
@@ -84,22 +84,22 @@ public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest {
String value = "Portuguese Wild Mushrooms: Chemical characterization and functional study"
+ " of antiproliferative and proapoptotic properties in cancer cell lines";
assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider);
assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider);
Optional<ExternalDataObject> result = openAIREFundingDataProvider.getExternalDataObject(id);
Optional<ExternalDataObject> result = openaireFundingDataProvider.getExternalDataObject(id);
assertTrue("openAIREFunding.getExternalDataObject.exists", result.isPresent());
assertTrue("openAIREFunding.getExternalDataObject.value", value.equals(result.get().getValue()));
assertTrue("openaireFunding.getExternalDataObject.exists", result.isPresent());
assertTrue("openaireFunding.getExternalDataObject.value", value.equals(result.get().getValue()));
}
@Test
public void testGetDataObjectWInvalidId() {
String id = "WRONGID";
assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider);
assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider);
Optional<ExternalDataObject> result = openAIREFundingDataProvider.getExternalDataObject(id);
Optional<ExternalDataObject> result = openaireFundingDataProvider.getExternalDataObject(id);
assertTrue("openAIREFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty());
assertTrue("openaireFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty());
}
}

View File

@@ -0,0 +1,117 @@
/**
* 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.matcher;
import static org.dspace.content.QAEvent.OPENAIRE_SOURCE;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import org.dspace.content.Item;
import org.dspace.content.QAEvent;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
/**
* Implementation of {@link org.hamcrest.Matcher} to match a QAEvent by all its
* attributes.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class QAEventMatcher extends TypeSafeMatcher<QAEvent> {
private Matcher<String> eventIdMatcher;
private Matcher<String> originalIdMatcher;
private Matcher<String> relatedMatcher;
private Matcher<String> sourceMatcher;
private Matcher<String> statusMatcher;
private Matcher<String> targetMatcher;
private Matcher<String> titleMatcher;
private Matcher<String> messageMatcher;
private Matcher<String> topicMatcher;
private Matcher<Double> trustMatcher;
private QAEventMatcher(Matcher<String> eventIdMatcher, Matcher<String> originalIdMatcher,
Matcher<String> relatedMatcher, Matcher<String> sourceMatcher, Matcher<String> statusMatcher,
Matcher<String> targetMatcher, Matcher<String> titleMatcher, Matcher<String> messageMatcher,
Matcher<String> topicMatcher, Matcher<Double> trustMatcher) {
this.eventIdMatcher = eventIdMatcher;
this.originalIdMatcher = originalIdMatcher;
this.relatedMatcher = relatedMatcher;
this.sourceMatcher = sourceMatcher;
this.statusMatcher = statusMatcher;
this.targetMatcher = targetMatcher;
this.titleMatcher = titleMatcher;
this.messageMatcher = messageMatcher;
this.topicMatcher = topicMatcher;
this.trustMatcher = trustMatcher;
}
/**
* Creates an instance of {@link QAEventMatcher} that matches an OPENAIRE
* QAEvent with PENDING status, with an event id, without a related item and
* with the given attributes.
*
* @param originalId the original id to match
* @param target the target to match
* @param title the title to match
* @param message the message to match
* @param topic the topic to match
* @param trust the trust to match
* @return the matcher istance
*/
public static QAEventMatcher pendingOpenaireEventWith(String originalId, Item target,
String title, String message, String topic, Double trust) {
return new QAEventMatcher(notNullValue(String.class), is(originalId), nullValue(String.class),
is(OPENAIRE_SOURCE), is("PENDING"), is(target.getID().toString()), is(title), is(message), is(topic),
is(trust));
}
@Override
public boolean matchesSafely(QAEvent event) {
return eventIdMatcher.matches(event.getEventId())
&& originalIdMatcher.matches(event.getOriginalId())
&& relatedMatcher.matches(event.getRelated())
&& sourceMatcher.matches(event.getSource())
&& statusMatcher.matches(event.getStatus())
&& targetMatcher.matches(event.getTarget())
&& titleMatcher.matches(event.getTitle())
&& messageMatcher.matches(event.getMessage())
&& topicMatcher.matches(event.getTopic())
&& trustMatcher.matches(event.getTrust());
}
@Override
public void describeTo(Description description) {
description.appendText("a QA event with the following attributes:")
.appendText(" event id ").appendDescriptionOf(eventIdMatcher)
.appendText(", original id ").appendDescriptionOf(originalIdMatcher)
.appendText(", related ").appendDescriptionOf(relatedMatcher)
.appendText(", source ").appendDescriptionOf(sourceMatcher)
.appendText(", status ").appendDescriptionOf(statusMatcher)
.appendText(", target ").appendDescriptionOf(targetMatcher)
.appendText(", title ").appendDescriptionOf(titleMatcher)
.appendText(", message ").appendDescriptionOf(messageMatcher)
.appendText(", topic ").appendDescriptionOf(topicMatcher)
.appendText(" and trust ").appendDescriptionOf(trustMatcher);
}
}

View File

@@ -0,0 +1,58 @@
/**
* 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.matcher;
import static org.hamcrest.Matchers.is;
import org.dspace.qaevent.QASource;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
/**
* Implementation of {@link org.hamcrest.Matcher} to match a QASource by all its
* attributes.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class QASourceMatcher extends TypeSafeMatcher<QASource> {
private Matcher<String> nameMatcher;
private Matcher<Long> totalEventsMatcher;
private QASourceMatcher(Matcher<String> nameMatcher, Matcher<Long> totalEventsMatcher) {
this.nameMatcher = nameMatcher;
this.totalEventsMatcher = totalEventsMatcher;
}
/**
* Creates an instance of {@link QASourceMatcher} that matches a QATopic with
* the given name and total events count.
* @param name the name to match
* @param totalEvents the total events count to match
* @return the matcher instance
*/
public static QASourceMatcher with(String name, long totalEvents) {
return new QASourceMatcher(is(name), is(totalEvents));
}
@Override
public boolean matchesSafely(QASource event) {
return nameMatcher.matches(event.getName()) && totalEventsMatcher.matches(event.getTotalEvents());
}
@Override
public void describeTo(Description description) {
description.appendText("a QA source with the following attributes:")
.appendText(" name ").appendDescriptionOf(nameMatcher)
.appendText(" and total events ").appendDescriptionOf(totalEventsMatcher);
}
}

View File

@@ -0,0 +1,58 @@
/**
* 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.matcher;
import static org.hamcrest.Matchers.is;
import org.dspace.qaevent.QATopic;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
/**
* Implementation of {@link org.hamcrest.Matcher} to match a QATopic by all its
* attributes.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class QATopicMatcher extends TypeSafeMatcher<QATopic> {
private Matcher<String> keyMatcher;
private Matcher<Long> totalEventsMatcher;
private QATopicMatcher(Matcher<String> keyMatcher, Matcher<Long> totalEventsMatcher) {
this.keyMatcher = keyMatcher;
this.totalEventsMatcher = totalEventsMatcher;
}
/**
* Creates an instance of {@link QATopicMatcher} that matches a QATopic with the
* given key and total events count.
* @param key the key to match
* @param totalEvents the total events count to match
* @return the matcher instance
*/
public static QATopicMatcher with(String key, long totalEvents) {
return new QATopicMatcher(is(key), is(totalEvents));
}
@Override
public boolean matchesSafely(QATopic event) {
return keyMatcher.matches(event.getKey()) && totalEventsMatcher.matches(event.getTotalEvents());
}
@Override
public void describeTo(Description description) {
description.appendText("a QA topic with the following attributes:")
.appendText(" key ").appendDescriptionOf(keyMatcher)
.appendText(" and total events ").appendDescriptionOf(totalEventsMatcher);
}
}

View 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.io.IOException;
import org.apache.solr.client.solrj.SolrServerException;
import org.dspace.qaevent.service.impl.QAEventServiceImpl;
import org.dspace.solr.MockSolrServer;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
/**
* Mock SOLR service for the qaevents Core.
*/
@Service
public class MockQAEventService extends QAEventServiceImpl implements InitializingBean, DisposableBean {
private MockSolrServer mockSolrServer;
@Override
public void afterPropertiesSet() throws Exception {
mockSolrServer = new MockSolrServer("qaevent");
solr = mockSolrServer.getSolrServer();
}
/** Clear all records from the search core. */
public void reset() {
mockSolrServer.reset();
try {
mockSolrServer.getSolrServer().commit();
} catch (SolrServerException | IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void destroy() throws Exception {
mockSolrServer.destroy();
}
}

View File

@@ -0,0 +1,488 @@
/**
* 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 java.util.List.of;
import static org.dspace.content.QAEvent.OPENAIRE_SOURCE;
import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.URL;
import eu.dnetlib.broker.BrokerClient;
import org.apache.commons.io.IOUtils;
import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.app.launcher.ScriptLauncher;
import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.matcher.QASourceMatcher;
import org.dspace.matcher.QATopicMatcher;
import org.dspace.qaevent.service.OpenaireClientFactory;
import org.dspace.qaevent.service.QAEventService;
import org.dspace.qaevent.service.impl.OpenaireClientFactoryImpl;
import org.dspace.utils.DSpace;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Integration tests for {@link OpenaireEventsImport}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase {
private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/";
private static final String ORDER_FIELD = "topic";
private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class);
private Collection collection;
private BrokerClient brokerClient = OpenaireClientFactory.getInstance().getBrokerClient();
private BrokerClient mockBrokerClient = mock(BrokerClient.class);
@Before
public void setup() {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection")
.build();
context.restoreAuthSystemState();
((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient);
}
@After
public void after() {
((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(brokerClient);
}
@Test
public void testWithoutParameters() throws Exception {
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(), empty());
assertThat(handler.getInfoMessages(), empty());
Exception exception = handler.getException();
assertThat(exception, instanceOf(IllegalArgumentException.class));
assertThat(exception.getMessage(), is("One parameter between the location of the file and the email "
+ "must be entered to proceed with the import."));
verifyNoInteractions(mockBrokerClient);
}
@Test
public void testWithBothFileAndEmailParameters() throws Exception {
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json"),
"-e", "test@user.com" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(), empty());
assertThat(handler.getInfoMessages(), empty());
Exception exception = handler.getException();
assertThat(exception, instanceOf(IllegalArgumentException.class));
assertThat(exception.getMessage(), is("Only one parameter between the location of the file and the email "
+ "must be entered to proceed with the import."));
verifyNoInteractions(mockBrokerClient);
}
@Test
@SuppressWarnings("unchecked")
public void testManyEventsImportFromFile() throws Exception {
context.turnOffAuthorisationSystem();
Item firstItem = createItem("Test item", "123456789/99998");
Item secondItem = createItem("Test item 2", "123456789/99999");
context.restoreAuthSystemState();
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json") };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(), empty());
assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the provided file",
"Found 5 events in the given file"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder(
QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MORE/PID", 1L),
QATopicMatcher.with("ENRICH/MISSING/PID", 1L),
QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L)));
String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\","
+ "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}";
assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem,
"Egypt, crossroad of translations and literary interweavings", projectMessage,
"ENRICH/MORE/PROJECT", 1.00d)));
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication",
abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d)));
verifyNoInteractions(mockBrokerClient);
}
@Test
public void testManyEventsImportFromFileWithUnknownHandle() throws Exception {
context.turnOffAuthorisationSystem();
Item item = createItem("Test item", "123456789/99999");
context.restoreAuthSystemState();
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json") };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(),
contains("An error occurs storing the event with id b4e09c71312cd7c397969f56c900823f: "
+ "Skipped event b4e09c71312cd7c397969f56c900823f related to the oai record "
+ "oai:www.openstarts.units.it:123456789/99998 as the record was not found",
"An error occurs storing the event with id d050d2c4399c6c6ccf27d52d479d26e4: "
+ "Skipped event d050d2c4399c6c6ccf27d52d479d26e4 related to the oai record "
+ "oai:www.openstarts.units.it:123456789/99998 as the record was not found"));
assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the provided file",
"Found 5 events in the given file"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder(
QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L),
QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MORE/PID", 1L)
));
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication",
abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d)));
verifyNoInteractions(mockBrokerClient);
}
@Test
public void testManyEventsImportFromFileWithUnknownTopic() throws Exception {
context.turnOffAuthorisationSystem();
createItem("Test item", "123456789/99999");
Item secondItem = createItem("Test item 2", "123456789/999991");
context.restoreAuthSystemState();
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("unknown-topic-events.json") };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(),
contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg"));
assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the provided file",
"Found 2 events in the given file"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false),
contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L)));
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2",
abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d)));
verifyNoInteractions(mockBrokerClient);
}
@Test
public void testImportFromFileWithoutEvents() throws Exception {
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("empty-file.json") };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(),
contains(containsString("A not recoverable error occurs during OPENAIRE events import")));
assertThat(handler.getWarningMessages(),empty());
assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the provided file"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty());
verifyNoInteractions(mockBrokerClient);
}
@Test
@SuppressWarnings("unchecked")
public void testImportFromOpenaireBroker() throws Exception {
context.turnOffAuthorisationSystem();
Item firstItem = createItem("Test item", "123456789/99998");
Item secondItem = createItem("Test item 2", "123456789/99999");
Item thirdItem = createItem("Test item 3", "123456789/999991");
context.restoreAuthSystemState();
URL openaireURL = new URL("http://api.openaire.eu/broker");
when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")).thenReturn(of("sub1", "sub2", "sub3"));
doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "events.json"))
.when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any());
doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "empty-events-list.json"))
.when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any());
doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "unknown-topic-events.json"))
.when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any());
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(),
contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg"));
assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the OPENAIRE broker",
"Found 3 subscriptions related to the given email",
"Found 5 events from the subscription sub1",
"Found 0 events from the subscription sub2",
"Found 2 events from the subscription sub3"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder(
QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MORE/PID", 1L),
QATopicMatcher.with("ENRICH/MISSING/PID", 1L),
QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L)));
String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\","
+ "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}";
assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem,
"Egypt, crossroad of translations and literary interweavings", projectMessage,
"ENRICH/MORE/PROJECT", 1.00d)));
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), containsInAnyOrder(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication",
abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d),
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", thirdItem, "Test Publication 2",
abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d)));
verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com");
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any());
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any());
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any());
verifyNoMoreInteractions(mockBrokerClient);
}
@Test
public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws Exception {
URL openaireURL = new URL("http://api.openaire.eu/broker");
when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com"))
.thenThrow(new RuntimeException("Connection refused"));
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(),
contains("A not recoverable error occurs during OPENAIRE events import: Connection refused"));
assertThat(handler.getWarningMessages(), empty());
assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the OPENAIRE broker"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty());
verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com");
verifyNoMoreInteractions(mockBrokerClient);
}
@Test
@SuppressWarnings("unchecked")
public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception {
context.turnOffAuthorisationSystem();
createItem("Test item", "123456789/99998");
createItem("Test item 2", "123456789/99999");
createItem("Test item 3", "123456789/999991");
context.restoreAuthSystemState();
URL openaireURL = new URL("http://api.openaire.eu/broker");
when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")).thenReturn(of("sub1", "sub2", "sub3"));
doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "events.json"))
.when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any());
doThrow(new RuntimeException("Invalid subscription id"))
.when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any());
doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "unknown-topic-events.json"))
.when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any());
TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl);
assertThat(handler.getErrorMessages(), contains("An error occurs downloading the events "
+ "related to the subscription sub2: Invalid subscription id"));
assertThat(handler.getWarningMessages(),
contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg"));
assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the OPENAIRE broker",
"Found 3 subscriptions related to the given email",
"Found 5 events from the subscription sub1",
"Found 0 events from the subscription sub2",
"Found 2 events from the subscription sub3"));
assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L)));
assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder(
QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MISSING/PID", 1L),
QATopicMatcher.with("ENRICH/MORE/PID", 1L),
QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L),
QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L)));
assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1));
assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2));
verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com");
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any());
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any());
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any());
verifyNoMoreInteractions(mockBrokerClient);
}
private Item createItem(String title, String handle) {
return ItemBuilder.createItem(context, collection)
.withTitle(title)
.withHandle(handle)
.build();
}
private Void writeToOutputStream(OutputStream outputStream, String fileName) {
try {
byte[] fileContent = getFileContent(fileName);
IOUtils.write(fileContent, outputStream);
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private byte[] getFileContent(String fileName) throws Exception {
String fileLocation = getFileLocation(fileName);
try (FileInputStream fis = new FileInputStream(new File(fileLocation))) {
return IOUtils.toByteArray(fis);
}
}
private String getFileLocation(String fileName) throws Exception {
URL resource = getClass().getClassLoader().getResource(BASE_JSON_DIR_PATH + fileName);
if (resource == null) {
throw new IllegalStateException("No resource found named " + BASE_JSON_DIR_PATH + fileName);
}
return new File(resource.getFile()).getAbsolutePath();
}
}

View File

@@ -0,0 +1,58 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.util;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.junit.Test;
/**
* Unit tests for {@link RawJsonDeserializer}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class RawJsonDeserializerTest {
private String json = ""
+ "{"
+ " \"attribute\": {"
+ " \"firstField\":\"value\","
+ " \"secondField\": 1"
+ " }"
+ "}";
@Test
public void testDeserialization() throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
DeserializationTestClass object = mapper.readValue(json, DeserializationTestClass.class);
assertThat(object, notNullValue());
assertThat(object.getAttribute(), is("{\"firstField\":\"value\",\"secondField\":1}"));
}
private static class DeserializationTestClass {
@JsonDeserialize(using = RawJsonDeserializer.class)
private String attribute;
public String getAttribute() {
return attribute;
}
}
}

View File

@@ -0,0 +1,62 @@
[
{
"originalId": "oai:www.openstarts.units.it:123456789/99998",
"title": "Egypt, crossroad of translations and literary interweavings",
"topic": "ENRICH/MORE/PROJECT",
"trust": 1.0,
"message": {
"projects[0].acronym": "PAThs",
"projects[0].code": "687567",
"projects[0].funder": "EC",
"projects[0].fundingProgram": "H2020",
"projects[0].jurisdiction": "EU",
"projects[0].openaireId": "40|corda__h2020::6e32f5eb912688f2424c68b851483ea4",
"projects[0].title": "Tracking Papyrus and Parchment Paths"
}
},
{
"originalId": "oai:www.openstarts.units.it:123456789/99999",
"title": "Test Publication",
"topic": "ENRICH/MISSING/ABSTRACT",
"trust": 1.0,
"message": {
"abstracts[0]": "Missing Abstract"
}
},
{
"originalId": "oai:www.openstarts.units.it:123456789/99998",
"title": "Egypt, crossroad of translations and literary interweavings",
"topic": "ENRICH/MISSING/PID",
"trust": 1.0,
"message": {
"pids[0].type": "doi",
"pids[0].value": "10.13137/2282-572x/987"
}
},
{
"originalId": "oai:www.openstarts.units.it:123456789/99999",
"title": "Test Publication",
"topic": "ENRICH/MORE/PID",
"trust": 0.375,
"message": {
"pids[0].type": "doi",
"pids[0].value": "987654"
}
},
{
"originalId": "oai:www.openstarts.units.it:123456789/99999",
"title": "Test Publication",
"topic": "ENRICH/MISSING/PROJECT",
"trust": 1.0,
"message": {
"projects[0].acronym": "02.SNES missing project acronym",
"projects[0].code": "prjcode_snes",
"projects[0].funder": "02.SNES missing project funder",
"projects[0].fundingProgram": "02.SNES missing project fundingProgram",
"projects[0].jurisdiction": "02.SNES missing project jurisdiction",
"projects[0].title": "Project01"
}
}
]

View File

@@ -0,0 +1,20 @@
[
{
"originalId": "oai:www.openstarts.units.it:123456789/99998",
"title": "Egypt, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature",
"topic": "ENRICH/MORE/UNKNOWN",
"trust": 1.0
},
{
"originalId": "oai:www.openstarts.units.it:123456789/999991",
"title": "Test Publication 2",
"topic": "ENRICH/MISSING/ABSTRACT",
"trust": 1.0,
"message": {
"abstracts[0]": "Missing Abstract"
}
}
]

View File

@@ -0,0 +1,143 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG;
import java.io.IOException;
import java.sql.SQLException;
import java.util.UUID;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.model.hateoas.ItemResource;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.AuthorizeException;
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.service.QAEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.ControllerUtils;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* This RestController will take care to manipulate the related item eventually
* associated with a qa event
* "/api/integration/qualityassuranceevents/{qaeventid}/related"
*/
@RestController
@RequestMapping("/api/" + QAEventRest.CATEGORY + "/qualityassuranceevents"
+ REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + "/related")
public class QAEventRelatedRestController {
@Autowired
protected Utils utils;
@Autowired
private ConverterService converterService;
@Autowired
private ItemService itemService;
@Autowired
private QAEventService qaEventService;
/**
* This method associate an item to a qa event
*
* @param qaeventId The qa event id
* @param relatedItemUUID The uuid of the related item to associate with the qa event
* @return The related item
* @throws SQLException If something goes wrong
* @throws AuthorizeException If something goes wrong
*/
@PostMapping
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<RepresentationModel<?>> addRelatedItem(@PathVariable(name = "id") String qaeventId,
@RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException {
Context context = ContextUtil.obtainCurrentRequestContext();
QAEvent qaevent = qaEventService.findEventByEventId(qaeventId);
if (qaevent == null) {
throw new ResourceNotFoundException("No such qa event: " + qaeventId);
}
if (!qaEventService.isRelatedItemSupported(qaevent)) {
throw new UnprocessableEntityException("The given event does not supports a related item");
}
if (qaevent.getRelated() != null) {
throw new UnprocessableEntityException("The given event already has a related item");
}
Item relatedItem = itemService.find(context, relatedItemUUID);
if (relatedItem == null) {
throw new UnprocessableEntityException("The proposed related item was not found");
}
qaevent.setRelated(relatedItemUUID.toString());
qaEventService.store(context, qaevent);
ItemRest relatedItemRest = converterService.toRest(relatedItem, utils.obtainProjection());
ItemResource itemResource = converterService.toResource(relatedItemRest);
context.complete();
return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), itemResource);
}
/**
* This method remove the association to a related item from a qa event
*
* @param qaeventId The qa event id
* @return The related item
* @throws SQLException If something goes wrong
* @throws AuthorizeException If something goes wrong
*/
@DeleteMapping
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<RepresentationModel<?>> removeRelatedItem(@PathVariable(name = "id") String qaeventId)
throws SQLException, AuthorizeException, IOException {
Context context = ContextUtil.obtainCurrentRequestContext();
QAEvent qaevent = qaEventService.findEventByEventId(qaeventId);
if (qaevent == null) {
throw new ResourceNotFoundException("No such qa event: " + qaeventId);
}
if (!qaEventService.isRelatedItemSupported(qaevent)) {
throw new UnprocessableEntityException("The given event does not supports a related item");
}
if (qaevent.getRelated() != null) {
qaevent.setRelated(null);
qaEventService.store(context, qaevent);
context.complete();
}
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT);
}
}

View File

@@ -572,6 +572,37 @@ public class RestResourceController implements InitializingBean {
return uploadInternal(request, apiCategory, model, uuid, uploadfile);
}
/**
* Called in POST, multipart, upload to a specific rest resource the file passed as "file" request parameter
*
* Note that the regular expression in the request mapping accept a String as identifier;
*
* @param request
* the http request
* @param apiCategory
* the api category
* @param model
* the rest model that identify the REST resource collection
* @param id
* the id of the specific rest resource
* @param uploadfile
* the file to upload
* @return the created resource
* @throws HttpRequestMethodNotSupportedException
*/
@RequestMapping(method = RequestMethod.POST,
value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG,
headers = "content-type=multipart/form-data")
public <ID extends Serializable> ResponseEntity<RepresentationModel<?>> upload(HttpServletRequest request,
@PathVariable String apiCategory,
@PathVariable String model,
@PathVariable String id,
@RequestParam("file") MultipartFile
uploadfile)
throws HttpRequestMethodNotSupportedException {
return uploadInternal(request, apiCategory, model, id, uploadfile);
}
/**
* Internal upload method.
*
@@ -684,6 +715,28 @@ public class RestResourceController implements InitializingBean {
return patchInternal(request, apiCategory, model, id, jsonNode);
}
/**
* PATCH method, using operation on the resources following (JSON) Patch notation (https://tools.ietf
* .org/html/rfc6902)
*
* Note that the regular expression in the request mapping accept a UUID as identifier;
*
* @param request
* @param apiCategory
* @param model
* @param id
* @param jsonNode
* @return
* @throws HttpRequestMethodNotSupportedException
*/
@RequestMapping(method = RequestMethod.PATCH, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG)
public ResponseEntity<RepresentationModel<?>> patch(HttpServletRequest request, @PathVariable String apiCategory,
@PathVariable String model,
@PathVariable String id,
@RequestBody(required = true) JsonNode jsonNode) {
return patchInternal(request, apiCategory, model, id, jsonNode);
}
/**
* Internal patch method
*
@@ -711,9 +764,13 @@ public class RestResourceController implements InitializingBean {
log.error(e.getMessage(), e);
throw e;
}
if (modelObject != null) {
DSpaceResource result = converter.toResource(modelObject);
//TODO manage HTTPHeader
return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result);
} else {
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT);
}
}
@@ -1036,6 +1093,17 @@ public class RestResourceController implements InitializingBean {
return uriComponentsBuilder.encode().build().toString();
}
/**
* Method to delete an entity by ID
* Note that the regular expression in the request mapping accept a number as identifier;
*
* @param request
* @param apiCategory
* @param model
* @param id
* @return
* @throws HttpRequestMethodNotSupportedException
*/
@RequestMapping(method = RequestMethod.DELETE, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT)
public ResponseEntity<RepresentationModel<?>> delete(HttpServletRequest request, @PathVariable String apiCategory,
@PathVariable String model, @PathVariable Integer id)
@@ -1050,6 +1118,13 @@ public class RestResourceController implements InitializingBean {
return deleteInternal(apiCategory, model, uuid);
}
@RequestMapping(method = RequestMethod.DELETE, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG)
public ResponseEntity<RepresentationModel<?>> delete(HttpServletRequest request, @PathVariable String apiCategory,
@PathVariable String model, @PathVariable String id)
throws HttpRequestMethodNotSupportedException {
return deleteInternal(apiCategory, model, id);
}
/**
* Internal method to delete resource.
*

View 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.app.rest.authorization.impl;
import java.sql.SQLException;
import org.dspace.app.rest.authorization.AuthorizationFeature;
import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* The QA Event feature. It can be used to verify if Quality Assurance can be seen.
*
* Authorization is granted if the current user has READ permissions on the given bitstream.
*/
@Component
@AuthorizationFeatureDocumentation(name = QAAuthorizationFeature.NAME,
description = "It can be used to verify if the user can manage Quality Assurance events")
public class QAAuthorizationFeature implements AuthorizationFeature {
public final static String NAME = "canSeeQA";
@Autowired
private ConfigurationService configurationService;
@Override
public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException {
return configurationService.getBooleanProperty("qaevents.enabled", false);
}
@Override
public String[] getSupportedTypes() {
return new String[]{
SiteRest.CATEGORY + "." + SiteRest.NAME
};
}
}

View File

@@ -0,0 +1,115 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import java.text.DecimalFormat;
import javax.annotation.PostConstruct;
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 org.dspace.app.rest.model.OpenaireQAEventMessageRest;
import org.dspace.app.rest.model.QAEventMessageRest;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.content.QAEvent;
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
import org.dspace.qaevent.service.dto.QAMessageDTO;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link DSpaceConverter} that converts {@link QAEvent} to
* {@link QAEventRest}.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component
public class QAEventConverter implements DSpaceConverter<QAEvent, QAEventRest> {
private static final String OPENAIRE_PID_HREF_PREFIX_PROPERTY = "qaevents.openaire.pid-href-prefix.";
@Autowired
private ConfigurationService configurationService;
private ObjectMapper jsonMapper;
@PostConstruct
public void setup() {
jsonMapper = new JsonMapper();
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public QAEventRest convert(QAEvent modelObject, Projection projection) {
QAEventRest rest = new QAEventRest();
rest.setId(modelObject.getEventId());
try {
rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(),
modelObject.getMessageDtoClass())));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
rest.setOriginalId(modelObject.getOriginalId());
rest.setProjection(projection);
rest.setTitle(modelObject.getTitle());
rest.setTopic(modelObject.getTopic());
rest.setEventDate(modelObject.getLastUpdate());
rest.setTrust(new DecimalFormat("0.000").format(modelObject.getTrust()));
// right now only the pending status can be found in persisted qa events
rest.setStatus(modelObject.getStatus());
return rest;
}
private QAEventMessageRest convertMessage(QAMessageDTO dto) {
if (dto instanceof OpenaireMessageDTO) {
return convertOpenaireMessage(dto);
}
throw new IllegalArgumentException("Unknown message type: " + dto.getClass());
}
private QAEventMessageRest convertOpenaireMessage(QAMessageDTO dto) {
OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto;
OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest();
message.setAbstractValue(openaireDto.getAbstracts());
message.setOpenaireId(openaireDto.getOpenaireId());
message.setAcronym(openaireDto.getAcronym());
message.setCode(openaireDto.getCode());
message.setFunder(openaireDto.getFunder());
message.setFundingProgram(openaireDto.getFundingProgram());
message.setJurisdiction(openaireDto.getJurisdiction());
message.setTitle(openaireDto.getTitle());
message.setType(openaireDto.getType());
message.setValue(openaireDto.getValue());
message.setPidHref(calculateOpenairePidHref(openaireDto.getType(), openaireDto.getValue()));
return message;
}
private String calculateOpenairePidHref(String type, String value) {
if (type == null) {
return null;
}
String hrefType = type.toLowerCase();
if (!configurationService.hasProperty(OPENAIRE_PID_HREF_PREFIX_PROPERTY + hrefType)) {
return null;
}
String hrefPrefix = configurationService.getProperty(OPENAIRE_PID_HREF_PREFIX_PROPERTY + hrefType, "");
return hrefPrefix + value;
}
@Override
public Class<QAEvent> getModelClass() {
return QAEvent.class;
}
}

View File

@@ -0,0 +1,40 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.QASourceRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.qaevent.QASource;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link DSpaceConverter} that converts {@link QASource} to
* {@link QASourceRest}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
@Component
public class QASourceConverter implements DSpaceConverter<QASource, QASourceRest> {
@Override
public Class<QASource> getModelClass() {
return QASource.class;
}
@Override
public QASourceRest convert(QASource modelObject, Projection projection) {
QASourceRest rest = new QASourceRest();
rest.setProjection(projection);
rest.setId(modelObject.getName());
rest.setLastEvent(modelObject.getLastEvent());
rest.setTotalEvents(modelObject.getTotalEvents());
return rest;
}
}

View File

@@ -0,0 +1,41 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.QATopicRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.qaevent.QATopic;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link DSpaceConverter} that converts {@link QATopic} to
* {@link QATopicRest}.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component
public class QATopicConverter implements DSpaceConverter<QATopic, QATopicRest> {
@Override
public Class<QATopic> getModelClass() {
return QATopic.class;
}
@Override
public QATopicRest convert(QATopic modelObject, Projection projection) {
QATopicRest rest = new QATopicRest();
rest.setProjection(projection);
rest.setId(modelObject.getKey().replace("/", "!"));
rest.setName(modelObject.getKey());
rest.setLastEvent(modelObject.getLastEvent());
rest.setTotalEvents(modelObject.getTotalEvents());
return rest;
}
}

View File

@@ -0,0 +1,115 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Implementation of {@link QAEventMessageRest} related to OPENAIRE events.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class OpenaireQAEventMessageRest implements QAEventMessageRest {
// pids
private String type;
private String value;
private String pidHref;
// abstract
@JsonProperty(value = "abstract")
private String abstractValue;
// project
private String openaireId;
private String acronym;
private String code;
private String funder;
private String fundingProgram;
private String jurisdiction;
private String title;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getAbstractValue() {
return abstractValue;
}
public void setAbstractValue(String abstractValue) {
this.abstractValue = abstractValue;
}
public String getOpenaireId() {
return openaireId;
}
public void setOpenaireId(String openaireId) {
this.openaireId = openaireId;
}
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 getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPidHref() {
return pidHref;
}
public void setPidHref(String pidHref) {
this.pidHref = pidHref;
}
}

View File

@@ -0,0 +1,18 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
/**
* Interface for classes that model a message with the details of a QA event.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public interface QAEventMessageRest {
}

View File

@@ -0,0 +1,136 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.Date;
import org.dspace.app.rest.RestResourceController;
/**
* QA event Rest object.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@LinksRest(
links = {
@LinkRest(name = "topic", method = "getTopic"),
@LinkRest(name = "target", method = "getTarget"),
@LinkRest(name = "related", method = "getRelated")
})
public class QAEventRest extends BaseObjectRest<String> {
private static final long serialVersionUID = -5001130073350654793L;
public static final String NAME = "qualityassuranceevent";
public static final String CATEGORY = RestAddressableModel.INTEGRATION;
public static final String TOPIC = "topic";
public static final String TARGET = "target";
public static final String RELATED = "related";
private String source;
private String originalId;
private String title;
private String topic;
private String trust;
private Date eventDate;
private QAEventMessageRest message;
private String status;
@Override
public String getType() {
return NAME;
}
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return RestResourceController.class;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
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 String getTrust() {
return trust;
}
public void setTrust(String trust) {
this.trust = trust;
}
public Date getEventDate() {
return eventDate;
}
public void setEventDate(Date eventDate) {
this.eventDate = eventDate;
}
public QAEventMessageRest getMessage() {
return message;
}
public void setMessage(QAEventMessageRest message) {
this.message = message;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
/**
* @return the source
*/
public String getSource() {
return source;
}
/**
* @param source the source to set
*/
public void setSource(String source) {
this.source = source;
}
}

View File

@@ -0,0 +1,69 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.Date;
import org.dspace.app.rest.RestResourceController;
/**
* REST Representation of a quality assurance broker source
*
* @author Luca Giamminonni (luca.giamminonni at 4Science)
*
*/
public class QASourceRest extends BaseObjectRest<String> {
private static final long serialVersionUID = -7455358581579629244L;
public static final String NAME = "qualityassurancesource";
public static final String CATEGORY = RestAddressableModel.INTEGRATION;
private String id;
private Date lastEvent;
private long totalEvents;
@Override
public String getType() {
return NAME;
}
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return RestResourceController.class;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getLastEvent() {
return lastEvent;
}
public void setLastEvent(Date lastEvent) {
this.lastEvent = lastEvent;
}
public long getTotalEvents() {
return totalEvents;
}
public void setTotalEvents(long totalEvents) {
this.totalEvents = totalEvents;
}
}

View File

@@ -0,0 +1,78 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.Date;
import org.dspace.app.rest.RestResourceController;
/**
* REST Representation of a quality assurance broker topic
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
public class QATopicRest extends BaseObjectRest<String> {
private static final long serialVersionUID = -7455358581579629244L;
public static final String NAME = "qualityassurancetopic";
public static final String CATEGORY = RestAddressableModel.INTEGRATION;
private String id;
private String name;
private Date lastEvent;
private long totalEvents;
@Override
public String getType() {
return NAME;
}
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return RestResourceController.class;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getLastEvent() {
return lastEvent;
}
public void setLastEvent(Date lastEvent) {
this.lastEvent = lastEvent;
}
public long getTotalEvents() {
return totalEvents;
}
public void setTotalEvents(long totalEvents) {
this.totalEvents = totalEvents;
}
}

View File

@@ -0,0 +1,27 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* QA event Rest resource.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@RelNameDSpaceResource(QAEventRest.NAME)
public class QAEventResource extends DSpaceResource<QAEventRest> {
public QAEventResource(QAEventRest data, Utils utils) {
super(data, utils);
}
}

View File

@@ -0,0 +1,27 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.QASourceRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* QA source Rest resource.
*
* @author Luca Giamminonni (luca.giamminonni at 4Science)
*
*/
@RelNameDSpaceResource(QASourceRest.NAME)
public class QASourceResource extends DSpaceResource<QASourceRest> {
public QASourceResource(QASourceRest data, Utils utils) {
super(data, utils);
}
}

View File

@@ -0,0 +1,27 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.QATopicRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* QA topic Rest resource.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@RelNameDSpaceResource(QATopicRest.NAME)
public class QATopicResource extends DSpaceResource<QATopicRest> {
public QATopicResource(QATopicRest data, Utils utils) {
super(data, utils);
}
}

View File

@@ -0,0 +1,77 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.projection.Projection;
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.service.QAEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "related" subresource of a qa event.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.RELATED)
public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository {
@Autowired
private QAEventService qaEventService;
@Autowired
private ItemService itemService;
/**
* Returns the item related to the qa event with the given id. This is another
* item that should be linked to the target item as part of the correction
*
* @param request the http servlet request
* @param id the qa event id
* @param pageable the optional pageable
* @param projection the projection object
* @return the item rest representation of the secondary item related to qa event
*/
@PreAuthorize("hasAuthority('ADMIN')")
public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable,
Projection projection) {
Context context = obtainContext();
QAEvent qaEvent = qaEventService.findEventByEventId(id);
if (qaEvent == null) {
throw new ResourceNotFoundException("No qa event with ID: " + id);
}
if (qaEvent.getRelated() == null) {
return null;
}
UUID itemUuid = UUID.fromString(qaEvent.getRelated());
Item item;
try {
item = itemService.find(context, itemUuid);
if (item == null) {
throw new ResourceNotFoundException("No related item found with id : " + id);
}
return converter.toRest(item, projection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,134 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.model.patch.Patch;
import org.dspace.app.rest.repository.patch.ResourcePatch;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Item;
import org.dspace.content.QAEvent;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.qaevent.dao.QAEventsDAO;
import org.dspace.qaevent.service.QAEventService;
import org.dspace.util.UUIDUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Rest repository that handle QA events.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME)
public class QAEventRestRepository extends DSpaceRestRepository<QAEventRest, String> {
final static String ORDER_FIELD = "trust";
@Autowired
private QAEventService qaEventService;
@Autowired
private QAEventsDAO qaEventDao;
@Autowired
private ItemService itemService;
@Autowired
private ResourcePatch<QAEvent> resourcePatch;
@Override
@PreAuthorize("hasAuthority('ADMIN')")
public QAEventRest findOne(Context context, String id) {
QAEvent qaEvent = qaEventService.findEventByEventId(id);
if (qaEvent == null) {
// check if this request is part of a patch flow
qaEvent = (QAEvent) requestService.getCurrentRequest().getAttribute("patchedNotificationEvent");
if (qaEvent != null && qaEvent.getEventId().contentEquals(id)) {
return converter.toRest(qaEvent, utils.obtainProjection());
} else {
return null;
}
}
return converter.toRest(qaEvent, utils.obtainProjection());
}
@SearchRestMethod(name = "findByTopic")
@PreAuthorize("hasAuthority('ADMIN')")
public Page<QAEventRest> findByTopic(Context context, @Parameter(value = "topic", required = true) String topic,
Pageable pageable) {
List<QAEvent> qaEvents = null;
long count = 0L;
boolean ascending = false;
if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) {
ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC;
}
qaEvents = qaEventService.findEventsByTopicAndPage(topic,
pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending);
count = qaEventService.countEventsByTopic(topic);
if (qaEvents == null) {
return null;
}
return converter.toRestPage(qaEvents, pageable, count, utils.obtainProjection());
}
@Override
@PreAuthorize("hasAuthority('ADMIN')")
protected void delete(Context context, String eventId) throws AuthorizeException {
Item item = findTargetItem(context, eventId);
EPerson eperson = context.getCurrentUser();
qaEventService.deleteEventByEventId(eventId);
qaEventDao.storeEvent(context, eventId, eperson, item);
}
@Override
public Page<QAEventRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll");
}
@Override
@PreAuthorize("hasAuthority('ADMIN')")
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model,
String id, Patch patch) throws SQLException, AuthorizeException {
QAEvent qaEvent = qaEventService.findEventByEventId(id);
resourcePatch.patch(context, qaEvent, patch.getOperations());
}
private Item findTargetItem(Context context, String eventId) {
QAEvent qaEvent = qaEventService.findEventByEventId(eventId);
if (qaEvent == null) {
return null;
}
try {
return itemService.find(context, UUIDUtils.fromString(qaEvent.getTarget()));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<QAEventRest> getDomainClass() {
return QAEventRest.class;
}
}

View File

@@ -0,0 +1,73 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.projection.Projection;
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.service.QAEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "target" subresource of a qa event.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.TARGET)
public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository {
@Autowired
private QAEventService qaEventService;
@Autowired
private ItemService itemService;
/**
* Returns the item target of the qa event with the given id.
*
* @param request the http servlet request
* @param id the qa event id
* @param pageable the optional pageable
* @param projection the projection object
* @return the item rest representation of the qa event target
*/
@PreAuthorize("hasAuthority('ADMIN')")
public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable,
Projection projection) {
Context context = obtainContext();
QAEvent qaEvent = qaEventService.findEventByEventId(id);
if (qaEvent == null) {
throw new ResourceNotFoundException("No qa event with ID: " + id);
}
UUID itemUuid = UUID.fromString(qaEvent.getTarget());
Item item;
try {
item = itemService.find(context, itemUuid);
if (item == null) {
throw new ResourceNotFoundException("No target item found with id : " + id);
}
return converter.toRest(item, projection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,61 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.model.QATopicRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.content.QAEvent;
import org.dspace.core.Context;
import org.dspace.qaevent.QATopic;
import org.dspace.qaevent.service.QAEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "topic" subresource of a qa event.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.TOPIC)
public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository {
@Autowired
private QAEventService qaEventService;
/**
* Returns the topic of the qa event with the given id.
*
* @param request the http servlet request
* @param id the qa event id
* @param pageable the optional pageable
* @param projection the projection object
* @return the qa topic rest representation
*/
@PreAuthorize("hasAuthority('ADMIN')")
public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable,
Projection projection) {
Context context = obtainContext();
QAEvent qaEvent = qaEventService.findEventByEventId(id);
if (qaEvent == null) {
throw new ResourceNotFoundException("No qa event with ID: " + id);
}
QATopic topic = qaEventService.findTopicByTopicId(qaEvent.getTopic().replace("/", "!"));
if (topic == null) {
throw new ResourceNotFoundException("No topic found with id : " + id);
}
return converter.toRest(topic, projection);
}
}

View File

@@ -0,0 +1,58 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.util.List;
import org.dspace.app.rest.model.QASourceRest;
import org.dspace.core.Context;
import org.dspace.qaevent.QASource;
import org.dspace.qaevent.service.QAEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Rest repository that handle QA sources.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
@Component(QASourceRest.CATEGORY + "." + QASourceRest.NAME)
public class QASourceRestRepository extends DSpaceRestRepository<QASourceRest, String> {
@Autowired
private QAEventService qaEventService;
@Override
@PreAuthorize("hasAuthority('ADMIN')")
public QASourceRest findOne(Context context, String id) {
QASource qaSource = qaEventService.findSource(id);
if (qaSource == null) {
return null;
}
return converter.toRest(qaSource, utils.obtainProjection());
}
@Override
@PreAuthorize("hasAuthority('ADMIN')")
public Page<QASourceRest> findAll(Context context, Pageable pageable) {
List<QASource> qaSources = qaEventService.findAllSources(pageable.getOffset(), pageable.getPageSize());
long count = qaEventService.countSources();
return converter.toRestPage(qaSources, pageable, count, utils.obtainProjection());
}
@Override
public Class<QASourceRest> getDomainClass() {
return QASourceRest.class;
}
}

View File

@@ -0,0 +1,88 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.util.List;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.model.QATopicRest;
import org.dspace.core.Context;
import org.dspace.qaevent.QATopic;
import org.dspace.qaevent.service.QAEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Rest repository that handle QA topics.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component(QATopicRest.CATEGORY + "." + QATopicRest.NAME)
public class QATopicRestRepository extends DSpaceRestRepository<QATopicRest, String> {
final static String ORDER_FIELD = "topic";
@Autowired
private QAEventService qaEventService;
@Override
@PreAuthorize("hasAuthority('ADMIN')")
public QATopicRest findOne(Context context, String id) {
QATopic topic = qaEventService.findTopicByTopicId(id);
if (topic == null) {
return null;
}
return converter.toRest(topic, utils.obtainProjection());
}
@Override
@PreAuthorize("hasAuthority('ADMIN')")
public Page<QATopicRest> findAll(Context context, Pageable pageable) {
boolean ascending = false;
if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) {
ascending = pageable.getSort()
.getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC;
}
List<QATopic> topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize(),
ORDER_FIELD, ascending);
long count = qaEventService.countTopics();
if (topics == null) {
return null;
}
return converter.toRestPage(topics, pageable, count, utils.obtainProjection());
}
@SearchRestMethod(name = "bySource")
@PreAuthorize("hasAuthority('ADMIN')")
public Page<QATopicRest> findBySource(Context context,
@Parameter(value = "source", required = true) String source, Pageable pageable) {
boolean ascending = false;
if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) {
ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC;
}
List<QATopic> topics = qaEventService.findAllTopicsBySource(source,
pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending);
long count = qaEventService.countTopicsBySource(source);
if (topics == null) {
return null;
}
return converter.toRestPage(topics, pageable, count, utils.obtainProjection());
}
@Override
public Class<QATopicRest> getDomainClass() {
return QATopicRest.class;
}
}

View File

@@ -0,0 +1,60 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository.patch.operation;
import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.content.QAEvent;
import org.dspace.core.Context;
import org.dspace.qaevent.service.QAEventActionService;
import org.dspace.services.RequestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Replace operation related to the {@link QAEvent} status.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
@Component
public class QAEventStatusReplaceOperation extends PatchOperation<QAEvent> {
@Autowired
private RequestService requestService;
@Autowired
private QAEventActionService qaEventActionService;
@Override
public QAEvent perform(Context context, QAEvent qaevent, Operation operation) throws SQLException {
String value = (String) operation.getValue();
if (StringUtils.equalsIgnoreCase(value, QAEvent.ACCEPTED)) {
qaEventActionService.accept(context, qaevent);
} else if (StringUtils.equalsIgnoreCase(value, QAEvent.REJECTED)) {
qaEventActionService.reject(context, qaevent);
} else if (StringUtils.equalsIgnoreCase(value, QAEvent.DISCARDED)) {
qaEventActionService.discard(context, qaevent);
} else {
throw new IllegalArgumentException(
"The received operation is not valid: " + operation.getPath() + " - " + value);
}
qaevent.setStatus(value.toUpperCase());
// HACK, we need to store the temporary object in the request so that a subsequent find would get it
requestService.getCurrentRequest().setAttribute("patchedNotificationEvent", qaevent);
return qaevent;
}
@Override
public boolean supports(Object objectToMatch, Operation operation) {
return StringUtils.equals(operation.getOp(), "replace") && objectToMatch instanceof QAEvent && StringUtils
.containsAny(operation.getValue().toString().toLowerCase(), QAEvent.ACCEPTED, QAEvent.DISCARDED,
QAEvent.REJECTED);
}
}

View File

@@ -33,7 +33,7 @@ public class RegexUtils {
*/
public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\d+$)" +
"(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)"
+ "[\\w+\\-\\.:]+$+}";
+ "[\\w+\\-\\.:!]+$+}";
/**
* Regular expression in the request mapping to accept number as identifier

View File

@@ -135,28 +135,28 @@
<heading>fake.workflow.readonly</heading> <processing-class>org.dspace.submit.step.SampleStep</processing-class>
<type>sample</type> <scope visibility="read-only">workflow</scope> </step-definition> -->
<!-- OpenAIRE submission steps/forms -->
<step-definition id="openAIREProjectForm" mandatory="true">
<!-- Openaire submission steps/forms -->
<step-definition id="openaireProjectForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREPersonForm" mandatory="true">
<step-definition id="openairePersonForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREOrganizationForm" mandatory="true">
<step-definition id="openaireOrganizationForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREPublicationPageoneForm" mandatory="true">
<step-definition id="openairePublicationPageoneForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREPublicationPagetwoForm" mandatory="true">
<step-definition id="openairePublicationPagetwoForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
@@ -259,13 +259,13 @@
<step id="license"/>
</submission-process>
<!-- OpenAIRE submission processes -->
<submission-process name="openAIREPublicationSubmission">
<!-- Openaire submission processes -->
<submission-process name="openairePublicationSubmission">
<step id="collection"/>
<!--Step will be to Describe the item. -->
<step id="openAIREPublicationPageoneForm"/>
<step id="openAIREPublicationPagetwoForm"/>
<step id="openairePublicationPageoneForm"/>
<step id="openairePublicationPagetwoForm"/>
<!--Step will be to Upload the item -->
<!-- step id="upload-with-embargo"/-->
@@ -274,17 +274,17 @@
<!--Step will be to Sign off on the License -->
<step id="license"/>
</submission-process>
<submission-process name="openAIREPersonSubmission">
<submission-process name="openairePersonSubmission">
<step id="collection"/>
<step id="openAIREPersonForm"/>
<step id="openairePersonForm"/>
</submission-process>
<submission-process name="openAIREProjectSubmission">
<submission-process name="openaireProjectSubmission">
<step id="collection"/>
<step id="openAIREProjectForm"/>
<step id="openaireProjectForm"/>
</submission-process>
<submission-process name="openAIREOrganizationSubmission">
<submission-process name="openaireOrganizationSubmission">
<step id="collection"/>
<step id="openAIREOrganizationForm"/>
<step id="openaireOrganizationForm"/>
</submission-process>
</submission-definitions>

View File

@@ -4,8 +4,8 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-lazy-init="true">
<bean id="openAIRERestConnector"
class="org.dspace.external.OpenAIRERestConnector">
<bean id="openaireRestConnector"
class="org.dspace.external.OpenaireRestConnector">
<constructor-arg
value="${openaire.api.url:https://api.openaire.eu}" />
<property name="tokenEnabled"
@@ -18,10 +18,10 @@
value="${openaire.token.clientSecret}" />
</bean>
<bean
class="org.dspace.external.provider.impl.MockOpenAIREFundingDataProvider"
class="org.dspace.external.provider.impl.MockOpenaireFundingDataProvider"
init-method="init">
<property name="sourceIdentifier" value="openAIREFunding" />
<property name="connector" ref="openAIRERestConnector" />
<property name="sourceIdentifier" value="openaireFunding" />
<property name="connector" ref="openaireRestConnector" />
<property name="supportedEntityTypes">
<list>
<value>Project</value>

View File

@@ -86,8 +86,8 @@
<!-- search for an entity that can be a Person or an OrgUnit -->
<entry key="personOrOrgunit" value-ref="personOrOrgunit"/>
<!-- OpenAIRE4 guidelines - search for an OrgUnit that have a specific dc.type=FundingOrganization -->
<entry key="openAIREFundingAgency" value-ref="openAIREFundingAgency"/>
<!-- Openaire4 guidelines - search for an OrgUnit that have a specific dc.type=FundingOrganization -->
<entry key="openaireFundingAgency" value-ref="openaireFundingAgency"/>
<entry key="eperson_claims" value-ref="eperson_claims"/>

View File

@@ -1057,8 +1057,8 @@ public class DiscoveryVersioningIT extends AbstractControllerIntegrationTest {
}
@Test
public void test_discoveryXml_openAIREFundingAgency_expectLatestVersionsOnly() throws Exception {
final String configuration = "openAIREFundingAgency";
public void test_discoveryXml_openaireFundingAgency_expectLatestVersionsOnly() throws Exception {
final String configuration = "openaireFundingAgency";
Collection collection = createCollection("OrgUnit");

View File

@@ -51,7 +51,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati
ExternalSourceMatcher.matchExternalSource(
"pubmed", "pubmed", false),
ExternalSourceMatcher.matchExternalSource(
"openAIREFunding", "openAIREFunding", false)
"openaireFunding", "openaireFunding", false)
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(10)));
}

View File

@@ -19,7 +19,7 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.hamcrest.Matchers;
import org.junit.Test;
public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrationTest {
public class OpenaireFundingExternalSourcesIT extends AbstractControllerIntegrationTest {
/**
* Test openaire funding external source
@@ -27,10 +27,10 @@ public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat
* @throws Exception
*/
@Test
public void findOneOpenAIREFundingExternalSourceTest() throws Exception {
public void findOneOpenaireFundingExternalSourceTest() throws Exception {
getClient().perform(get("/api/integration/externalsources")).andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItem(
ExternalSourceMatcher.matchExternalSource("openAIREFunding", "openAIREFunding", false))));
ExternalSourceMatcher.matchExternalSource("openaireFunding", "openaireFunding", false))));
}
/**
@@ -39,9 +39,9 @@ public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat
* @throws Exception
*/
@Test
public void findOneOpenAIREFundingExternalSourceEntriesEmptyWithQueryTest() throws Exception {
public void findOneOpenaireFundingExternalSourceEntriesEmptyWithQueryTest() throws Exception {
getClient().perform(get("/api/integration/externalsources/openAIREFunding/entries").param("query", "empty"))
getClient().perform(get("/api/integration/externalsources/openaireFunding/entries").param("query", "empty"))
.andExpect(status().isOk()).andExpect(jsonPath("$.page.number", is(0)));
}
@@ -52,11 +52,11 @@ public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat
* @throws Exception
*/
@Test
public void findOneOpenAIREFundingExternalSourceEntriesWithQueryMultipleKeywordsTest() throws Exception {
public void findOneOpenaireFundingExternalSourceEntriesWithQueryMultipleKeywordsTest() throws Exception {
getClient()
.perform(
get("/api/integration/externalsources/openAIREFunding/entries").param("query", "empty+results"))
get("/api/integration/externalsources/openaireFunding/entries").param("query", "empty+results"))
.andExpect(status().isOk()).andExpect(jsonPath("$.page.number", is(0)));
}
@@ -66,14 +66,14 @@ public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat
* @throws Exception
*/
@Test
public void findOneOpenAIREFundingExternalSourceEntriesWithQueryTest() throws Exception {
getClient().perform(get("/api/integration/externalsources/openAIREFunding/entries").param("query", "mushroom"))
public void findOneOpenaireFundingExternalSourceEntriesWithQueryTest() throws Exception {
getClient().perform(get("/api/integration/externalsources/openaireFunding/entries").param("query", "mushroom"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.externalSourceEntries",
Matchers.hasItem(ExternalSourceEntryMatcher.matchExternalSourceEntry(
"aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L05XTy8rLzIzMDAxNDc3MjgvTkw=",
"Master switches of initiation of mushroom formation",
"Master switches of initiation of mushroom formation", "openAIREFunding"))));
"Master switches of initiation of mushroom formation", "openaireFunding"))));
}
@@ -83,19 +83,19 @@ public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat
* @throws Exception
*/
@Test
public void findOneOpenAIREFundingExternalSourceEntryValueTest() throws Exception {
public void findOneOpenaireFundingExternalSourceEntryValueTest() throws Exception {
// "info:eu-repo/grantAgreement/mock/mock/mock/mock" base64 encoded
String projectID = "aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L0ZDVC81ODc2LVBQQ0RUSS8xMTAwNjIvUFQ=";
String projectName = "Portuguese Wild Mushrooms: Chemical characterization and functional study"
+ " of antiproliferative and proapoptotic properties in cancer cell lines";
getClient().perform(get("/api/integration/externalsources/openAIREFunding/entryValues/" + projectID))
getClient().perform(get("/api/integration/externalsources/openaireFunding/entryValues/" + projectID))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
Matchers.allOf(hasJsonPath("$.id", is(projectID)), hasJsonPath("$.display", is(projectName)),
hasJsonPath("$.value", is(projectName)),
hasJsonPath("$.externalSource", is("openAIREFunding")),
hasJsonPath("$.externalSource", is("openaireFunding")),
hasJsonPath("$.type", is("externalSourceEntry")))));
}

View File

@@ -0,0 +1,801 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath;
import static org.dspace.app.rest.matcher.QAEventMatcher.matchQAEventEntry;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.MediaType;
import org.dspace.app.rest.matcher.ItemMatcher;
import org.dspace.app.rest.matcher.QAEventMatcher;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.ReplaceOperation;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EntityTypeBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.QAEventBuilder;
import org.dspace.builder.RelationshipTypeBuilder;
import org.dspace.content.Collection;
import org.dspace.content.EntityType;
import org.dspace.content.Item;
import org.dspace.content.QAEvent;
import org.dspace.content.QAEventProcessed;
import org.dspace.qaevent.dao.QAEventsDAO;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link QAEventRestRepository}.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private QAEventsDAO qaEventsDao;
@Test
public void findAllNotImplementedTest() throws Exception {
String adminToken = getAuthToken(admin.getEmail(), password);
getClient(adminToken).perform(get("/api/integration/qualityassuranceevents"))
.andExpect(status().isMethodNotAllowed());
String epersonToken = getAuthToken(admin.getEmail(), password);
getClient(epersonToken).perform(get("/api/integration/qualityassuranceevents"))
.andExpect(status().isMethodNotAllowed());
getClient().perform(get("/api/integration/qualityassuranceevents")).andExpect(status().isMethodNotAllowed());
}
@Test
public void findOneTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(event1)));
getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event4.getEventId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(event4)));
}
@Test
public void findOneWithProjectionTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event5 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5")
.withTopic("ENRICH/MISSING/PROJECT")
.withMessage(
"{\"projects[0].acronym\":\"PAThs\","
+ "\"projects[0].code\":\"687567\","
+ "\"projects[0].funder\":\"EC\","
+ "\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: "
+ "An Archaeological Atlas of Coptic Literature."
+ "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, "
+ "Dissemination and Storage\"}")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event1)));
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event5.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event5)));
}
@Test
public void findOneUnauthorizedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()))
.andExpect(status().isUnauthorized());
}
@Test
public void findOneForbiddenTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()))
.andExpect(status().isForbidden());
}
@Test
public void findByTopicTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1),
QAEventMatcher.matchQAEventEntry(event2))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2)));
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic",
"ENRICH!MISSING!ABSTRACT"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event4))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1)));
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "not-existing"))
.andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
@Test
public void findByTopicPaginatedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"pmc\",\"pids[0].value\":\"2144303\"}").build();
QAEvent event5 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144304\"}").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")
.param("size", "2"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(
QAEventMatcher.matchQAEventEntry(event1),
QAEventMatcher.matchQAEventEntry(event2))))
.andExpect(jsonPath("$._links.self.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.next.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.first.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.prev.href").doesNotExist())
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalPages", is(3)))
.andExpect(jsonPath("$.page.totalElements", is(5)));
getClient(authToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")
.param("size", "2").param("page", "1"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(
QAEventMatcher.matchQAEventEntry(event3),
QAEventMatcher.matchQAEventEntry(event4))))
.andExpect(jsonPath("$._links.self.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.next.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.first.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.prev.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalPages", is(3)))
.andExpect(jsonPath("$.page.totalElements", is(5)));
getClient(authToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")
.param("size", "2").param("page", "2"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(
QAEventMatcher.matchQAEventEntry(event5))))
.andExpect(jsonPath("$._links.self.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.next.href").doesNotExist())
.andExpect(jsonPath("$._links.last.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.first.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.prev.href",
Matchers.allOf(
Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"),
Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"),
Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalPages", is(3)))
.andExpect(jsonPath("$.page.totalElements", is(5)));
}
@Test
public void findByTopicUnauthorizedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build();
context.restoreAuthSystemState();
getClient()
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID"))
.andExpect(status().isUnauthorized());
}
@Test
public void findByTopicForbiddenTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build();
context.restoreAuthSystemState();
String epersonToken = getAuthToken(eperson.getEmail(), password);
getClient(epersonToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID"))
.andExpect(status().isForbidden());
}
@Test
public void findByTopicBadRequestTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build();
context.restoreAuthSystemState();
String adminToken = getAuthToken(admin.getEmail(), password);
getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic"))
.andExpect(status().isBadRequest());
}
@Test
public void recordDecisionTest() throws Exception {
context.turnOffAuthorisationSystem();
EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build();
EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build();
RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, project, "isProjectOfPublication",
"isPublicationOfProject", 0, null, 0,
null).withCopyToRight(true).build();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withEntityType("Publication")
.withName("Collection 1").build();
Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection Fundings")
.withEntityType("Project").build();
Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths")
.build();
QAEvent eventProjectBound = QAEventBuilder.createTarget(context, col1, "Science and Freedom with project")
.withTopic("ENRICH/MISSING/PROJECT")
.withMessage(
"{\"projects[0].acronym\":\"PAThs\","
+ "\"projects[0].code\":\"687567\","
+ "\"projects[0].funder\":\"EC\","
+ "\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: "
+ "An Archaeological Atlas of Coptic Literature."
+ "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, "
+ "Dissemination and Storage\"}")
.withRelatedItem(funding.getID().toString())
.build();
QAEvent eventProjectNoBound = QAEventBuilder
.createTarget(context, col1, "Science and Freedom with unrelated project")
.withTopic("ENRICH/MISSING/PROJECT")
.withMessage(
"{\"projects[0].acronym\":\"NEW\","
+ "\"projects[0].code\":\"123456\","
+ "\"projects[0].funder\":\"EC\","
+ "\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"newProjectID\","
+ "\"projects[0].title\":\"A new project\"}")
.build();
QAEvent eventMissingPID1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent eventMissingPID2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEvent eventMissingUnknownPID = QAEventBuilder.createTarget(context, col1, "Science and Freedom URN PID")
.withTopic("ENRICH/MISSING/PID")
.withMessage(
"{\"pids[0].type\":\"urn\",\"pids[0].value\":\"http://thesis2.sba.units.it/store/handle/item/12937\"}")
.build();
QAEvent eventMorePID = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}").build();
QAEvent eventAbstract = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"An abstract to add...\"}").build();
QAEvent eventAbstractToDiscard = QAEventBuilder.createTarget(context, col1, "Science and Freedom 7")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}").build();
context.restoreAuthSystemState();
// prepare the different patches for our decisions
List<Operation> acceptOp = new ArrayList<Operation>();
acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED));
List<Operation> acceptOpUppercase = new ArrayList<Operation>();
acceptOpUppercase.add(new ReplaceOperation("/status", QAEvent.ACCEPTED));
List<Operation> discardOp = new ArrayList<Operation>();
discardOp.add(new ReplaceOperation("/status", QAEvent.DISCARDED));
List<Operation> rejectOp = new ArrayList<Operation>();
rejectOp.add(new ReplaceOperation("/status", QAEvent.REJECTED));
String patchAccept = getPatchContent(acceptOp);
String patchAcceptUppercase = getPatchContent(acceptOpUppercase);
String patchDiscard = getPatchContent(discardOp);
String patchReject = getPatchContent(rejectOp);
String authToken = getAuthToken(admin.getEmail(), password);
// accept pid1, unknownPID, morePID, the two projects and abstract
eventMissingPID1.setStatus(QAEvent.ACCEPTED);
eventMorePID.setStatus(QAEvent.ACCEPTED);
eventMissingUnknownPID.setStatus(QAEvent.ACCEPTED);
eventMissingUnknownPID.setStatus(QAEvent.ACCEPTED);
eventProjectBound.setStatus(QAEvent.ACCEPTED);
eventProjectNoBound.setStatus(QAEvent.ACCEPTED);
eventAbstract.setStatus(QAEvent.ACCEPTED);
getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMissingPID1.getEventId())
.content(patchAccept)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID1)));
getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMorePID.getEventId())
.content(patchAcceptUppercase)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMorePID)));
getClient(authToken)
.perform(patch("/api/integration/qualityassuranceevents/" + eventMissingUnknownPID.getEventId())
.content(patchAccept)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingUnknownPID)));
getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventProjectBound.getEventId())
.content(patchAccept)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectBound)));
getClient(authToken)
.perform(patch("/api/integration/qualityassuranceevents/" + eventProjectNoBound.getEventId())
.content(patchAccept)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectNoBound)));
getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventAbstract.getEventId())
.content(patchAccept)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstract)));
// check if the item has been updated
getClient(authToken).perform(get("/api/core/items/" + eventMissingPID1.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$",
hasJsonPath("$.metadata['dc.identifier.other'][0].value", is("10.2307/2144300"))));
getClient(authToken).perform(get("/api/core/items/" + eventMorePID.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", is("2144302"))));
getClient(authToken).perform(get("/api/core/items/" + eventMissingUnknownPID.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value",
is("http://thesis2.sba.units.it/store/handle/item/12937"))));
getClient(authToken).perform(get("/api/core/items/" + eventProjectBound.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value",
is(funding.getID().toString()))));
getClient(authToken).perform(get("/api/core/items/" + eventProjectNoBound.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value",
is(not(empty())))));
getClient(authToken).perform(get("/api/core/items/" + eventAbstract.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
hasJsonPath("$.metadata['dc.description.abstract'][0].value", is("An abstract to add..."))));
// reject pid2
eventMissingPID2.setStatus(QAEvent.REJECTED);
getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMissingPID2.getEventId())
.content(patchReject)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID2)));
getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
hasNoJsonPath("$.metadata['dc.identifier.other']")));
// discard abstractToDiscard
eventAbstractToDiscard.setStatus(QAEvent.DISCARDED);
getClient(authToken)
.perform(patch("/api/integration/qualityassuranceevents/" + eventAbstractToDiscard.getEventId())
.content(patchDiscard)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstractToDiscard)));
getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget())
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
hasNoJsonPath("$.metadata['dc.description.abstract']")));
// no pending qa events should be longer available
getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0)));
// we should have stored the decision into the database as well
}
@Test
public void setRelatedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection Fundings").build();
QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5")
.withTopic("ENRICH/MISSING/PROJECT")
.withMessage(
"{\"projects[0].acronym\":\"PAThs\","
+ "\"projects[0].code\":\"687567\","
+ "\"projects[0].funder\":\"EC\","
+ "\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: "
+ "An Archaeological Atlas of Coptic Literature."
+ "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, "
+ "Dissemination and Storage\"}")
.build();
Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event)));
getClient(authToken)
.perform(post("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related").param("item",
funding.getID().toString()))
.andExpect(status().isCreated())
.andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding)));
// update our local event copy to reflect the association with the related item
event.setRelated(funding.getID().toString());
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event)));
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding)));
}
@Test
public void unsetRelatedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection Fundings").build();
Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths")
.build();
QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5")
.withTopic("ENRICH/MISSING/PROJECT")
.withMessage(
"{\"projects[0].acronym\":\"PAThs\","
+ "\"projects[0].code\":\"687567\","
+ "\"projects[0].funder\":\"EC\","
+ "\"projects[0].fundingProgram\":\"H2020\","
+ "\"projects[0].jurisdiction\":\"EU\","
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: "
+ "An Archaeological Atlas of Coptic Literature."
+ "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, "
+ "Dissemination and Storage\"}")
.withRelatedItem(funding.getID().toString())
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event)));
getClient(authToken)
.perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related"))
.andExpect(status().isNoContent());
// update our local event copy to reflect the association with the related item
event.setRelated(null);
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event)));
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related"))
.andExpect(status().isNoContent());
}
@Test
public void setInvalidRelatedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection Fundings").build();
QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event)));
getClient(authToken)
.perform(post("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related").param("item",
funding.getID().toString()))
.andExpect(status().isUnprocessableEntity());
// check that no related item has been added to our event
getClient(authToken)
.perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event)));
}
@Test
public void deleteItemWithEventTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1),
QAEventMatcher.matchQAEventEntry(event2))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2)));
getClient(authToken).perform(delete("/api/core/items/" + event1.getTarget()))
.andExpect(status().is(204));
getClient(authToken).perform(get("/api/core/items/" + event1.getTarget()))
.andExpect(status().is(404));
getClient(authToken)
.perform(
get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID"))
.andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1)))
.andExpect(jsonPath("$._embedded.qualityassuranceevents",
Matchers.containsInAnyOrder(
QAEventMatcher.matchQAEventEntry(event2))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1)));
}
@Test
public void testEventDeletion() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}")
.build();
QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", matchQAEventEntry(event)));
List<QAEventProcessed> processedEvents = qaEventsDao.findAll(context);
assertThat(processedEvents, empty());
getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId()))
.andExpect(status().isNoContent());
getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()))
.andExpect(status().isNotFound());
processedEvents = qaEventsDao.findAll(context);
assertThat(processedEvents, hasSize(1));
QAEventProcessed processedEvent = processedEvents.get(0);
assertThat(processedEvent.getEventId(), is(event.getEventId()));
assertThat(processedEvent.getItem(), notNullValue());
assertThat(processedEvent.getItem().getID().toString(), is(event.getTarget()));
assertThat(processedEvent.getEventTimestamp(), notNullValue());
assertThat(processedEvent.getEperson().getID(), is(admin.getID()));
getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId()))
.andExpect(status().isInternalServerError());
authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event2.getEventId()))
.andExpect(status().isForbidden());
}
}

View File

@@ -0,0 +1,200 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static org.dspace.app.rest.matcher.QASourceMatcher.matchQASourceEntry;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.QAEventBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.QAEvent;
import org.dspace.services.ConfigurationService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link QASourceRestRepository}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private ConfigurationService configurationService;
private Item target;
@Before
public void setup() {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withTitle("Community")
.build();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection")
.build();
target = ItemBuilder.createItem(context, collection)
.withTitle("Item")
.build();
context.restoreAuthSystemState();
configurationService.setProperty("qaevent.sources",
new String[] { "openaire", "test-source", "test-source-2" });
}
@Test
public void testFindAll() throws Exception {
context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 2");
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3");
createEvent("test-source", "TOPIC/TEST/1", "Title 4");
createEvent("test-source", "TOPIC/TEST/1", "Title 5");
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancesources"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancesources", contains(
matchQASourceEntry("openaire", 3),
matchQASourceEntry("test-source", 2),
matchQASourceEntry("test-source-2", 0))))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(3)));
}
@Test
public void testFindAllForbidden() throws Exception {
context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("test-source", "TOPIC/TEST/1", "Title 4");
context.restoreAuthSystemState();
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/integration/qualityassurancesources"))
.andExpect(status().isForbidden());
}
@Test
public void testFindAllUnauthorized() throws Exception {
context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("test-source", "TOPIC/TEST/1", "Title 4");
context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassurancesources"))
.andExpect(status().isUnauthorized());
}
@Test
public void testFindOne() throws Exception {
context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 2");
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3");
createEvent("test-source", "TOPIC/TEST/1", "Title 4");
createEvent("test-source", "TOPIC/TEST/1", "Title 5");
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancesources/openaire"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", matchQASourceEntry("openaire", 3)));
getClient(authToken).perform(get("/api/integration/qualityassurancesources/test-source"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", matchQASourceEntry("test-source", 2)));
getClient(authToken).perform(get("/api/integration/qualityassurancesources/test-source-2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", matchQASourceEntry("test-source-2", 0)));
getClient(authToken).perform(get("/api/integration/qualityassurancesources/unknown-test-source"))
.andExpect(status().isNotFound());
}
@Test
public void testFindOneForbidden() throws Exception {
context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("test-source", "TOPIC/TEST/1", "Title 4");
context.restoreAuthSystemState();
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/integration/qualityassurancesources/openaire"))
.andExpect(status().isForbidden());
}
@Test
public void testFindOneUnauthorized() throws Exception {
context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("test-source", "TOPIC/TEST/1", "Title 4");
context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassurancesources/openaire"))
.andExpect(status().isUnauthorized());
}
private QAEvent createEvent(String source, String topic, String title) {
return QAEventBuilder.createTarget(context, target)
.withSource(source)
.withTopic(topic)
.withTitle(title)
.build();
}
}

View File

@@ -0,0 +1,277 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.rest.matcher.QATopicMatcher;
import org.dspace.app.rest.repository.QATopicRestRepository;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.QAEventBuilder;
import org.dspace.content.Collection;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link QATopicRestRepository}.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private ConfigurationService configurationService;
@Test
public void findAllTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage(
"{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics",
Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2),
QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1),
QATopicMatcher.matchQATopicEntry("ENRICH/MORE/PID", 1))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3)));
}
@Test
public void findAllUnauthorizedTest() throws Exception {
getClient().perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isUnauthorized());
}
@Test
public void findAllForbiddenTest() throws Exception {
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isForbidden());
}
@Test
public void findAllPaginationTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
//create collection
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage(
"{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics").param("size", "2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.hasSize(2)))
.andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3)));
getClient(authToken)
.perform(get("/api/integration/qualityassurancetopics")
.param("size", "2")
.param("page", "1"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.hasSize(1)))
.andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3)));
}
@Test
public void findOneTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage(
"{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!PID"))
.andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2)));
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!ABSTRACT"))
.andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1)));
}
@Test
public void findOneUnauthorizedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID").build();
context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!PID"))
.andExpect(status().isUnauthorized());
getClient().perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!ABSTRACT"))
.andExpect(status().isUnauthorized());
}
@Test
public void findOneForbiddenTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!PID"))
.andExpect(status().isForbidden());
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!ABSTRACT"))
.andExpect(status().isForbidden());
}
@Test
public void findBySourceTest() throws Exception {
context.turnOffAuthorisationSystem();
configurationService.setProperty("qaevent.sources",
new String[] { "openaire", "test-source", "test-source-2" });
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic("ENRICH/MISSING/PID")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic("ENRICH/MORE/PID")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withTopic("ENRICH/MISSING/ABSTRACT")
.withMessage(
"{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 5")
.withTopic("TEST/TOPIC")
.withSource("test-source")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 6")
.withTopic("TEST/TOPIC")
.withSource("test-source")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 7")
.withTopic("TEST/TOPIC/2")
.withSource("test-source")
.build();
context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "openaire"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics",
Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2),
QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1),
QATopicMatcher.matchQATopicEntry("ENRICH/MORE/PID", 1))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3)));
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "test-source"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics",
Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("TEST/TOPIC/2", 1),
QATopicMatcher.matchQATopicEntry("TEST/TOPIC", 2))))
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2)));
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "test-source-2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics").doesNotExist())
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0)));
}
@Test
public void findBySourceUnauthorizedTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID").build();
context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "openaire"))
.andExpect(status().isUnauthorized());
}
@Test
public void findBySourceForbiddenTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic("ENRICH/MISSING/PID").build();
context.restoreAuthSystemState();
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "openaire"))
.andExpect(status().isForbidden());
}
}

View File

@@ -0,0 +1,84 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.authorization;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.rest.authorization.impl.QAAuthorizationFeature;
import org.dspace.app.rest.converter.SiteConverter;
import org.dspace.app.rest.matcher.AuthorizationMatcher;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.rest.projection.DefaultProjection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.content.Site;
import org.dspace.content.service.SiteService;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Test suite for the Quality Assurance Authorization feature
*
* @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
*
*/
public class QAAuthorizationFeatureIT extends AbstractControllerIntegrationTest {
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
@Autowired
private SiteService siteService;
@Autowired
private SiteConverter siteConverter;
@Autowired
private ConfigurationService configurationService;
private AuthorizationFeature qaAuthorizationFeature;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
context.turnOffAuthorisationSystem();
qaAuthorizationFeature = authorizationFeatureService.find(QAAuthorizationFeature.NAME);
context.restoreAuthSystemState();
}
@Test
public void testQAAuthorizationSuccess() throws Exception {
configurationService.setProperty("qaevents.enabled", true);
Site site = siteService.findSite(context);
SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT);
String tokenAdmin = getAuthToken(admin.getEmail(), password);
Authorization authAdminSite = new Authorization(admin, qaAuthorizationFeature, siteRest);
getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID()))
.andExpect(jsonPath("$", Matchers.is(
AuthorizationMatcher.matchAuthorization(authAdminSite))));
}
@Test
public void testQAAuthorizationFail() throws Exception {
configurationService.setProperty("qaevents.enabled", false);
Site site = siteService.findSite(context);
SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT);
String tokenAdmin = getAuthToken(admin.getEmail(), password);
Authorization authAdminSite = new Authorization(admin, qaAuthorizationFeature, siteRest);
getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID()))
.andExpect(status().isNotFound());
}
}

View File

@@ -0,0 +1,126 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.is;
import java.text.DecimalFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.QAEvent;
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.core.IsAnything;
/**
* Matcher related to {@link QAEventResource}.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
public class QAEventMatcher {
private QAEventMatcher() {
}
public static Matcher<? super Object> matchQAEventFullEntry(QAEvent event) {
return allOf(
matchQAEventEntry(event),
hasJsonPath("$._embedded.topic.name", is(event.getTopic())),
hasJsonPath("$._embedded.target.id", is(event.getTarget())),
event.getRelated() != null ?
hasJsonPath("$._embedded.related.id", is(event.getRelated())) :
hasJsonPath("$._embedded.related", is(emptyOrNullString()))
);
}
public static Matcher<? super Object> matchQAEventEntry(QAEvent event) {
try {
ObjectMapper jsonMapper = new JsonMapper();
return allOf(hasJsonPath("$.id", is(event.getEventId())),
hasJsonPath("$.originalId", is(event.getOriginalId())),
hasJsonPath("$.title", is(event.getTitle())),
hasJsonPath("$.trust", is(new DecimalFormat("0.000").format(event.getTrust()))),
hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())),
hasJsonPath("$.message",
matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(),
OpenaireMessageDTO.class))),
hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")),
hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")),
hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")),
hasJsonPath("$.type", is("qualityassuranceevent")));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
private static Matcher<? super Object> matchMessage(String topic, OpenaireMessageDTO message) {
if (StringUtils.endsWith(topic, "/ABSTRACT")) {
return allOf(hasJsonPath("$.abstract", is(message.getAbstracts())));
} else if (StringUtils.endsWith(topic, "/PID")) {
return allOf(
hasJsonPath("$.value", is(message.getValue())),
hasJsonPath("$.type", is(message.getType())),
hasJsonPath("$.pidHref", is(calculateOpenairePidHref(message.getType(), message.getValue()))));
} else if (StringUtils.endsWith(topic, "/PROJECT")) {
return allOf(
hasJsonPath("$.openaireId", is(message.getOpenaireId())),
hasJsonPath("$.acronym", is(message.getAcronym())),
hasJsonPath("$.code", is(message.getCode())),
hasJsonPath("$.funder", is(message.getFunder())),
hasJsonPath("$.fundingProgram", is(message.getFundingProgram())),
hasJsonPath("$.jurisdiction", is(message.getJurisdiction())),
hasJsonPath("$.title", is(message.getTitle())));
}
return IsAnything.anything();
}
private static String calculateOpenairePidHref(String type, String value) {
if (type == null) {
return null;
}
String hrefPrefix = null;
switch (type) {
case "arxiv":
hrefPrefix = "https://arxiv.org/abs/";
break;
case "handle":
hrefPrefix = "https://hdl.handle.net/";
break;
case "urn":
hrefPrefix = "";
break;
case "doi":
hrefPrefix = "https://doi.org/";
break;
case "pmc":
hrefPrefix = "https://www.ncbi.nlm.nih.gov/pmc/articles/";
break;
case "pmid":
hrefPrefix = "https://pubmed.ncbi.nlm.nih.gov/";
break;
case "ncid":
hrefPrefix = "https://ci.nii.ac.jp/ncid/";
break;
default:
break;
}
return hrefPrefix != null ? hrefPrefix + value : null;
}
}

View File

@@ -0,0 +1,43 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import org.dspace.app.rest.model.hateoas.QASourceResource;
import org.hamcrest.Matcher;
/**
* Matcher related to {@link QASourceResource}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class QASourceMatcher {
private QASourceMatcher() { }
public static Matcher<? super Object> matchQASourceEntry(String key, int totalEvents) {
return allOf(
hasJsonPath("$.type", is("qualityassurancesource")),
hasJsonPath("$.id", is(key)),
hasJsonPath("$.totalEvents", is(totalEvents))
);
}
public static Matcher<? super Object> matchQASourceEntry(String key) {
return allOf(
hasJsonPath("$.type", is("qualityassurancesource")),
hasJsonPath("$.id", is(key))
);
}
}

View File

@@ -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.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import org.dspace.app.rest.model.hateoas.QATopicResource;
import org.hamcrest.Matcher;
/**
* Matcher related to {@link QATopicResource}.
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/
public class QATopicMatcher {
private QATopicMatcher() { }
public static Matcher<? super Object> matchQATopicEntry(String key, int totalEvents) {
return allOf(
hasJsonPath("$.type", is("qualityassurancetopic")),
hasJsonPath("$.name", is(key)),
hasJsonPath("$.id", is(key.replace("/", "!"))),
hasJsonPath("$.totalEvents", is(totalEvents))
);
}
public static Matcher<? super Object> matchQATopicEntry(String key) {
return allOf(
hasJsonPath("$.type", is("qualityassurancetopic")),
hasJsonPath("$.name", is(key)),
hasJsonPath("$.id", is(key.replace("/", "/")))
);
}
}

View File

@@ -14,7 +14,7 @@ import javax.xml.bind.JAXBException;
import eu.openaire.jaxb.helper.OpenAIREHandler;
import eu.openaire.jaxb.model.Response;
import org.dspace.external.OpenAIRERestConnector;
import org.dspace.external.OpenaireRestConnector;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
@@ -22,14 +22,14 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Mock the OpenAIRE external source using a mock rest connector so that query
* Mock the Openaire external source using a mock rest connector so that query
* will be resolved against static test files
*
*/
public class MockOpenAIREFundingDataProvider extends OpenAIREFundingDataProvider {
public class MockOpenaireFundingDataProvider extends OpenaireFundingDataProvider {
@Override
public void init() throws IOException {
OpenAIRERestConnector restConnector = Mockito.mock(OpenAIRERestConnector.class);
OpenaireRestConnector restConnector = Mockito.mock(OpenaireRestConnector.class);
when(restConnector.searchProjectByKeywords(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt(),
ArgumentMatchers.startsWith("mushroom"))).thenAnswer(new Answer<Response>() {

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Following OpenAIRE Guidelines 4 -->
<!-- Following Openaire Guidelines 4 -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:doc="http://www.lyncode.com/xoai">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
@@ -12,7 +12,7 @@
<!--
Formatting dc.date.issued
based on what OpenAIRE4 specifies for issued dates
based on what Openaire4 specifies for issued dates
https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_publicationdate.html
-->
<xsl:template

View File

@@ -47,7 +47,7 @@
</Context>
<!--
OpenAIRE Guidelines 3.0:
Openaire Guidelines 3.0:
- https://guidelines.openaire.eu/
@@ -55,10 +55,10 @@
- Predefined DSpace fields don't allow to set this up with a default.
-->
<Context baseurl="openaire" name="OpenAIRE Context">
<Context baseurl="openaire" name="Openaire Context">
<!-- Date format, field prefixes, etc are ensured by the transformer -->
<Transformer ref="openaireTransformer"/>
<!-- OpenAIRE filter -->
<!-- Openaire filter -->
<Filter ref="openAireFilter"/>
<!-- Just an alias, in fact it returns all items within the driver context -->
<Set ref="openaireSet"/>
@@ -66,12 +66,12 @@
<Format ref="oaidc"/>
<Format ref="mets"/>
<Description>
This contexts complies with OpenAIRE Guidelines for Literature Repositories v3.0.
This contexts complies with Openaire Guidelines for Literature Repositories v3.0.
</Description>
</Context>
<!--
OpenAIRE Guidelines 4.0:
Openaire Guidelines 4.0:
- https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/
@@ -79,15 +79,15 @@
- Predefined DSpace fields don't allow to set this up with a default.
-->
<Context baseurl="openaire4" name="OpenAIRE 4 Context">
<Context baseurl="openaire4" name="Openaire 4 Context">
<!-- Date format, field prefixes, etc are ensured by the transformer -->
<Transformer ref="openaire4Transformer"/>
<!-- OpenAIRE filter -->
<Filter ref="openAIRE4Filter"/>
<!-- Openaire filter -->
<Filter ref="openaire4Filter"/>
<!-- Metadata Formats -->
<Format ref="oaiopenaire"/>
<Description>
This contexts complies with OpenAIRE Guidelines for Literature Repositories v4.0.
This contexts complies with Openaire Guidelines for Literature Repositories v4.0.
</Description>
</Context>
@@ -176,7 +176,7 @@
<Namespace>http://irdb.nii.ac.jp/oai</Namespace>
<SchemaLocation>http://irdb.nii.ac.jp/oai/junii2-3-1.xsd</SchemaLocation>
</Format>
<!-- Metadata format based on the OpenAIRE 4.0 guidelines
<!-- Metadata format based on the Openaire 4.0 guidelines
https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/use_of_oai_pmh.html
-->
<Format id="oaiopenaire">
@@ -250,13 +250,13 @@
</Definition>
</Filter>
<!-- OpenAIRE filter for records returned by OAI-PMH.
<!-- Openaire filter for records returned by OAI-PMH.
By default, return an Item record:
* If a Title & Author field both exist
* AND a valid DRIVER Document Type exists
* AND Item is either publicly accessible OR Withdrawn (for tombstones)
* AND the OpenAIRE "dc.relation" is specified
This filter is only used in the OpenAIRE context ([oai]/openaire).
* AND the Openaire "dc.relation" is specified
This filter is only used in the Openaire context ([oai]/openaire).
-->
<Filter id="openAireFilter">
<Definition>
@@ -319,7 +319,7 @@
</Definition>
</Filter>
<!-- OpenAIRE4 filter for records returned by OAI-PMH.
<!-- Openaire4 filter for records returned by OAI-PMH.
By default, return an Item record:
* If it is publicly accessible
* * OR it has been withdrawn (in order to display a tombstone record).
@@ -328,7 +328,7 @@
* limiting the results only to Publications as expected
This filter is used by the default context ([oai]/request).
-->
<Filter id="openAIRE4Filter">
<Filter id="openaire4Filter">
<Definition>
<And>
<LeftCondition>
@@ -457,7 +457,7 @@
<!-- This condition determines if an Item has a "dc.rights" field
specifying "open access", which is required for DRIVER
OR "openAccess", which is required by OpenAIRE. -->
OR "openAccess", which is required by Openaire. -->
<CustomCondition id="driverAccessCondition">
<Class>org.dspace.xoai.filter.DSpaceAtLeastOneMetadataFilter</Class>
<Configuration>
@@ -483,7 +483,7 @@
</CustomCondition>
<!-- This condition determines if an Item has a "dc.relation" field
which specifies the openAIRE project ID. -->
which specifies the openaire project ID. -->
<CustomCondition id="openaireRelationCondition">
<Class>org.dspace.xoai.filter.DSpaceAtLeastOneMetadataFilter</Class>
<Configuration>

View File

@@ -780,7 +780,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher
# Add rdf here, if you are using dspace-rdf to export your repository content as RDF.
# Add iiif here, if you are using dspace-iiif.
# Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality
event.dispatcher.default.consumers = versioning, discovery, eperson, submissionconfig
event.dispatcher.default.consumers = versioning, discovery, eperson, submissionconfig, qaeventsdelete
# The noindex dispatcher will not create search or browse indexes (useful for batch item imports)
event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher
@@ -806,6 +806,10 @@ event.consumer.rdf.filters = Community|Collection|Item|Bundle|Bitstream|Site+Add
#event.consumer.test.class = org.dspace.event.TestConsumer
#event.consumer.test.filters = All+All
# qaevents consumer to delete events related to deleted items
event.consumer.qaeventsdelete.class = org.dspace.qaevent.QAEventsDeleteCascadeConsumer
event.consumer.qaeventsdelete.filters = Item+Delete
# consumer to maintain versions
event.consumer.versioning.class = org.dspace.versioning.VersioningConsumer
event.consumer.versioning.filters = Item+Install
@@ -1646,6 +1650,7 @@ include = ${module_dir}/irus-statistics.cfg
include = ${module_dir}/oai.cfg
include = ${module_dir}/openaire-client.cfg
include = ${module_dir}/orcid.cfg
include = ${module_dir}/qaevents.cfg
include = ${module_dir}/rdf.cfg
include = ${module_dir}/rest.cfg
include = ${module_dir}/iiif.cfg

View File

@@ -62,6 +62,8 @@
<mapping class="org.dspace.content.Site"/>
<mapping class="org.dspace.content.WorkspaceItem"/>
<mapping class="org.dspace.content.QAEventProcessed" />
<mapping class="org.dspace.eperson.EPerson"/>
<mapping class="org.dspace.eperson.Group"/>
<mapping class="org.dspace.eperson.Group2GroupCache"/>

View File

@@ -185,28 +185,28 @@
<type>extract</type>
</step-definition>
<!-- OpenAIRE submission steps/forms -->
<step-definition id="openAIREProjectForm" mandatory="true">
<!-- Openaire submission steps/forms -->
<step-definition id="openaireProjectForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREPersonForm" mandatory="true">
<step-definition id="openairePersonForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREOrganizationForm" mandatory="true">
<step-definition id="openaireOrganizationForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREPublicationPageoneForm" mandatory="true">
<step-definition id="openairePublicationPageoneForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
<step-definition id="openAIREPublicationPagetwoForm" mandatory="true">
<step-definition id="openairePublicationPagetwoForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
@@ -361,13 +361,13 @@
<step id="license"/>
</submission-process>
<!-- OpenAIRE submission processes -->
<submission-process name="openAIREPublicationSubmission">
<!-- Openaire submission processes -->
<submission-process name="openairePublicationSubmission">
<step id="collection"/>
<!--Step will be to Describe the item. -->
<step id="openAIREPublicationPageoneForm"/>
<step id="openAIREPublicationPagetwoForm"/>
<step id="openairePublicationPageoneForm"/>
<step id="openairePublicationPagetwoForm"/>
<!--Step will be to Upload the item -->
<!-- step id="upload-with-embargo"/-->
@@ -376,17 +376,17 @@
<!--Step will be to Sign off on the License -->
<step id="license"/>
</submission-process>
<submission-process name="openAIREPersonSubmission">
<submission-process name="openairePersonSubmission">
<step id="collection"/>
<step id="openAIREPersonForm"/>
<step id="openairePersonForm"/>
</submission-process>
<submission-process name="openAIREProjectSubmission">
<submission-process name="openaireProjectSubmission">
<step id="collection"/>
<step id="openAIREProjectForm"/>
<step id="openaireProjectForm"/>
</submission-process>
<submission-process name="openAIREOrganizationSubmission">
<submission-process name="openaireOrganizationSubmission">
<step id="collection"/>
<step id="openAIREOrganizationForm"/>
<step id="openaireOrganizationForm"/>
</submission-process>
</submission-definitions>

View File

@@ -16,20 +16,20 @@
# The accessToken it only has a validity of one hour
# For more details about the token, please check: https://develop.openaire.eu/personalToken.html
#
# the current OpenAIRE Rest client implementation uses basic authentication
# the current Openaire Rest client implementation uses basic authentication
# Described here: https://develop.openaire.eu/basic.html
#
# ---- Token usage required definitions ----
# you can override this settings in your local.cfg file - can be true/false
openaire.token.enabled = false
# URL of the OpenAIRE authentication and authorization service
# URL of the Openaire authentication and authorization service
openaire.token.url = https://aai.openaire.eu/oidc/token
# you will be required to register at OpenAIRE (https://services.openaire.eu/uoa-user-management/registeredServices)
# you will be required to register at Openaire (https://services.openaire.eu/uoa-user-management/registeredServices)
# and create your service in order to get the following data:
openaire.token.clientId = CLIENT_ID_HERE
openaire.token.clientSecret = CLIENT_SECRET_HERE
# URL of OpenAIRE Rest API
# URL of Openaire Rest API
openaire.api.url = https://api.openaire.eu

View File

@@ -0,0 +1,34 @@
#---------------------------------------------------------------#
#------- Quality Assurance Broker Events CONFIGURATIONS --------#
#---------------------------------------------------------------#
# Configuration properties used by data correction service #
#---------------------------------------------------------------#
# Quality Assurance enable property, false by default
qaevents.enabled = false
qaevents.solr.server = ${solr.server}/${solr.multicorePrefix}qaevent
# A POST to these url(s) will be done to notify oaire of decision taken for each qaevents
# qaevents.openaire.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events
# The list of the supported events incoming from openaire (see also dspace/config/spring/api/qaevents.xml)
# add missing abstract suggestion
qaevents.openaire.import.topic = ENRICH/MISSING/ABSTRACT
# add missing publication id suggestion
qaevents.openaire.import.topic = ENRICH/MISSING/PID
# add more publication id suggestion
qaevents.openaire.import.topic = ENRICH/MORE/PID
# add missing project suggestion
qaevents.openaire.import.topic = ENRICH/MISSING/PROJECT
# add more project suggestion
qaevents.openaire.import.topic = ENRICH/MORE/PROJECT
# The list of the supported pid href for the OPENAIRE events
qaevents.openaire.pid-href-prefix.arxiv = https://arxiv.org/abs/
qaevents.openaire.pid-href-prefix.handle = https://hdl.handle.net/
qaevents.openaire.pid-href-prefix.urn =
qaevents.openaire.pid-href-prefix.doi = https://doi.org/
qaevents.openaire.pid-href-prefix.pmc = https://www.ncbi.nlm.nih.gov/pmc/articles/
qaevents.openaire.pid-href-prefix.pmid = https://pubmed.ncbi.nlm.nih.gov/
qaevents.openaire.pid-href-prefix.ncid = https://ci.nii.ac.jp/ncid/
# The URI used by the OPENAIRE broker client to import QA events
qaevents.openaire.broker-url = http://api.openaire.eu/broker

Some files were not shown because too many files have changed in this diff Show More