diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java index 9e90f81be32c..d78d140dcfe5 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -13,6 +13,7 @@ import java.util.Date; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.dspace.qaevent.service.dto.OpenaireMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.util.RawJsonDeserializer; @@ -29,7 +30,8 @@ public class QAEvent { public static final String REJECTED = "rejected"; public static final String DISCARDED = "discarded"; - public static final String OPENAIRE_SOURCE = "openaire"; + public static final String OPENAIRE_SOURCE = "OpenAIRE"; + public static final String DSPACE_USERS_SOURCE = "DSpaceUsers"; private String source; @@ -62,8 +64,7 @@ public class QAEvent { private String status = "PENDING"; - public QAEvent() { - } + public QAEvent() {} public QAEvent(String source, String originalId, String target, String title, String topic, double trust, String message, Date lastUpdate) { @@ -81,7 +82,6 @@ public QAEvent(String source, String originalId, String target, String title, } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { throw new IllegalStateException(e); } - } public String getOriginalId() { @@ -205,6 +205,8 @@ public Class getMessageDtoClass() { switch (getSource()) { case OPENAIRE_SOURCE: return OpenaireMessageDTO.class; + case DSPACE_USERS_SOURCE: + return CorrectionTypeMessageDTO.class; default: throw new IllegalArgumentException("Unknown event's source: " + getSource()); } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java new file mode 100644 index 000000000000..bc5abaef4efb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java @@ -0,0 +1,81 @@ +/** + * 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.correctiontype; + +import java.sql.SQLException; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Interface class that model the CorrectionType. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public interface CorrectionType { + + /** + * Retrieves the unique identifier associated to the CorrectionType. + */ + public String getId(); + + /** + * Retrieves the topic associated with the to the CorrectionType. + */ + public String getTopic(); + + /** + * Checks whether the CorrectionType required related item. + */ + public boolean isRequiredRelatedItem(); + + /** + * Checks whether target item is allowed for current CorrectionType + * + * @param context Current DSpace session + * @param targetItem Target item + * @throws AuthorizeException if authorize error + * @throws SQLException if there's a database problem + */ + public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException; + + /** + * Checks whether target item and related item are allowed for current CorrectionType + * + * @param context Current DSpace session + * @param targetItem Target item + * @param relatedItem Related item + * @throws AuthorizeException if authorize error + * @throws SQLException if there's a database problem + */ + public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException,SQLException; + + /** + * Creates a QAEvent for a specific target item. + * + * @param context Current DSpace session + * @param targetItem Target item + * @param reason Reason + * @return QAEvent + */ + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason); + + /** + * Creates a QAEvent for a target item and related item. + * @param context Current DSpace session + * @param targetItem Target item + * @param relatedItem Related item + * @param reason Reason + * @return QAEvent + */ + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason); + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java new file mode 100644 index 000000000000..847236d11071 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -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.correctiontype; + +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.correctiontype.WithdrawnCorrectionType.WITHDRAWAL_REINSTATE_GROUP; + +import java.sql.SQLException; +import java.util.Date; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation class for {@link CorrectionType} + * that will reinstate target item if it's withdrawn. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class ReinstateCorrectionType implements CorrectionType, InitializingBean { + + private String id; + private String topic; + private String creationForm; + + @Autowired + private GroupService groupService; + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ConfigurationService configurationService; + + @Override + public boolean isAllowed(Context context, Item targetItem) throws SQLException { + if (!targetItem.isWithdrawn()) { + return false; + } + boolean isAdmin = authorizeService.isAdmin(context); + if (!currentUserIsMemberOfwithdrawalReinstateGroup(context) && !isAdmin) { + return false; + } + long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); + return tot == 0; + } + + private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { + String groupName = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(groupName)) { + return false; + } + Group withdrawalReinstateGroup = groupService.findByName(context, groupName); + return withdrawalReinstateGroup != null && groupService.isMember(context, withdrawalReinstateGroup); + } + + @Override + public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException, + SQLException { + return isAllowed(context, targetItem); + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { + ObjectNode reasonJson = createReasonJson(reason); + QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, + context.getCurrentUser().getID().toString(), + targetItem.getID().toString(), + targetItem.getName(), + this.getTopic(), + 1.0, + reasonJson.toString(), + new Date() + ); + + qaEventService.store(context, qaEvent); + return qaEvent; + } + + private ObjectNode createReasonJson(QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode jsonNode = new ObjectMapper().createObjectNode(); + jsonNode.put("reason", mesasge.getReason()); + return jsonNode; + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { + return this.createCorrection(context, targetItem, reason); + } + + @Override + public boolean isRequiredRelatedItem() { + return false; + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + @Override + public void afterPropertiesSet() throws Exception {} + + public void setCreationForm(String creationForm) { + this.creationForm = creationForm; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java new file mode 100644 index 000000000000..edf71ed8151f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -0,0 +1,149 @@ +/** + * 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.correctiontype; + +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.core.Constants.READ; + +import java.sql.SQLException; +import java.util.Date; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation class for {@link CorrectionType} + * that will withdrawn target item if it archived and wasn't withdrawn alredy. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class WithdrawnCorrectionType implements CorrectionType, InitializingBean { + + public static final String WITHDRAWAL_REINSTATE_GROUP = "qaevents.withdraw-reinstate.group"; + + private String id; + private String topic; + private String creationForm; + + @Autowired + private GroupService groupService; + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ConfigurationService configurationService; + + + @Override + public boolean isAllowed(Context context, Item targetItem) throws SQLException { + if (targetItem.isWithdrawn() || !targetItem.isArchived()) { + return false; + } + try { + authorizeService.authorizeAction(context, targetItem, READ); + } catch (AuthorizeException e) { + return false; + } + boolean isAdmin = authorizeService.isAdmin(context); + if (!currentUserIsMemberOfwithdrawalReinstateGroup(context) && !isAdmin) { + return false; + } + long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); + return tot == 0; + } + + private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { + String groupName = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(groupName)) { + return false; + } + Group withdrawalReinstateGroup = groupService.findByName(context, groupName); + return withdrawalReinstateGroup != null && groupService.isMember(context, withdrawalReinstateGroup); + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { + ObjectNode reasonJson = createReasonJson(reason); + QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, + context.getCurrentUser().getID().toString(), + targetItem.getID().toString(), + targetItem.getName(), + this.getTopic(), + 1.0, + reasonJson.toString(), + new Date() + ); + + qaEventService.store(context, qaEvent); + return qaEvent; + } + + private ObjectNode createReasonJson(QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode jsonNode = new ObjectMapper().createObjectNode(); + jsonNode.put("reason", mesasge.getReason()); + return jsonNode; + } + + @Override + public boolean isAllowed(Context context, Item targetItem, Item relatedItem) + throws AuthorizeException, SQLException { + return isAllowed(context, targetItem); + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { + return createCorrection(context, targetItem, reason); + } + + @Override + public boolean isRequiredRelatedItem() { + return false; + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + @Override + public void afterPropertiesSet() throws Exception {} + + public void setCreationForm(String creationForm) { + this.creationForm = creationForm; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java b/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java new file mode 100644 index 000000000000..e76e1f7ec146 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java @@ -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.correctiontype.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; + +/** + * Service interface class for the CorrectionType object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface CorrectionTypeService { + + /** + * Retrieves a CorrectionType object from the system based on a unique identifier. + * + * @param id The unique identifier of the CorrectionType object to be retrieved. + * @return The CorrectionType object corresponding to the provided identifier, + * or null if no object is found. + */ + public CorrectionType findOne(String id); + + /** + * Retrieves a list of all CorrectionType objects available in the system. + * + * @return Returns a List containing all CorrectionType objects in the system. + */ + public List findAll(); + + /** + * Retrieves a list of CorrectionType objects related to the provided Item. + * + * @param context Current DSpace session. + * @param item Target item + * @throws AuthorizeException If authorize error + * @throws SQLException If a database error occurs during the operation. + */ + public List findByItem(Context context, Item item) throws AuthorizeException, SQLException; + + /** + * Retrieves a CorrectionType object associated with a specific topic. + * + * @param topic The topic for which the CorrectionType object is to be retrieved. + */ + public CorrectionType findByTopic(String topic); + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java new file mode 100644 index 000000000000..e64120c46a50 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java @@ -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.correctiontype.service.impl; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service implementation class for the CorrectionType object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class CorrectionTypeServiceImpl implements CorrectionTypeService { + + @Autowired + private List correctionTypes; + + @Override + public CorrectionType findOne(String id) { + return findAll().stream() + .filter(correctionType -> correctionType.getId().equals(id)) + .findFirst() + .orElse(null); + } + + @Override + public List findAll() { + return CollectionUtils.isNotEmpty(correctionTypes) ? correctionTypes : List.of(); + } + + @Override + public List findByItem(Context context, Item item) throws AuthorizeException, SQLException { + List correctionTypes = new ArrayList<>(); + for (CorrectionType correctionType : findAll()) { + if (correctionType.isAllowed(context, item)) { + correctionTypes.add(correctionType); + } + } + return correctionTypes; + } + + @Override + public CorrectionType findByTopic(String topic) { + return findAll().stream() + .filter(correctionType -> correctionType.getTopic().equals(topic)) + .findFirst() + .orElse(null); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java new file mode 100644 index 000000000000..4bf9867a2bfb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.qaevent; + +/** + * Constants for Quality Assurance configurations to be used into cfg and xml spring. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + */ +public class QANotifyPatterns { + + public static final String TOPIC_ENRICH_MORE_PROJECT = "ENRICH/MORE/PROJECT"; + public static final String TOPIC_ENRICH_MISSING_PROJECT = "ENRICH/MISSING/PROJECT"; + public static final String TOPIC_ENRICH_MISSING_ABSTRACT = "ENRICH/MISSING/ABSTRACT"; + public static final String TOPIC_ENRICH_MORE_REVIEW = "ENRICH/MORE/REVIEW"; + public static final String TOPIC_ENRICH_MORE_ENDORSEMENT = "ENRICH/MORE/ENDORSEMENT"; + public static final String TOPIC_ENRICH_MORE_PID = "ENRICH/MORE/PID"; + public static final String TOPIC_ENRICH_MISSING_PID = "ENRICH/MISSING/PID"; + + /** + * Default constructor + */ + private QANotifyPatterns() { } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java index e22f7d32a770..506f68e9a2a0 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -8,6 +8,7 @@ package org.dspace.qaevent; import java.util.Date; +import java.util.UUID; /** * This model class represent the source/provider of the QA events (as Openaire). @@ -16,9 +17,16 @@ * */ public class QASource { + + /** + * The focus attributes specify if the QASource object is describing the status of a specific + * quality assurance source for the whole repository (focus = null) or for a specific + * DSpaceObject (focus = uuid of the DSpaceObject). This would mostly affect the totalEvents attribute below. + */ + private UUID focus; private String name; - private long totalEvents; private Date lastEvent; + private long totalEvents; public String getName() { return name; @@ -43,4 +51,12 @@ public Date getLastEvent() { public void setLastEvent(Date lastEvent) { this.lastEvent = lastEvent; } + + public UUID getFocus() { + return focus; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java index 63e523b9cb5e..c96847e7a108 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -8,18 +8,29 @@ package org.dspace.qaevent; import java.util.Date; +import java.util.UUID; /** * 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 { + + /** + * The focus attributes specify if the QATopic object is describing the status of a specific + * quality assurance topic for the whole repository (focus = null) or for a specific + * DSpaceObject (focus = uuid of the DSpaceObject). This would mostly affect the totalEvents attribute below. + */ + private UUID focus; private String key; - private long totalEvents; + /** + * The source attributes contains the name of the QA source like: OpenAIRE, DSpaceUsers + */ + private String source; private Date lastEvent; + private long totalEvents; public String getKey() { return key; @@ -44,4 +55,20 @@ public Date getLastEvent() { public void setLastEvent(Date lastEvent) { this.lastEvent = lastEvent; } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public UUID getFocus() { + return focus; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java index 2509b768aefb..1252462c8183 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java @@ -19,22 +19,29 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link QualityAssuranceAction} that add a simple metadata to the given - * item. + * 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; + + protected String metadata; + protected String metadataSchema; + protected String metadataElement; + protected String metadataQualifier; + @Autowired - private ItemService itemService; + protected ItemService itemService; - public void setItemService(ItemService itemService) { - this.itemService = itemService; + @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.getMessage(), e); + } } public String getMetadata() { @@ -50,15 +57,4 @@ public void setMetadata(String metadata) { 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); - } - } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java new file mode 100644 index 000000000000..cd436f396650 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.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.QAMessageDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QAReinstateRequestAction is an implementation of the QualityAssuranceAction interface. + * It is responsible for applying a correction to reinstate a specified item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class QAReinstateRequestAction implements QualityAssuranceAction { + + private static final Logger log = LoggerFactory.getLogger(QAReinstateRequestAction.class); + + @Autowired + private ItemService itemService; + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + try { + itemService.reinstate(context, item); + } catch (SQLException | AuthorizeException e) { + log.error(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java new file mode 100644 index 000000000000..7048a8285ea1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.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.QAMessageDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QAWithdrawnRequestAction is an implementation of the QualityAssuranceAction interface. + * It is responsible for applying a correction to withdraw a specified item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class QAWithdrawnRequestAction implements QualityAssuranceAction { + + private static final Logger log = LoggerFactory.getLogger(QAWithdrawnRequestAction.class); + + @Autowired + private ItemService itemService; + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + try { + itemService.withdraw(context, item); + } catch (SQLException | AuthorizeException e) { + log.error(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index 9087606aa6e5..cbaf80842232 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -7,9 +7,9 @@ */ package org.dspace.qaevent.script; - import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.substringAfter; +import static org.dspace.core.Constants.ITEM; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -28,10 +28,14 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.handle.HandleServiceImpl; +import org.dspace.handle.service.HandleService; import org.dspace.qaevent.service.OpenaireClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.DSpaceRunnable; @@ -71,6 +75,8 @@ public class OpenaireEventsImport extends DSpaceRunnable> { + private HandleService handleService; + private QAEventService qaEventService; private String[] topicsToImport; @@ -103,7 +109,9 @@ public void setup() throws ParseException { jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - qaEventService = new DSpace().getSingletonService(QAEventService.class); + DSpace dspace = new DSpace(); + handleService = dspace.getSingletonService(HandleServiceImpl.class); + qaEventService = dspace.getSingletonService(QAEventService.class); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); @@ -236,14 +244,47 @@ private List readOpenaireQAEventsFromJson(InputStream inputStream) thro private void storeOpenaireQAEvents(List events) { for (QAEvent event : events) { try { + final String resourceUUID = getResourceUUID(context, event.getOriginalId()); + if (resourceUUID == null) { + throw new IllegalArgumentException("Skipped event " + event.getEventId() + + " related to the oai record " + event.getOriginalId() + " as the record was not found"); + } + event.setTarget(resourceUUID); storeOpenaireQAEvent(event); - } catch (RuntimeException e) { + } catch (RuntimeException | SQLException e) { handler.logWarning("An error occurs storing the event with id " + event.getEventId() + ": " + getMessage(e)); } } } + private String getResourceUUID(Context context, String originalId) throws IllegalStateException, SQLException { + String id = getHandleFromOriginalId(originalId); + if (StringUtils.isNotBlank(id)) { + DSpaceObject dso = handleService.resolveToObject(context, id); + if (dso != null && dso.getType() == ITEM) { + Item item = (Item) dso; + 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 originalId; + } + } + /** * Store the given QAEvent, skipping it if it is not supported. * diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java new file mode 100644 index 000000000000..38cf40ce3989 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java @@ -0,0 +1,51 @@ +/** + * 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.security; + +import java.sql.SQLException; +import java.util.Optional; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QASecurity that restrict access to the QA Source and related events only to repository administrators + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public class AdministratorsOnlyQASecurity implements QASecurity { + + @Autowired + private AuthorizeService authorizeService; + + public Optional generateFilterQuery(Context context, EPerson currentUser) { + return Optional.empty(); + } + + @Override + public boolean canSeeQASource(Context context, EPerson user) { + try { + return authorizeService.isAdmin(context, user); + } catch (SQLException e) { + return false; + } + } + + @Override + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { + try { + return authorizeService.isAdmin(context, user); + } catch (SQLException e) { + return false; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java new file mode 100644 index 000000000000..68ef1066654f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java @@ -0,0 +1,55 @@ +/** + * 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.security; + +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * The QASecurity interface defines methods for implementing security strategies + * related to Quality Assurance (QA) events. Classes implementing this interface should + * provide logic to filter and determine visibility of QA events based on the user's permissions. + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public interface QASecurity { + + /** + * Return a SOLR queries that can be applied querying the qaevent SOLR core to retrieve only the qaevents visible to + * the provided user + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return the SOLR filter query to apply + */ + public Optional generateFilterQuery(Context context, EPerson user); + + /** + * Return true it the user is potentially allowed to see events in the qasource that adopt this + * security strategy + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return true if the user can eventually see some qaevents + */ + public boolean canSeeQASource(Context context, EPerson user); + + /** + * Return true it the user is potentially allowed to see events in the qasource that adopt this + * security strategy + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return true if the user can see the provided qaEvent + */ + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java new file mode 100644 index 000000000000..3d66d221e681 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java @@ -0,0 +1,70 @@ +/** + * 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.security; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.Optional; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.service.QAEventService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QASecurity implementations that allow access to only qa events that match a SORL query generated using the eperson + * uuid + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public class UserBasedFilterQASecurity implements QASecurity { + + private String filterTemplate; + private boolean allowAdmins = true; + + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + + public Optional generateFilterQuery(Context context, EPerson user) { + try { + if (allowAdmins && authorizeService.isAdmin(context, user)) { + return Optional.empty(); + } else { + return Optional.of(MessageFormat.format(filterTemplate, user.getID().toString())); + } + } catch (SQLException e) { + throw new RuntimeException("Error checking permissions", e); + } + } + + public boolean canSeeQASource(Context context, EPerson user) { + return user != null; + } + + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { + try { + return (allowAdmins && authorizeService.isAdmin(context, user)) + || qaEventService.qaEventsInSource(context, user, qaEvent.getEventId(), qaEvent.getSource()); + } catch (SQLException e) { + throw new RuntimeException("Error checking permissions", e); + } + } + + public void setFilterTemplate(String filterTemplate) { + this.filterTemplate = filterTemplate; + } + + public void setAllowAdmins(boolean allowAdmins) { + this.allowAdmins = allowAdmins; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java new file mode 100644 index 000000000000..7f6ef7a12cd7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java @@ -0,0 +1,55 @@ +/** + * 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.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Interface to limit the visibility of {@link QAEvent} to specific users. + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + * + */ +public interface QAEventSecurityService { + + /** + * Check if the specified user can see a specific QASource + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name + * @return true if the specified user can eventually see events in the QASource + */ + boolean canSeeSource(Context context, EPerson user, String sourceName); + + /** + * Check if the specified user can see a specific QAEvent. It is expected that a QAEvent in a not visible QASource + * cannot be accessed. So implementation of this method should enforce this rule. + * + * @param context the context + * @param user the eperson to consider + * @param qaEvent the qaevent to check + * @return true if the specified user can see the specified event + */ + boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent); + + /** + * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the + * specified user + * + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name + * @return the solr filter query + */ + public Optional generateQAEventFilterQuery(Context context, EPerson user, String sourceName); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index 2332a55caf52..9f9fd3eaa5ce 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -12,6 +12,7 @@ import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.qaevent.QASource; import org.dspace.qaevent.QATopic; @@ -26,69 +27,73 @@ public interface QAEventService { /** * Find all the event's topics. * - * @param offset the offset to apply - * @return the topics list + * @param context the DSpace context + * @param offset the offset to apply + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the topics list */ - public List findAllTopics(long offset, long count, String orderField, boolean ascending); + public List findAllTopics(Context context, 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 + * @param context the DSpace context + * @param source the source to search for + * @param offset the offset to apply + * @param count the page size + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the topics list */ - public List findAllTopicsBySource(String source, long offset, long count, - String orderField, boolean ascending); + public List findAllTopicsBySource(Context context, String source, long offset, long count, + String orderField, boolean ascending); /** * Count all the event's topics. * - * @return the count result + * @return the count result */ public long countTopics(); /** * Count all the event's topics related to the given source. * + * @param context the DSpace context * @param source the source to search for * @return the count result */ - public long countTopicsBySource(String source); + public long countTopicsBySource(Context context, String source); /** * Find all the events by topic. * + * @param context the DSpace context + * @param sourceName the source name * @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 + * @param size 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 findEventsByTopicAndPage(String topic, long offset, int pageSize, - String orderField, boolean ascending); + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + String orderField, boolean ascending); /** * Find all the events by topic. * - * @param topic the topic to search for - * @return the events + * @param context the DSpace context + * @param sourceName the source name + * @param topic the topic to search for + * @return the events count */ - public List 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); + public long countEventsByTopic(Context context, String sourceName, 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); @@ -126,26 +131,58 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag /** * Find a specific source by the given name. * - * @param source the source name - * @return the source + * @param context the DSpace context + * @param source the source name + * @return the source + */ + public QASource findSource(Context context, String source); + + /** + * Find a specific source by the given name including the stats focused on the target item. + * + * @param context the DSpace context + * @param source the source name + * @param target the uuid of the item target + * @return the source */ - public QASource findSource(String source); + public QASource findSource(Context context, String source, UUID target); /** * Find all the event's sources. * + * @param context the DSpace context * @param offset the offset to apply * @param pageSize the page size * @return the sources list */ - public List findAllSources(long offset, int pageSize); + public List findAllSources(Context context, long offset, int pageSize); /** * Count all the event's sources. * + * @param context the DSpace context * @return the count result */ - public long countSources(); + public long countSources(Context context); + + /** + * Count all the event's sources related to a specific item + * + * @param context the DSpace context + * @param target the item uuid + * @return the count result + */ + public long countSourcesByTarget(Context context, UUID target); + + /** + * Count all the event's topics related to the given source referring to a specific item + * + * @param context the DSpace context + * @param target the item uuid + * @param source the source to search for + * @return the count result + */ + public long countTopicsBySourceAndTarget(Context context, String source, UUID target); /** * Check if the given QA event supports a related item. @@ -155,4 +192,76 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag */ public boolean isRelatedItemSupported(QAEvent qaevent); + /** + * Find a list of QA events according to the pagination parameters for the specified topic and target sorted by + * trust descending + * + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @param offset the offset to apply + * @param pageSize the page size + * @param target the uuid of the QA event's target + * @return the events + */ + public List findEventsByTopicAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize); + + /** + * Check if a qaevent with the provided id is visible to the current user according to the source security + * + * @param context the DSpace context + * @param user the user to consider for the security check + * @param eventId the id of the event to check for existence + * @param source the qa source name + * @return true if the event exists + */ + public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source); + + /** + * Count the QA events related to the specified topic and target + * + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @param target the uuid of the QA event's target + * @return the count result + */ + public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target); + + /** + * Find a specific topic by its name, source and optionally a target. + * + * @param context the DSpace context + * @param sourceName the name of the source + * @param topicName the topic name to search for + * @param target (nullable) the uuid of the target to focus on + * @return the topic + */ + public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName, UUID target); + + /** + * Find all the event's sources related to a specific item + * + * @param context the DSpace context + * @param target the item referring to + * @param offset the offset to apply + * @param pageSize the page size + * @return the source list + */ + public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize); + + /** + * Find all the event's topics related to the given source for a specific item + * + * @param context the DSpace context + * @param source (not null) the source to search for + * @param target the item referring to + * @param offset the offset to apply + * @param pageSize the page size + * @return the topics list + */ + public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, + int pageSize, String orderField, boolean ascending); + } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java new file mode 100644 index 000000000000..e5e38c23966e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.qaevent.service.dto; + +import java.io.Serializable; + +/** + * The CorrectionTypeMessageDTO class implements the QAMessageDTO interface + * and represents a Data Transfer Object (DTO) for holding information + * related to a correction type message in the context of Quality Assurance (QA). + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class CorrectionTypeMessageDTO implements QAMessageDTO, Serializable { + + private static final long serialVersionUID = 2718151302291303796L; + + private String reason; + + public CorrectionTypeMessageDTO() {} + + public CorrectionTypeMessageDTO(String reason) { + this.reason = reason; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java index 2a63f42e615c..ede32ef49757 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java @@ -17,5 +17,4 @@ */ public interface QAMessageDTO { - } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java index cca70ecd0430..e25d914de54d 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java @@ -23,6 +23,7 @@ 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.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.QAEvent; @@ -41,7 +42,8 @@ * */ public class QAEventActionServiceImpl implements QAEventActionService { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventActionServiceImpl.class); + + private static final Logger log = LogManager.getLogger(QAEventActionServiceImpl.class); private ObjectMapper jsonMapper; diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java new file mode 100644 index 000000000000..ca689a79e09a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java @@ -0,0 +1,101 @@ +/** + * 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.util.Map; +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.security.QASecurity; +import org.dspace.qaevent.service.QAEventSecurityService; + +/** + * Implementation of the security service for QAEvents. + * This implementation manages a configuration of {@link QASecurity} instances, + * each responsible for security checks for a specific QA source. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class QAEventSecurityServiceImpl implements QAEventSecurityService { + + /** + * The default security settings to be used when specific configurations are not available for a QA source. + */ + private QASecurity defaultSecurity; + + /** + * A mapping of QA source names to their corresponding QASecurity configurations. + */ + private Map qaSecurityConfiguration; + + public void setQaSecurityConfiguration(Map qaSecurityConfiguration) { + this.qaSecurityConfiguration = qaSecurityConfiguration; + } + + public void setDefaultSecurity(QASecurity defaultSecurity) { + this.defaultSecurity = defaultSecurity; + } + + /** + * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the + * specified user + * + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name + * @return the solr filter query + */ + @Override + public Optional generateQAEventFilterQuery(Context context, EPerson user, String sourceName) { + QASecurity qaSecurity = getQASecurity(sourceName); + return qaSecurity.generateFilterQuery(context, user); + } + + /** + * Retrieves the QASecurity configuration for the specified QA source, or uses the default + * configuration if not available. + * + * @param qaSource The name of the QA source. + * @return The QASecurity configuration for the specified QA source, or the default configuration if not available. + */ + private QASecurity getQASecurity(String qaSource) { + return qaSecurityConfiguration.getOrDefault(qaSource, defaultSecurity); + } + + /** + * Determines whether the user is authorized to see the specified QA event. + * + * @param context the context + * @param user the eperson to consider + * @param qaEvent the qaevent to check + * @return true if the specified user can see the specified event + */ + @Override + public boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent) { + String source = qaEvent.getSource(); + QASecurity qaSecurity = getQASecurity(source); + return qaSecurity.canSeeQASource(context, user) && qaSecurity.canSeeQAEvent(context, user, qaEvent); + } + + /** + * Determines whether the user is authorized to see events from the specified QA source. + * @param context The context. + * @param user The EPerson to consider + * @param sourceName The source name + * + * @return True if the user is authorized to see events from the source, false otherwise. + */ + @Override + public boolean canSeeSource(Context context, EPerson user, String sourceName) { + QASecurity qaSecurity = getQASecurity(sourceName); + return qaSecurity.canSeeQASource(context, user); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 1dfcc1b6d96a..817171bc26cf 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -16,10 +16,14 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.ArrayUtils; @@ -37,18 +41,22 @@ 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.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.eperson.EPerson; 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.QAEventSecurityService; import org.dspace.qaevent.service.QAEventService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -62,14 +70,18 @@ */ public class QAEventServiceImpl implements QAEventService { + private static final Logger log = LoggerFactory.getLogger(QAEventServiceImpl.class); + + public static final String QAEVENTS_SOURCES = "qaevents.sources"; + @Autowired(required = true) protected ConfigurationService configurationService; @Autowired(required = true) - protected ItemService itemService; + protected QAEventSecurityService qaSecurityService; - @Autowired - private HandleService handleService; + @Autowired(required = true) + protected ItemService itemService; @Autowired private QAEventsDAOImpl qaEventsDao; @@ -124,14 +136,19 @@ public long countTopics() { } @Override - public long countTopicsBySource(String source) { + public long countTopicsBySource(Context context, String sourceName) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return 0; + } SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - solrQuery.setQuery("*:*"); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); - solrQuery.addFilterQuery("source:" + source); + solrQuery.addFilterQuery("source:" + sourceName); QueryResponse response; try { response = getSolr().query(solrQuery); @@ -141,6 +158,44 @@ public long countTopicsBySource(String source) { return response.getFacetField(TOPIC).getValueCount(); } + @Override + public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName,UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return null; + } + SolrQuery solrQuery = new SolrQuery(); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setRows(0); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topicName + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + 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(topicName)) { + QATopic topic = new QATopic(); + topic.setSource(sourceName); + 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 void deleteEventByEventId(String id) { try { @@ -189,36 +244,36 @@ public QATopic findTopicByTopicId(String topicId) { } @Override - public List findAllTopics(long offset, long count, String orderField, boolean ascending) { - return findAllTopicsBySource(null, offset, count, orderField, ascending); + public List findAllTopics(Context context, long offset, long count, String orderField, boolean ascending) { + return findAllTopicsBySource(context, null, offset, count, orderField, ascending); } @Override - public List findAllTopicsBySource(String source, long offset, long count, - String orderField, boolean ascending) { - - if (source != null && isNotSupportedSource(source)) { - return null; + public List findAllTopicsBySource(Context context, String sourceName, long offset, long count, + String orderField, boolean ascending) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return List.of(); } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); - solrQuery.setQuery("*:*"); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.setFacetLimit((int) (offset + count)); solrQuery.addFacetField(TOPIC); - if (source != null) { - solrQuery.addFilterQuery(SOURCE + ":" + source); + if (sourceName != null) { + solrQuery.addFilterQuery(SOURCE + ":" + sourceName); } QueryResponse response; List 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) { @@ -226,6 +281,7 @@ public List findAllTopicsBySource(String source, long offset, long coun continue; } QATopic topic = new QATopic(); + topic.setSource(sourceName); topic.setKey(c.getName()); topic.setTotalEvents(c.getCount()); topic.setLastEvent(new Date()); @@ -261,18 +317,52 @@ public void store(Context context, QAEvent dto) { updateRequest.process(getSolr()); getSolr().commit(); + sentEmailToAdminAboutNewRequest(dto); } } catch (Exception e) { throw new RuntimeException(e); } } + /** + * Sends an email notification to the system administrator about a new + * Quality Assurance (QA) request event. The email includes details such as the + * topic, target, and message associated with the QA event. + * + * @param qaEvent The Quality Assurance event for which the notification is generated. + */ + public void sentEmailToAdminAboutNewRequest(QAEvent qaEvent) { + try { + String uiUrl = configurationService.getProperty("dspace.ui.url"); + Email email = Email.getEmail(I18nUtil.getEmailFilename(Locale.getDefault(), "qaevent_admin_notification")); + email.addRecipient(configurationService.getProperty("qaevents.mail.notification")); + email.addArgument(qaEvent.getTopic()); + email.addArgument(uiUrl + "/items/" + qaEvent.getTarget()); + email.addArgument(parsJson(qaEvent.getMessage())); + email.send(); + } catch (Exception e) { + log.warn("Error during sending email of Withdrawn/Reinstate request for item with uuid:" + + qaEvent.getTarget(), e); + } + } + + private String parsJson(String jsonString) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(jsonString); + return jsonNode.get("reason").asText(); + } catch (Exception e) { + log.warn("Unable to parse the JSON:" + jsonString); + return jsonString; + } + } + @Override public QAEvent findEventByEventId(String eventId) { - SolrQuery param = new SolrQuery(EVENT_ID + ":" + eventId); - QueryResponse response; + SolrQuery solrQuery = new SolrQuery("*:*"); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); try { - response = getSolr().query(param); + QueryResponse response = getSolr().query(solrQuery); if (response != null) { SolrDocumentList list = response.getResults(); if (list != null && list.size() == 1) { @@ -287,8 +377,12 @@ public QAEvent findEventByEventId(String eventId) { } @Override - public List findEventsByTopicAndPage(String topic, long offset, - int pageSize, String orderField, boolean ascending) { + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int pageSize, + String orderField, boolean ascending) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return List.of(); + } SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(((Long) offset).intValue()); @@ -296,75 +390,91 @@ public List findEventsByTopicAndPage(String topic, long offset, solrQuery.setRows(pageSize); } solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); - QueryResponse response; try { - response = getSolr().query(solrQuery); + QueryResponse response = getSolr().query(solrQuery); if (response != null) { - SolrDocumentList list = response.getResults(); + SolrDocumentList solrDocuments = response.getResults(); List responseItem = new ArrayList<>(); - for (SolrDocument doc : list) { + for (SolrDocument doc : solrDocuments) { QAEvent item = getQAEventFromSOLR(doc); responseItem.add(item); } return responseItem; } } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } - return List.of(); } @Override - public List findEventsByTopic(String topic) { - return findEventsByTopicAndPage(topic, 0, -1, TRUST, false); - } + public long countEventsByTopic(Context context, String sourceName, String topic) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return 0; + } + + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); - @Override - public long countEventsByTopic(String topic) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - solrQuery.setQuery(TOPIC + ":" + topic.replace("!", "/")); - QueryResponse response = null; + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); try { - response = getSolr().query(solrQuery); - return response.getResults().getNumFound(); + return getSolr().query(solrQuery).getResults().getNumFound(); } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } } @Override - public QASource findSource(String sourceName) { + public QASource findSource(Context context, String sourceName) { + String[] split = sourceName.split(":"); + return findSource(context, split[0], split.length == 2 ? UUID.fromString(split[1]) : null); + } - if (isNotSupportedSource(sourceName)) { + @Override + public QASource findSource(Context context, String sourceName, UUID target) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return null; } - SolrQuery solrQuery = new SolrQuery("*:*"); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setRows(0); - solrQuery.addFilterQuery(SOURCE + ":" + sourceName); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + if (target != null) { + solrQuery.addFilterQuery("resource_uuid:" + target.toString()); + } solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(SOURCE); - QueryResponse response; try { - response = getSolr().query(solrQuery); + QueryResponse 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.setFocus(target); source.setTotalEvents(c.getCount()); source.setLastEvent(new Date()); return source; } } } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } QASource source = new QASource(); @@ -375,18 +485,15 @@ public QASource findSource(String sourceName) { } @Override - public List findAllSources(long offset, int pageSize) { + public List findAllSources(Context context, 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; + .map((sourceName) -> findSource(context, sourceName)) + .filter(Objects::nonNull) + .sorted(comparing(QASource::getTotalEvents) + .reversed()) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); } @Override @@ -405,42 +512,11 @@ private SolrInputDocument createSolrDocument(Context context, QAEvent dto, Strin 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(RESOURCE_UUID, dto.getTarget()); 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)); @@ -456,12 +532,190 @@ private QAEvent getQAEventFromSOLR(SolrDocument doc) { return item; } + @Override + public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source) { + SolrQuery solrQuery = new SolrQuery(); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, user, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + if (response != null) { + return response.getResults().getNumFound() == 1; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Exception querying Solr", e); + } + return false; + } + + @Override + public long countEventsByTopicAndTarget(Context context, String sourceName, String topic, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); + QueryResponse response = null; + try { + response = getSolr().query(solrQuery); + return response.getResults().getNumFound(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findEventsByTopicAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setStart(((Long) offset).intValue()); + solrQuery.setRows(pageSize); + solrQuery.setSort(TRUST, ORDER.desc); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); + + try { + QueryResponse response = getSolr().query(solrQuery); + if (response != null) { + SolrDocumentList list = response.getResults(); + List 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(); + } + private boolean isNotSupportedSource(String source) { return !ArrayUtils.contains(getSupportedSources(), source); } private String[] getSupportedSources() { - return configurationService.getArrayProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); + return configurationService.getArrayProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); + } + + @Override + public long countSources(Context context) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); + } + + @Override + public long countSourcesByTarget(Context context, UUID target) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName, target)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); + } + + @Override + public long countTopicsBySourceAndTarget(Context context, String source, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + try { + QueryResponse response = getSolr().query(solrQuery); + return response.getFacetField(TOPIC).getValueCount(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName, target)) + .filter(Objects::nonNull) + .sorted(comparing(QASource::getTotalEvents).reversed()) + .filter(source -> source.getTotalEvents() > 0) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); + } + + @Override + public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, + int pageSize, String orderField, boolean ascending) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); + } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.setFacetLimit((int) (offset + pageSize)); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":" + target.toString()); + } + try { + List topics = new ArrayList<>(); + QueryResponse response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + int idx = 0; + for (Count c : facetField.getValues()) { + if (idx < offset) { + idx++; + continue; + } + QATopic topic = new QATopic(); + topic.setSource(source); + topic.setKey(c.getName()); + topic.setFocus(target); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + topics.add(topic); + idx++; + } + return topics; + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } } } diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 6bb979f48be8..9769f72dcde9 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -10,11 +10,13 @@ import static java.util.List.of; import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; 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.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -32,6 +34,7 @@ import java.io.FileInputStream; import java.io.OutputStream; import java.net.URL; +import java.util.List; import eu.dnetlib.broker.BrokerClient; import org.apache.commons.io.IOUtils; @@ -43,11 +46,15 @@ import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.QAEvent; import org.dspace.matcher.QASourceMatcher; import org.dspace.matcher.QATopicMatcher; +import org.dspace.qaevent.QANotifyPatterns; +import org.dspace.qaevent.QATopic; import org.dspace.qaevent.service.OpenaireClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.impl.OpenaireClientFactoryImpl; +import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.junit.After; import org.junit.Before; @@ -61,9 +68,8 @@ */ 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 static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class); @@ -73,11 +79,17 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase private BrokerClient mockBrokerClient = mock(BrokerClient.class); + private ConfigurationService configurationService = new DSpace().getConfigurationService(); + @Before public void setup() { context.turnOffAuthorisationSystem(); - + // we want all the test in this class to be run using the administrator user as OpenAIRE events are only visible + // to administrators. + // Please note that test related to forbidden and unauthorized access to qaevent are included in + // the QAEventRestRepositoryIT here we are only testing that the import script is creating the expected events + context.setCurrentUser(admin); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -87,7 +99,7 @@ public void setup() { .build(); context.restoreAuthSystemState(); - + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); } @@ -136,7 +148,6 @@ public void testWithBothFileAndEmailParameters() throws Exception { } @Test - @SuppressWarnings("unchecked") public void testManyEventsImportFromFile() throws Exception { context.turnOffAuthorisationSystem(); @@ -157,14 +168,15 @@ public void testManyEventsImportFromFile() throws Exception { "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.findAllSources(context, 0, 20), hasItem(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))); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," @@ -172,19 +184,20 @@ public void testManyEventsImportFromFile() throws Exception { + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0, 20, + ORDER_FIELD, false), 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( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE,"ENRICH/MISSING/ABSTRACT", 0, 20, + ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -203,32 +216,32 @@ public void testManyEventsImportFromFileWithUnknownHandle() throws Exception { 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")); + contains("An error occurs storing the event with id 406fb9c5656c7f11cac8995abb746887: " + + "Skipped event 406fb9c5656c7f11cac8995abb746887 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 eafd747feee49cca7603d30ba4e768dc: " + + "Skipped event eafd747feee49cca7603d30ba4e768dc 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.findAllSources(context, 0, 20), hasItem(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) - )); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20, + ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -248,24 +261,26 @@ public void testManyEventsImportFromFileWithUnknownTopic() throws Exception { assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), - contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + contains("An error occurs storing the event with id 8307aa56769deba961faed7162d91aab:" + + " Skipped event 8307aa56769deba961faed7162d91aab 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 2 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), - contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, ORDER_FIELD, false), + contains(QATopicMatcher.with(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", - abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -281,15 +296,14 @@ public void testImportFromFileWithoutEvents() throws Exception { 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.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20, ORDER_FIELD, false), empty()); verifyNoInteractions(mockBrokerClient); } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBroker() throws Exception { context.turnOffAuthorisationSystem(); @@ -328,14 +342,14 @@ public void testImportFromOpenaireBroker() throws Exception { "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.findAllSources(context, 0, 20), hasItem(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))); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE,0,20,ORDER_FIELD,false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," @@ -343,14 +357,16 @@ public void testImportFromOpenaireBroker() throws Exception { + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0 , 20, + ORDER_FIELD, false), 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( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20, + ORDER_FIELD, false), 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", @@ -382,9 +398,9 @@ public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws 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.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20,ORDER_FIELD, false), empty()); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); @@ -393,7 +409,6 @@ public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { context.turnOffAuthorisationSystem(); @@ -433,17 +448,19 @@ public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws E "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.findAllSources(context, 0, 20), hasItem(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))); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE,0,20,ORDER_FIELD,false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1)); - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20, ORDER_FIELD, false), hasSize(1)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, false), hasSize(2)); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); @@ -451,7 +468,6 @@ public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws E verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); verifyNoMoreInteractions(mockBrokerClient); - } private Item createItem(String title, String handle) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java index 99e91e6998ea..b3afdf7945ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java @@ -78,7 +78,6 @@ public ResponseEntity> addRelatedItem(@PathVariable(name @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); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java index c20668fcc551..15438a5e9aa4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java @@ -15,7 +15,6 @@ import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.DefaultProjection; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; @@ -34,8 +33,6 @@ description = "It can be used to verify if the user can delete a version of an Item") public class CanDeleteVersionFeature extends DeleteFeature { - @Autowired - private ItemService itemService; @Autowired private ItemConverter itemConverter; @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java new file mode 100644 index 000000000000..58330fdfae90 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java @@ -0,0 +1,38 @@ +/** + * 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.CorrectionTypeRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.correctiontype.CorrectionType; +import org.springframework.stereotype.Component; + +/** + * This class provides the method to convert a CorrectionType to its REST representation, the + * CorrectionTypeRest + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public class CorrectionTypeConverter implements DSpaceConverter { + + @Override + public CorrectionTypeRest convert(CorrectionType target, Projection projection) { + CorrectionTypeRest targetRest = new CorrectionTypeRest(); + targetRest.setProjection(projection); + targetRest.setId(target.getId()); + targetRest.setTopic(target.getTopic()); + return targetRest; + } + + @Override + public Class getModelClass() { + return CorrectionType.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java index a32c0ddc99a9..0cb73d94e318 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java @@ -8,17 +8,21 @@ package org.dspace.app.rest.converter; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; 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.CorrectionTypeQAEventMessageRest; 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.CorrectionTypeMessageDTO; import org.dspace.qaevent.service.dto.OpenaireMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.services.ConfigurationService; @@ -54,16 +58,18 @@ public QAEventRest convert(QAEvent modelObject, Projection projection) { rest.setId(modelObject.getEventId()); try { rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), - modelObject.getMessageDtoClass()))); + modelObject.getMessageDtoClass()))); } catch (JsonProcessingException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } + rest.setSource(modelObject.getSource()); 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())); + DecimalFormat decimalFormat = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.ENGLISH)); + rest.setTrust(decimalFormat.format(modelObject.getTrust())); // right now only the pending status can be found in persisted qa events rest.setStatus(modelObject.getStatus()); return rest; @@ -73,9 +79,19 @@ private QAEventMessageRest convertMessage(QAMessageDTO dto) { if (dto instanceof OpenaireMessageDTO) { return convertOpenaireMessage(dto); } + if (dto instanceof CorrectionTypeMessageDTO) { + return convertCorrectionTypeMessage(dto); + } throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); } + private QAEventMessageRest convertCorrectionTypeMessage(QAMessageDTO dto) { + CorrectionTypeMessageDTO correctionTypeDto = (CorrectionTypeMessageDTO) dto; + CorrectionTypeQAEventMessageRest message = new CorrectionTypeQAEventMessageRest(); + message.setReason(correctionTypeDto.getReason()); + return message; + } + private QAEventMessageRest convertOpenaireMessage(QAMessageDTO dto) { OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto; OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java index 6c1bc0d66cb7..8b651d821d54 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java @@ -31,7 +31,8 @@ public Class getModelClass() { public QASourceRest convert(QASource modelObject, Projection projection) { QASourceRest rest = new QASourceRest(); rest.setProjection(projection); - rest.setId(modelObject.getName()); + rest.setId(modelObject.getName() + (modelObject.getFocus() != null ? ":" + modelObject.getFocus().toString() + : "")); rest.setLastEvent(modelObject.getLastEvent()); rest.setTotalEvents(modelObject.getTotalEvents()); return rest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java index efa32baba224..a40ae527b192 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java @@ -31,7 +31,8 @@ public Class getModelClass() { public QATopicRest convert(QATopic modelObject, Projection projection) { QATopicRest rest = new QATopicRest(); rest.setProjection(projection); - rest.setId(modelObject.getKey().replace("/", "!")); + rest.setId(modelObject.getSource() + ":" + modelObject.getKey().replace("/", "!") + + (modelObject.getFocus() != null ? ":" + modelObject.getFocus().toString() : "")); rest.setName(modelObject.getKey()); rest.setLastEvent(modelObject.getLastEvent()); rest.setTotalEvents(modelObject.getTotalEvents()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java new file mode 100644 index 000000000000..6929f390712c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java @@ -0,0 +1,29 @@ +/** + * 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; + +/** + * The CorrectionTypeQAEventMessageRest class implements the QAEventMessageRest + * interface and represents a message structure for Quality Assurance (QA) + * events related to correction types. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class CorrectionTypeQAEventMessageRest implements QAEventMessageRest { + + private String reason; + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java new file mode 100644 index 000000000000..6a26313f9220 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java @@ -0,0 +1,57 @@ +/** + * 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; +import org.dspace.app.rest.RestResourceController; + +/** + * The CorrectionType REST Resource + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class CorrectionTypeRest extends BaseObjectRest { + + private static final long serialVersionUID = -8297846719538025938L; + + public static final String NAME = "correctiontype"; + public static final String PLURAL_NAME = "correctiontypes"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + private String topic; + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java index 5ce25c8f9d69..5a74d335310d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java @@ -19,9 +19,9 @@ */ @LinksRest( links = { - @LinkRest(name = "topic", method = "getTopic"), - @LinkRest(name = "target", method = "getTarget"), - @LinkRest(name = "related", method = "getRelated") + @LinkRest(name = "topic", method = "getTopic"), + @LinkRest(name = "target", method = "getTarget"), + @LinkRest(name = "related", method = "getRelated") }) public class QAEventRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CorrectionTypeResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CorrectionTypeResource.java new file mode 100644 index 000000000000..cb1ba5552fef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CorrectionTypeResource.java @@ -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.CorrectionTypeRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * CorrectionType Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@RelNameDSpaceResource(CorrectionTypeRest.NAME) +public class CorrectionTypeResource extends DSpaceResource { + + public CorrectionTypeResource(CorrectionTypeRest target, Utils utils) { + super(target, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java new file mode 100644 index 000000000000..14dc7d9fb265 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.CorrectionTypeRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; +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; + +/** + * The CorrectionType REST Repository + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component(CorrectionTypeRest.CATEGORY + "." + CorrectionTypeRest.PLURAL_NAME) +public class CorrectionTypeRestRepository extends DSpaceRestRepository { + + @Autowired + private ItemService itemService; + @Autowired + private CorrectionTypeService correctionTypeService; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public CorrectionTypeRest findOne(Context context, String id) { + CorrectionType correctionType = correctionTypeService.findOne(id); + return Objects.nonNull(correctionType) ? converter.toRest(correctionType, utils.obtainProjection()) : null; + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public Page findAll(Context context, Pageable pageable) { + return converter.toRestPage(correctionTypeService.findAll(), pageable, utils.obtainProjection()); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findByItem") + public Page findByItem(@Parameter(value = "uuid",required = true) UUID uuid,Pageable pageable) { + Context context = obtainContext(); + try { + Item item = itemService.find(context, uuid); + if (Objects.isNull(item)) { + throw new UnprocessableEntityException("Item with uuid:" + uuid + " not found"); + } + + List correctionTypes; + try { + correctionTypes = correctionTypeService.findByItem(context, item); + } catch (AuthorizeException e) { + throw new RESTAuthorizationException(e); + } + + return converter.toRestPage(correctionTypes, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findByTopic") + public CorrectionTypeRest findByTopic(@Parameter(value = "topic", required = true) String topic) { + CorrectionType correctionType = correctionTypeService.findByTopic(topic); + return Objects.nonNull(correctionType) ? converter.toRest(correctionType, utils.obtainProjection()) : null; + } + + @Override + public Class getDomainClass() { + return CorrectionTypeRest.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java index cc287b19bec3..81f9aaab5850 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java @@ -51,7 +51,7 @@ public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository i * @param projection the projection object * @return the item rest representation of the secondary item related to qa event */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index 0da8a56c9307..7e80fd1c92fb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -7,13 +7,20 @@ */ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ResourcePatch; @@ -22,14 +29,16 @@ import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; import org.dspace.eperson.EPerson; import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; 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; @@ -56,8 +65,16 @@ public class QAEventRestRepository extends DSpaceRestRepository resourcePatch; + @Autowired + private CorrectionTypeService correctionTypeService; + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); + } + @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QAEventRest findOne(Context context, String id) { QAEvent qaEvent = qaEventService.findEventByEventId(id); if (qaEvent == null) { @@ -73,26 +90,25 @@ public QAEventRest findOne(Context context, String id) { } @SearchRestMethod(name = "findByTopic") - @PreAuthorize("hasAuthority('ADMIN')") - public Page findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, - Pageable pageable) { - List 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) { + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTopic(@Parameter(value = "topic", required = true) String topic, Pageable pageable) { + Context context = obtainContext(); + String[] topicIdSplitted = topic.split(":", 3); + if (topicIdSplitted.length < 2) { return null; } + String sourceName = topicIdSplitted[0]; + String topicName = topicIdSplitted[1].replaceAll("!", "/"); + UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; + List qaEvents = qaEventService.findEventsByTopicAndTarget(context, sourceName, topicName, target, + pageable.getOffset(), + pageable.getPageSize()); + long count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target); return converter.toRestPage(qaEvents, pageable, count, utils.obtainProjection()); } @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'DELETE')") protected void delete(Context context, String eventId) throws AuthorizeException { Item item = findTargetItem(context, eventId); EPerson eperson = context.getCurrentUser(); @@ -101,12 +117,7 @@ protected void delete(Context context, String eventId) throws AuthorizeException } @Override - public Page findAll(Context context, Pageable pageable) { - throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); - } - - @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'WRITE')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, String id, Patch patch) throws SQLException, AuthorizeException { QAEvent qaEvent = qaEventService.findEventByEventId(id); @@ -126,6 +137,64 @@ private Item findTargetItem(Context context, String eventId) { } } + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + protected QAEventRest createAndReturn(Context context) throws SQLException, AuthorizeException { + ServletRequest request = getRequestService().getCurrentRequest().getServletRequest(); + + String itemUUID = request.getParameter("target"); + String relatedItemUUID = request.getParameter("related"); + String correctionTypeStr = request.getParameter("correctionType"); + + + if (StringUtils.isBlank(correctionTypeStr) || StringUtils.isBlank(itemUUID)) { + throw new UnprocessableEntityException("The target item and correctionType must be provided!"); + } + + Item targetItem = null; + Item relatedItem = null; + try { + targetItem = itemService.find(context, UUID.fromString(itemUUID)); + relatedItem = StringUtils.isNotBlank(relatedItemUUID) ? + itemService.find(context, UUID.fromString(relatedItemUUID)) : null; + } catch (Exception e) { + throw new UnprocessableEntityException(e.getMessage(), e); + } + + if (Objects.isNull(targetItem)) { + throw new UnprocessableEntityException("The target item UUID is not valid!"); + } + + CorrectionType correctionType = correctionTypeService.findOne(correctionTypeStr); + if (Objects.isNull(correctionType)) { + throw new UnprocessableEntityException("The given correction type in the request is not valid!"); + } + + if (correctionType.isRequiredRelatedItem() && Objects.isNull(relatedItem)) { + throw new UnprocessableEntityException("The given correction type in the request is not valid!"); + } + + if (!correctionType.isAllowed(context, targetItem)) { + throw new UnprocessableEntityException("This item cannot be processed by this correction type!"); + } + + ObjectMapper mapper = new ObjectMapper(); + CorrectionTypeMessageDTO reason = null; + try { + reason = mapper.readValue(request.getInputStream(), CorrectionTypeMessageDTO.class); + } catch (IOException exIO) { + throw new UnprocessableEntityException("error parsing the body " + exIO.getMessage(), exIO); + } + + QAEvent qaEvent; + if (correctionType.isRequiredRelatedItem()) { + qaEvent = correctionType.createCorrection(context, targetItem, relatedItem, reason); + } else { + qaEvent = correctionType.createCorrection(context, targetItem, reason); + } + return converter.toRest(qaEvent, utils.obtainProjection()); + } + @Override public Class getDomainClass() { return QAEventRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java index a5f7051c5499..74dbccc467d9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java @@ -48,9 +48,9 @@ public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository im * @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 + * @return the item rest representation of the qa event target */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java index 489f27bd92a0..222fbd218ebe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java @@ -42,9 +42,9 @@ public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository imp * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the qa topic rest representation + * @return the qa topic rest representation */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); @@ -52,9 +52,11 @@ public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nu if (qaEvent == null) { throw new ResourceNotFoundException("No qa event with ID: " + id); } - QATopic topic = qaEventService.findTopicByTopicId(qaEvent.getTopic().replace("/", "!")); + String source = qaEvent.getSource(); + String topicName = qaEvent.getTopic(); + QATopic topic = qaEventService.findTopicBySourceAndNameAndTarget(context, source, topicName, null); if (topic == null) { - throw new ResourceNotFoundException("No topic found with id : " + id); + throw new ResourceNotFoundException("No topic found with source: " + source + " topic: " + topicName); } return converter.toRest(topic, projection); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java index 3ad7345d459a..a227f81dadb2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java @@ -8,7 +8,10 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.UUID; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.QASourceRest; import org.dspace.core.Context; import org.dspace.qaevent.QASource; @@ -32,9 +35,9 @@ public class QASourceRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List qaSources = qaEventService.findAllSources(pageable.getOffset(), pageable.getPageSize()); - long count = qaEventService.countSources(); + List qaSources = qaEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSources(context); return converter.toRestPage(qaSources, pageable, count, utils.obtainProjection()); } + @SearchRestMethod(name = "byTarget") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTarget(@Parameter(value = "target", required = true) UUID target, + Pageable pageable) { + Context context = obtainContext(); + List topics = qaEventService.findAllSourcesByTarget(context, target, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSourcesByTarget(context, target); + if (topics == null) { + return null; + } + return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); + } @Override public Class getDomainClass() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index 8b1e49218682..b6542229b15d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -8,9 +8,11 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.UUID; 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.QATopicRest; import org.dspace.core.Context; import org.dspace.qaevent.QATopic; @@ -37,43 +39,54 @@ public class QATopicRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("Method not allowed!", ""); + } + + @Override + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCETOPIC', 'READ')") public QATopicRest findOne(Context context, String id) { - QATopic topic = qaEventService.findTopicByTopicId(id); - if (topic == null) { + String[] topicIdSplitted = id.split(":", 3); + if (topicIdSplitted.length < 2) { return null; } - return converter.toRest(topic, utils.obtainProjection()); + String sourceName = topicIdSplitted[0]; + String topicName = topicIdSplitted[1].replaceAll("!", "/"); + UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; + QATopic topic = qaEventService.findTopicBySourceAndNameAndTarget(context, sourceName, topicName, target); + return (topic != null) ? converter.toRest(topic, utils.obtainProjection()) : null; } - @Override - @PreAuthorize("hasAuthority('ADMIN')") - public Page findAll(Context context, Pageable pageable) { + @SearchRestMethod(name = "bySource") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findBySource(@Parameter(value = "source", required = true) String source, + Pageable pageable) { + Context context = obtainContext(); boolean ascending = false; if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { - ascending = pageable.getSort() - .getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - List topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize(), - ORDER_FIELD, ascending); - long count = qaEventService.countTopics(); + List topics = qaEventService.findAllTopicsBySource(context, source, + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + long count = qaEventService.countTopicsBySource(context, source); if (topics == null) { return null; } return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); } - @SearchRestMethod(name = "bySource") - @PreAuthorize("hasAuthority('ADMIN')") - public Page findBySource(Context context, + @SearchRestMethod(name = "byTarget") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTarget(@Parameter(value = "target", required = true) UUID target, @Parameter(value = "source", required = true) String source, Pageable pageable) { + Context context = obtainContext(); boolean ascending = false; if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - List topics = qaEventService.findAllTopicsBySource(source, - pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); - long count = qaEventService.countTopicsBySource(source); + List topics = qaEventService.findAllTopicsBySourceAndTarget(context, source, target, + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + long count = qaEventService.countTopicsBySourceAndTarget(context, source, target); if (topics == null) { return null; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceCorrectionTypeUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceCorrectionTypeUriListHandler.java new file mode 100644 index 000000000000..f3776e217ed2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceCorrectionTypeUriListHandler.java @@ -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.app.rest.repository.handler; + +import java.sql.SQLException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This class extends the {@link ExternalSourceEntryItemUriListHandler} abstract class and implements it specifically + * for the List<{@link CorrectionType}> objects. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public class ExternalSourceCorrectionTypeUriListHandler extends ExternalSourceEntryItemUriListHandler { + + @Autowired + private CorrectionTypeService correctionTypeService; + + @Override + @SuppressWarnings("rawtypes") + public boolean supports(List uriList, String method,Class clazz) { + return clazz != CorrectionType.class ? false : true; + } + + @Override + public CorrectionType handle(Context context, HttpServletRequest request, List uriList) + throws SQLException, AuthorizeException { + return getObjectFromUriList(context, uriList); + } + + @Override + public boolean validate(Context context, HttpServletRequest request, List uriList) + throws AuthorizeException { + return uriList.size() > 1 ? false : true; + } + + + private CorrectionType getObjectFromUriList(Context context, List uriList) { + CorrectionType correctionType = null; + String url = uriList.get(0); + Pattern pattern = Pattern.compile("\\/api\\/config\\/correctiontypes\\/(.*)"); + Matcher matcher = pattern.matcher(url); + if (!matcher.find()) { + throw new DSpaceBadRequestException("The uri: " + url + " doesn't resolve to an correction type"); + } + String id = matcher.group(1); + try { + correctionType = correctionTypeService.findOne(id); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + return correctionType; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..8ffc6be5e193 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java @@ -0,0 +1,74 @@ +/** + * 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.security; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.QAEventRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle Permissions for the {@link QAEventRest} object and its calls + * + * @author Andrea Bollini (4Science) + */ +@Component +public class QAEventRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(QAEventRestPermissionEvaluatorPlugin.class); + + @Autowired + private QAEventService qaEventService; + + @Autowired + private QAEventSecurityService qaEventSecurityService; + + @Autowired + private RequestService requestService; + + /** + * Responsible for checking whether or not the user has access to the requested QASource + * + * @param targetType the type of Rest Object that should be checked for permission. This class would deal only with + * qaevent + * @param targetId string to extract the sourcename from + */ + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (StringUtils.equalsIgnoreCase(QAEventRest.NAME, targetType)) { + log.debug("Checking permission for targetId {}", targetId); + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + if (Objects.isNull(targetId)) { + return true; + } + QAEvent qaEvent = qaEventService.findEventByEventId(targetId.toString()); + // everyone is expected to be able to see a not existing event (so we can return not found) + if ((qaEvent == null + || qaEventSecurityService.canSeeEvent(context, context.getCurrentUser(), qaEvent))) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..3d11b4d099e1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java @@ -0,0 +1,71 @@ +/** + * 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.security; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.QASourceRest; +import org.dspace.app.rest.model.QATopicRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle Permissions for the {@link QASourceRest} object and {@link QATopic} + * + * @author Andrea Bollini (4Science) + */ +@Component +public class QASourceRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(QASourceRestPermissionEvaluatorPlugin.class); + + @Autowired + private QAEventSecurityService qaEventSecurityService; + + @Autowired + private RequestService requestService; + + /** + * Responsible for checking whether or not the user has access to the requested QASource + * + * @param targetType the type of Rest Object that should be checked for permission. This class would deal only with + * qasource and qatopic + * @param targetId string to extract the sourcename from + */ + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (StringUtils.equalsIgnoreCase(QASourceRest.NAME, targetType) + || StringUtils.equalsIgnoreCase(QATopicRest.NAME, targetType)) { + log.debug("Checking permission for targetId {}", targetId); + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + if (Objects.isNull(targetId)) { + return true; + } + // the source name is always the first part of the id both for a source than a topic + // users can see all the topic in source that they can access, eventually they will have no + // events visible to them + String sourceName = targetId.toString().split(":")[0]; + return qaEventSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName); + } + return false; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java new file mode 100644 index 000000000000..6e720fd63f58 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java @@ -0,0 +1,308 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +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.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.UUID; + +import org.dspace.app.rest.repository.CorrectionTypeRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.service.AuthorizeService; +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.content.service.ItemService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +/** + * Test suite for {@link CorrectionTypeRestRepository} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ItemService itemService; + @Autowired + private AuthorizeService authorizeService; + + @Test + public void findAllAdminTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.type", equalTo("correctiontype")) + ), + allOf( + hasJsonPath("$.id", equalTo("request-reinstate")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")), + hasJsonPath("$.type", equalTo("correctiontype")) + ) + ))); + } + + @Test + public void findAllEPersonTest() throws Exception { + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/config/correctiontypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.type", equalTo("correctiontype")) + ), + allOf( + hasJsonPath("$.id", equalTo("request-reinstate")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")), + hasJsonPath("$.type", equalTo("correctiontype")) + ) + ))); + } + + @Test + public void findAllUnauthorizedTest() throws Exception { + getClient().perform(get("/api/config/correctiontypes")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneAdminTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/request-withdrawn")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); + } + + @Test + public void findOneEPersonTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/config/correctiontypes/request-withdrawn")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); + } + + @Test + public void findOneUnauthorizedTest() throws Exception { + getClient().perform(get("/api/config/correctiontypes/request-withdrawn")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneNotFoundTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/test")) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemWithoutUUIDParameterTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findByItemNotFoundTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", UUID.randomUUID().toString())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void findByItemUnAuthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); + Item privateItem = ItemBuilder.createItem(context, collection).build(); + authorizeService.removeAllPolicies(context, privateItem); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", privateItem.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByNotArchivedItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + item.setArchived(false); + itemService.update(context, item); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void findByWithdrawnItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + Item item = ItemBuilder.createItem(context, collection) + .withdrawn() + .build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder(allOf( + hasJsonPath("$.id", equalTo("request-reinstate")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")) + )))); + } + + @Test + public void findByNotDiscoverableItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + item.setDiscoverable(false); + itemService.update(context, item); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.type", equalTo("correctiontype")) + ) + ))); + } + + @Test + public void findByPersonalArchiveItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); + Item itemOne = ItemBuilder.createItem(context, collection) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", itemOne.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.type", equalTo("correctiontype")) + ) + ))); + + } + + @Test + public void findByItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.type", equalTo("correctiontype")) + ) + ))); + } + + @Test + public void findByTopicWithoutTopicParameterTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findByWrongTopicTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic") + .param("topic", "wrongValue")) + .andExpect(status().isNoContent()); + } + + @Test + public void findByTopicAdminTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic") + .param("topic", "REQUEST/WITHDRAWN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); + } + + @Test + public void findByTopicEPersonTest() throws Exception { + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/config/correctiontypes/search/findByTopic") + .param("topic", "REQUEST/WITHDRAWN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); + } + + @Test + public void findByTopicUnauthorizedTest() throws Exception { + getClient().perform(get("/api/config/correctiontypes/search/findByTopic") + .param("topic", "REQUEST/WITHDRAWN")) + .andExpect(status().isUnauthorized()); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 592b65945767..293152dbe88e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -7,9 +7,13 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; 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.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +import static org.dspace.correctiontype.WithdrawnCorrectionType.WITHDRAWAL_REINSTATE_GROUP; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -26,8 +30,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.QAEventMatcher; import org.dspace.app.rest.model.patch.Operation; @@ -35,7 +42,9 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.QAEventBuilder; import org.dspace.builder.RelationshipTypeBuilder; @@ -44,7 +53,12 @@ import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.QAEventProcessed; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.qaevent.QANotifyPatterns; import org.dspace.qaevent.dao.QAEventsDAO; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -59,16 +73,24 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private QAEventsDAO qaEventsDao; + @Autowired + private ConfigurationService configurationService; @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); + .andExpect(status() + .isMethodNotAllowed()); + + String epersonToken = getAuthToken(eperson.getEmail(), password); getClient(epersonToken).perform(get("/api/integration/qualityassuranceevents")) - .andExpect(status().isMethodNotAllowed()); - getClient().perform(get("/api/integration/qualityassuranceevents")).andExpect(status().isMethodNotAllowed()); + .andExpect(status() + .isMethodNotAllowed()); + + getClient().perform(get("/api/integration/qualityassuranceevents")) + .andExpect(status() + .isMethodNotAllowed()); } @Test @@ -95,48 +117,63 @@ public void findOneTest() throws Exception { @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(); + 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(); + .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(); + .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))); + 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(); + 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(); + .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()); + .andExpect(status().isUnauthorized()); } @Test @@ -164,7 +201,7 @@ public void findByTopicTest() throws Exception { 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") + 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") @@ -172,25 +209,31 @@ public void findByTopicTest() throws Exception { .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))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "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", OPENAIRE_SOURCE + ":" + "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 @@ -215,111 +258,108 @@ public void findByTopicPaginatedTest() throws Exception { .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"), + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "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=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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))); + .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"), + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "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=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.last.href",Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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"), + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "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))); + .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", OPENAIRE_SOURCE + ":" + "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=" + OPENAIRE_SOURCE + ":" + "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=" + OPENAIRE_SOURCE + ":" + "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=" + OPENAIRE_SOURCE + ":" + "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=" + OPENAIRE_SOURCE + ":" + "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 @@ -327,48 +367,23 @@ 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") + 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") + 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") + 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") + 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()); + getClient().perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID")) + .andExpect(status().isUnauthorized()); } @Test @@ -376,16 +391,16 @@ 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") + 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") + 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") + 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") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); @@ -399,20 +414,28 @@ 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(); + + 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(); + .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(); + .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") + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT) .withMessage( "{\"projects[0].acronym\":\"PAThs\"," + "\"projects[0].code\":\"687567\"," @@ -428,7 +451,7 @@ public void recordDecisionTest() throws Exception { .build(); QAEvent eventProjectNoBound = QAEventBuilder .createTarget(context, col1, "Science and Freedom with unrelated project") - .withTopic("ENRICH/MISSING/PROJECT") + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT) .withMessage( "{\"projects[0].acronym\":\"NEW\"," + "\"projects[0].code\":\"123456\"," @@ -439,35 +462,45 @@ public void recordDecisionTest() throws Exception { + "\"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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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") + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) + .withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}") + .build(); context.restoreAuthSystemState(); + // prepare the different patches for our decisions List acceptOp = new ArrayList(); acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + List acceptOpUppercase = new ArrayList(); acceptOpUppercase.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + List discardOp = new ArrayList(); discardOp.add(new ReplaceOperation("/status", QAEvent.DISCARDED)); + List rejectOp = new ArrayList(); rejectOp.add(new ReplaceOperation("/status", QAEvent.REJECTED)); + String patchAccept = getPatchContent(acceptOp); String patchAcceptUppercase = getPatchContent(acceptOpUppercase); String patchDiscard = getPatchContent(discardOp); @@ -484,82 +517,95 @@ public void recordDecisionTest() throws Exception { 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))); + .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))); + .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))); + .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))); + .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")))); + .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")))); + .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")))); + .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())))); + .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()))))); + .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...")))); + .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))); + .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']"))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasNoJsonPath("$.metadata['dc.identifier.other']"))); + // discard abstractToDiscard eventAbstractToDiscard.setStatus(QAEvent.DISCARDED); getClient(authToken) @@ -568,17 +614,18 @@ public void recordDecisionTest() throws Exception { .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']"))); + .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 + getClient(authToken).perform(get("/api/integration/qualityassurancesources/" + QAEvent.OPENAIRE_SOURCE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalEvents", is(0))); } @Test @@ -706,39 +753,49 @@ public void setInvalidRelatedTest() throws Exception { @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(); + 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(); + .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(); + .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(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "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)); + .andExpect(status().is(204)); getClient(authToken).perform(get("/api/core/items/" + event1.getTarget())) - .andExpect(status().is(404)); + .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))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "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 @@ -759,7 +816,7 @@ public void testEventDeletion() throws Exception { .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") .build(); - QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") .build(); @@ -790,12 +847,238 @@ public void testEventDeletion() throws Exception { 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()); + @Test + public void createQAEventByCorrectionTypeUnAuthorizedTest() throws Exception { + getClient().perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", UUID.randomUUID().toString()) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } - authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event2.getEventId())) - .andExpect(status().isForbidden()); + @Test + public void createQAEventByCorrectionTypeWithMissingTargetTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } + + @Test + public void createQAEventByCorrectionTypeWithdrawnRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty(WITHDRAWAL_REINSTATE_GROUP, "Anonymous"); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection for Publications") + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, col) + .withTitle("Publication archived item") + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + + AtomicReference idRef = new AtomicReference(); + + CorrectionTypeMessageDTO message = new CorrectionTypeMessageDTO("reasone"); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .content(new ObjectMapper().writeValueAsBytes(message)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) + .andExpect(jsonPath("$.title", is(publication.getName()))) + .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.trust", is("1.000"))) + .andExpect(jsonPath("$.status", is("PENDING"))); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + + getClient(adminToken).perform(patch("/api/integration/qualityassuranceevents/" + idRef.get()) + .content(getPatchContent(acceptOp)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + } + + @Test + public void createQAEventByCorrectionTypeReinstateRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty(WITHDRAWAL_REINSTATE_GROUP, "Anonymous"); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection for Publications") + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, col) + .withTitle("Publication archived item") + .withdrawn() + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + + AtomicReference idRef = new AtomicReference(); + + ObjectMapper mapper = new ObjectMapper(); + CorrectionTypeMessageDTO dto = new CorrectionTypeMessageDTO("provided reason!"); + + getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-reinstate") + .param("target", publication.getID().toString()) + .contentType(contentType) + .content(mapper.writeValueAsBytes(dto))) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) + .andExpect(jsonPath("$.title", is(publication.getName()))) + .andExpect(jsonPath("$.topic", is("REQUEST/REINSTATE"))) + .andExpect(jsonPath("$.trust", is("1.000"))) + .andExpect(jsonPath("$.status", is("PENDING"))); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + + getClient(adminToken).perform(patch("/api/integration/qualityassuranceevents/" + idRef.get()) + .content(getPatchContent(acceptOp)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + } + + @Test + public void createQAEventOnlyUserPresentInWithdrawalReinstateGroupTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson user1 = EPersonBuilder.createEPerson(context) + .withEmail("eperson-test@mail.com") + .withPassword(password) + .build(); + + Group withdrawalGroup = GroupBuilder.createGroup(context) + .withName("WithdrawGroup") + .addMember(user1) + .build(); + + configurationService.setProperty(WITHDRAWAL_REINSTATE_GROUP, withdrawalGroup.getName()); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection for Publications") + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, col) + .withTitle("Publication archived item") + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + AtomicReference idRef = new AtomicReference(); + + CorrectionTypeMessageDTO message = new CorrectionTypeMessageDTO("reasone"); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + // eperson is not present into the withdraw-reinstate group + // and so cannot make the request + getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .content(new ObjectMapper().writeValueAsBytes(message)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + String user1Token = getAuthToken(user1.getEmail(), password); + // instead user1 is present into the withdraw-reinstate group + getClient(user1Token).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .content(new ObjectMapper().writeValueAsBytes(message)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) + .andExpect(jsonPath("$.title", is(publication.getName()))) + .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.trust", is("1.000"))) + .andExpect(jsonPath("$.status", is("PENDING"))); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + + getClient(adminToken).perform(patch("/api/integration/qualityassuranceevents/" + idRef.get()) + .content(getPatchContent(acceptOp)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java index ac0ccc4ccecd..31a2c618064a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static org.dspace.app.rest.matcher.QASourceMatcher.matchQASourceEntry; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -43,74 +44,60 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest @Before public void setup() { - context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) - .withTitle("Community") - .build(); + .withTitle("Community") + .build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection") - .build(); + .withName("Collection") + .build(); target = ItemBuilder.createItem(context, collection) - .withTitle("Item") - .build(); + .withTitle("Item") + .build(); context.restoreAuthSystemState(); - configurationService.setProperty("qaevent.sources", - new String[] { "openaire", "test-source", "test-source-2" }); - + configurationService.setProperty(QAEVENTS_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"); + context.setCurrentUser(eperson); createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3"); createEvent("test-source", "TOPIC/TEST/1", "Title 4"); createEvent("test-source", "TOPIC/TEST/1", "Title 5"); + context.setCurrentUser(null); 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()); - + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/integration/qualityassurancesources")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).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 testFindAllUnauthorized() throws Exception { - context.turnOffAuthorisationSystem(); createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); @@ -119,8 +106,7 @@ public void testFindAllUnauthorized() throws Exception { context.restoreAuthSystemState(); getClient().perform(get("/api/integration/qualityassurancesources")) - .andExpect(status().isUnauthorized()); - + .andExpect(status().isUnauthorized()); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java index 55bfd0fecaf3..bf3f2f02ed81 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; 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; @@ -20,6 +22,7 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.QAEventBuilder; import org.dspace.content.Collection; +import org.dspace.qaevent.QANotifyPatterns; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; @@ -38,114 +41,60 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @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))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/qualityassurancetopics")) + .andExpect(status().isMethodNotAllowed()); } @Test public void findOneTest() throws Exception { context.turnOffAuthorisationSystem(); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { OPENAIRE_SOURCE, "test-source" }); parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + .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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) + .withMessage("{\"test\": \"Test...\"}") + .build(); + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withSource("test-source") + .withTopic("TOPIC/TEST") + .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))); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform( + get("/api/integration/qualityassurancetopics/" + OPENAIRE_SOURCE + ":ENRICH!MISSING!PID")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry( + QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2))); + + getClient(adminToken).perform(get("/api/integration/qualityassurancetopics/" + + OPENAIRE_SOURCE + ":ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry( + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1))); + + getClient(adminToken).perform(get("/api/integration/qualityassurancetopics/test-source:TOPIC!TEST")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",QATopicMatcher.matchQATopicEntry("test-source", "TOPIC/TEST", 1))); } @Test @@ -184,94 +133,97 @@ public void findOneForbiddenTest() throws Exception { @Test public void findBySourceTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty("qaevent.sources", - new String[] { "openaire", "test-source", "test-source-2" }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { + OPENAIRE_SOURCE, "test-source", "test-source-2" }); + parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + .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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(QANotifyPatterns.TOPIC_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(); + .withTopic(org.dspace.qaevent.QANotifyPatterns.TOPIC_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(); + .withTopic("TEST/TOPIC") + .withSource("test-source") + .build(); QAEventBuilder.createTarget(context, col1, "Science and Freedom 6") - .withTopic("TEST/TOPIC") - .withSource("test-source") - .build(); + .withTopic("TEST/TOPIC") + .withSource("test-source") + .build(); QAEventBuilder.createTarget(context, col1, "Science and Freedom 7") - .withTopic("TEST/TOPIC/2") - .withSource("test-source") - .build(); + .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))); + .param("source", OPENAIRE_SOURCE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder( + QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2), + QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1), + QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_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))); + .param("source", "test-source")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder( + QATopicMatcher.matchQATopicEntry("test-source", "TEST/TOPIC/2", 1), + QATopicMatcher.matchQATopicEntry("test-source", "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))); + .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(); + .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(); + .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()); + getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource") + .param("source", OPENAIRE_SOURCE)) + .andExpect(status().isUnauthorized()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java index b85746c64c6b..70e5fe61577b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java @@ -13,6 +13,8 @@ import static org.hamcrest.Matchers.is; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -49,10 +51,11 @@ public static Matcher matchQAEventFullEntry(QAEvent event) { public static Matcher matchQAEventEntry(QAEvent event) { try { ObjectMapper jsonMapper = new JsonMapper(); + DecimalFormat decimalFormat = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.ENGLISH)); 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("$.trust", is(decimalFormat.format(event.getTrust()))), hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())), hasJsonPath("$.message", matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java index 6428a971257d..85ffe6fb44b3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java @@ -12,6 +12,7 @@ import static org.hamcrest.Matchers.is; import org.dspace.app.rest.model.hateoas.QATopicResource; +import org.dspace.content.QAEvent; import org.hamcrest.Matcher; /** @@ -24,22 +25,41 @@ public class QATopicMatcher { private QATopicMatcher() { } - public static Matcher matchQATopicEntry(String key, int totalEvents) { + public static Matcher matchQATopicEntry(String topicName, int totalEvents) { + return matchQATopicEntry(QAEvent.OPENAIRE_SOURCE, topicName, totalEvents); + } + + + public static Matcher matchQATopicEntry(String topicName) { + return matchQATopicEntry(QAEvent.OPENAIRE_SOURCE, topicName); + } + + public static Matcher matchQATopicEntry(String source, String topicName, int totalEvents) { return allOf( hasJsonPath("$.type", is("qualityassurancetopic")), - hasJsonPath("$.name", is(key)), - hasJsonPath("$.id", is(key.replace("/", "!"))), + hasJsonPath("$.name", is(topicName)), + hasJsonPath("$.id", is(source + ":" + topicName.replace("/", "!"))), hasJsonPath("$.totalEvents", is(totalEvents)) ); } - public static Matcher matchQATopicEntry(String key) { + public static Matcher matchQATopicEntry(String source, String topicName) { return allOf( hasJsonPath("$.type", is("qualityassurancetopic")), - hasJsonPath("$.name", is(key)), - hasJsonPath("$.id", is(key.replace("/", "/"))) + hasJsonPath("$.name", is(topicName)), + hasJsonPath("$.id", is(source + ":" + topicName.replace("/", "!"))) ); } + public static Matcher matchQATopicEntry(String source, String topicName, String itemUuid, + int totalEvents) { + return allOf( + hasJsonPath("$.type", is("qualityassurancetopic")), + hasJsonPath("$.name", is(topicName)), + hasJsonPath("$.id", is(source + ":" + topicName.replace("/", "!") + ":" + itemUuid)), + hasJsonPath("$.totalEvents", is(totalEvents)) + ); + } + } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1575ff65036b..d4be67613ed4 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1089,7 +1089,6 @@ webui.preview.brand.fontpoint = 12 # Solr: # ItemCountDAO.class = org.dspace.browse.ItemCountDAOSolr - ###### Browse Configuration ###### # # Define the DAO class to use this must meet your storage choice for diff --git a/dspace/config/emails/qaevent_admin_notification b/dspace/config/emails/qaevent_admin_notification new file mode 100644 index 000000000000..6c75f89a8fb9 --- /dev/null +++ b/dspace/config/emails/qaevent_admin_notification @@ -0,0 +1,13 @@ +## E-mail sent to notify Administrator of new Withdrawn/Reinstate request +## +## Parameters: {0} Type of request 'topic' +## {1} resource id +## {2} reason +## +#set($subject = "Notification about ${params[0]} Request") + +Item Details: + +Type of request: ${params[0]} +Relatem to item: ${params[1]} +Reason: ${params[2]} \ No newline at end of file diff --git a/dspace/config/modules/qaevents.cfg b/dspace/config/modules/qaevents.cfg index 6cbaa120840e..f117bc807f23 100644 --- a/dspace/config/modules/qaevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -31,4 +31,18 @@ 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 \ No newline at end of file +qaevents.openaire.broker-url = http://api.openaire.eu/broker + +###### QAEvent source Configuration ###### +qaevents.sources = OpenAIRE, DSpaceUsers + +### Withdrawal&Reinstate correction Group ### +# Members of this group enabled to make requests for the Withdrawn or Reinstate of an item. +# By default this property is empty, so only Administrators will see the button to make these requests. +# If you want to allow all authenticated users to have this feature, +# you can configure this setting to use the Anonymous group. +qaevents.withdraw-reinstate.group = + +# Withdrawal&Reinstate email to notify the system administrator about a new +# Quality Assurance (QA) request event. +qaevents.mail.notification = ${mail.admin} diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index c7da3b0ceba4..0e03d76f7d18 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -54,4 +54,4 @@ rest.properties.exposed = google.recaptcha.mode rest.properties.exposed = cc.license.jurisdiction rest.properties.exposed = identifiers.item-status.register-doi rest.properties.exposed = authentication-password.domain.valid -rest.properties.exposed = handle.canonical.prefix +rest.properties.exposed = handle.canonical.prefix \ No newline at end of file diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index a13b1bb867ec..3f1f75d7824b 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -155,5 +155,7 @@ + + diff --git a/dspace/config/spring/api/correction-types.xml b/dspace/config/spring/api/correction-types.xml new file mode 100644 index 000000000000..b79e2408d8a5 --- /dev/null +++ b/dspace/config/spring/api/correction-types.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 8c52b8b1f2dd..438178e4fa9c 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -18,14 +18,15 @@ - + - + + + @@ -64,5 +65,28 @@ - + + + + + + + + + + + + + + + + + + + + + original_id:{0} + + +