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 cf6c06446c..c9ed33e34a 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -14,6 +14,7 @@ import java.util.Date; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.dspace.qaevent.service.dto.NotifyMessageDTO; +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; @@ -31,7 +32,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"; public static final String COAR_NOTIFY_SOURCE = "coar-notify"; private String source; @@ -65,8 +67,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) { @@ -84,7 +85,6 @@ public class QAEvent { } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { throw new IllegalStateException(e); } - } public String getOriginalId() { @@ -209,10 +209,10 @@ public class QAEvent { switch (getSource()) { case OPENAIRE_SOURCE: result = OpenaireMessageDTO.class; - break; + break; case COAR_NOTIFY_SOURCE: result = NotifyMessageDTO.class; - break; + break; 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 0000000000..bc5abaef4e --- /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 0000000000..847236d110 --- /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 0000000000..edf71ed815 --- /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 0000000000..e76e1f7ec1 --- /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 0000000000..e64120c46a --- /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 index b66bd9efb3..cbc36f823c 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java @@ -11,9 +11,9 @@ 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"; @@ -21,10 +21,9 @@ public class QANotifyPatterns { 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"; - public static final String TOPIC_ENRICH_MORE_LINK = "ENRICH/MORE/LINK"; /** * 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 852dd0b100..10849b47fc 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -17,16 +17,16 @@ import java.util.UUID; * */ public class QASource { - private String name; /** - * if the QASource stats (see next attributes) are related to a specific target + * 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 long totalEvents; - + private String name; private Date lastEvent; + private long totalEvents; public String getName() { return name; @@ -36,14 +36,6 @@ public class QASource { this.name = name; } - public UUID getFocus() { - return focus; - } - - public void setFocus(UUID focus) { - this.focus = focus; - } - public long getTotalEvents() { return totalEvents; } @@ -60,6 +52,14 @@ public class QASource { this.lastEvent = lastEvent; } + public UUID getFocus() { + return focus; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } + @Override public String toString() { return name + focus + totalEvents; 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 fcbb0f606a..92fe3737f4 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -15,17 +15,22 @@ import java.util.UUID; * topic represents a type of event and is therefore used to group events. * * @author Andrea Bollini (andrea.bollini at 4science.it) - * */ public class QATopic { - private String source; - private String key; + /** - * if the QASource stats (see next attributes) are related to a specific target + * 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 long totalEvents; + private String key; + /** + * The source attributes contains the name of the QA source like: OpenAIRE, DSpaceUsers + */ + private String source; private Date lastEvent; + private long totalEvents; public String getSource() { return source; @@ -65,5 +70,4 @@ public class QATopic { public void setLastEvent(Date lastEvent) { this.lastEvent = lastEvent; } - } 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 0000000000..cd436f3966 --- /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 0000000000..7048a8285e --- /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 9087606aa6..cbaf808422 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 eu.dnetlib.broker.BrokerClient; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.dspace.content.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 @@ import org.dspace.utils.DSpace; public class OpenaireEventsImport extends DSpaceRunnable> { + private HandleService handleService; + private QAEventService qaEventService; private String[] topicsToImport; @@ -103,7 +109,9 @@ public class OpenaireEventsImport 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 @@ public class OpenaireEventsImport 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 index cd3cf09425..38cf40ce39 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java @@ -20,9 +20,9 @@ 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; @@ -30,6 +30,7 @@ public class AdministratorsOnlyQASecurity implements QASecurity { return Optional.empty(); } + @Override public boolean canSeeQASource(Context context, EPerson user) { try { return authorizeService.isAdmin(context, user); @@ -38,6 +39,7 @@ public class AdministratorsOnlyQASecurity implements QASecurity { } } + @Override public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { try { return authorizeService.isAdmin(context, user); 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 index d969b87743..44b00e7d94 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java @@ -14,7 +14,9 @@ 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) * 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 index 09c0b8b719..3d66d221e6 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java @@ -23,18 +23,16 @@ import org.springframework.beans.factory.annotation.Autowired; * uuid * * @author Andrea Bollini (andrea.bollini at 4science.com) - * */ public class UserBasedFilterQASecurity implements QASecurity { - @Autowired - private AuthorizeService authorizeService; + + private String filterTemplate; + private boolean allowAdmins = true; @Autowired private QAEventService qaEventService; - - private String filterTemplate; - - private boolean allowAdmins = true; + @Autowired + private AuthorizeService authorizeService; public Optional generateFilterQuery(Context context, EPerson user) { try { @@ -68,4 +66,5 @@ public class UserBasedFilterQASecurity implements QASecurity { 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 index 5ed280da18..7f6ef7a12c 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java @@ -23,20 +23,20 @@ public interface QAEventSecurityService { /** * Check if the specified user can see a specific QASource - * @param context the context - * @param user the eperson to consider - * @param qaSource the qaSource involved + * @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 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 + * @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); @@ -45,11 +45,11 @@ public interface QAEventSecurityService { * 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 qaSource the qaSource involved + * @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 qaSource); + public Optional generateQAEventFilterQuery(Context context, EPerson user, String sourceName); -} \ No newline at end of file +} 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 c73677382d..f1729cda5c 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 @@ -27,8 +27,11 @@ 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(Context context, long offset, long count, String orderField, boolean ascending); @@ -36,13 +39,15 @@ public interface QAEventService { * Find all the event's topics related to the given source. * * @param context the DSpace context - * @param source the source to search for - * @param offset the offset to apply - * @param count the page size - * @return the topics list + * @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(Context context, String source, long offset, long count, - String orderField, boolean ascending); + String orderField, boolean ascending); /** * Find a specific topic by its name, source and optionally a target. @@ -67,30 +72,30 @@ public interface QAEventService { /** * Find all the events by topic sorted by trust descending. * - * @param context the DSpace context - * @param source the source name + * @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 size the page size * @return the events */ - public List findEventsByTopicAndPage(Context context, String source, String topic, long offset, - int pageSize); + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + String orderField, boolean ascending); /** * Find all the events by topic. * - * @param context the DSpace context - * @param topic the topic to search for - * @return the events count + * @param context the DSpace context + * @param sourceName the source name + * @param topic the topic to search for + * @return the events count */ - public long countEventsByTopic(Context context, String source, String topic); + public long countEventsByTopic(Context context, String sourceName, String topic); /** - * Find an event by the given id. Please note that no security filter are applied by this method. - * - * @param context the DSpace context + * Find an event by the given id. * @param id the id of the event to search for + * * @return the event */ public QAEvent findEventByEventId(Context context, String id); @@ -139,7 +144,7 @@ public interface QAEventService { /** * Find all the event's sources. * - * @param context the DSpace context + * @param context the DSpace context * @param offset the offset to apply * @param pageSize the page size * @return the sources list @@ -154,6 +159,25 @@ public interface QAEventService { */ 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 countTopicsBySourceAndTarget the count result + */ + public long countTopicsBySourceAndTarget(Context context, String source, UUID target); + /** * Check if the given QA event supports a related item. * @@ -165,7 +189,7 @@ public interface QAEventService { /** * 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 @@ -203,16 +227,6 @@ public interface QAEventService { public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, long count, String orderField, boolean ascending); - /** - * 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); - /** * Find all the event's sources related to a specific item * @@ -224,15 +238,6 @@ public interface QAEventService { */ public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize); - /** - * 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); - /** * Check if a qaevent with the provided id is visible to the current user according to the source security * 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 0000000000..e5e38c2396 --- /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 2a63f42e61..ede32ef497 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 @@ import org.dspace.content.QAEvent; */ 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 812159eb1f..30875a5105 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.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.QAEvent; @@ -41,7 +42,8 @@ import org.springframework.beans.factory.annotation.Autowired; * */ 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 index cfb3d8379f..7861673b27 100644 --- 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 @@ -17,14 +17,27 @@ 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 static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventSecurityServiceImpl.class); private Map qaSecurityConfiguration; - - private QASecurity defaultSecurity; - + public void setQaSecurityConfiguration(Map qaSecurityConfiguration) { this.qaSecurityConfiguration = qaSecurityConfiguration; } @@ -47,8 +60,7 @@ public class QAEventSecurityServiceImpl implements QAEventSecurityService { 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); + return qaSecurity.canSeeQASource(context, user) && qaSecurity.canSeeQAEvent(context, user, qaEvent); } @Override 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 df51c5bf79..7c7c102444 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 @@ -23,6 +23,7 @@ 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; @@ -41,12 +42,13 @@ import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.FacetParams; -import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -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.AutomaticProcessingAction; import org.dspace.qaevent.QAEventAutomaticProcessingEvaluation; import org.dspace.qaevent.QASource; @@ -58,6 +60,8 @@ 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; import org.springframework.beans.factory.annotation.Qualifier; @@ -84,9 +88,6 @@ public class QAEventServiceImpl implements QAEventService { @Autowired(required = true) protected ItemService itemService; - @Autowired - private HandleService handleService; - @Autowired private QAEventsDAOImpl qaEventsDao; @@ -154,23 +155,19 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public long countTopicsBySourceAndTarget(Context context, String source, UUID target) { - if (isNotSupportedSource(source) - || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), 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); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, - context.getCurrentUser(), source); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); 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() + "\""); - } + solrQuery.addFilterQuery("source:" + sourceName); QueryResponse response; try { response = getSolr().query(solrQuery); @@ -180,6 +177,44 @@ public class QAEventServiceImpl implements QAEventService { 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 { @@ -281,7 +316,6 @@ public class QAEventServiceImpl implements QAEventService { try { response = getSolr().query(solrQuery); FacetField facetField = response.getFacetField(TOPIC); - topics = new ArrayList<>(); int idx = 0; for (Count c : facetField.getValues()) { if (idx < offset) { @@ -366,13 +400,45 @@ public class QAEventServiceImpl implements QAEventService { } - @Override - public QAEvent findEventByEventId(Context context, String eventId) { - SolrQuery param = new SolrQuery("*:*"); - param.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); - QueryResponse response; + /** + * 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 { - response = getSolr().query(param); + 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 solrQuery = new SolrQuery("*:*"); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); + try { + QueryResponse response = getSolr().query(solrQuery); if (response != null) { SolrDocumentList list = response.getResults(); if (list != null && list.size() == 1) { @@ -386,28 +452,9 @@ public class QAEventServiceImpl implements QAEventService { return null; } - @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 List findEventsByTopicAndPage(Context context, String source, String topic, long offset, - int pageSize) { + int pageSize) { if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { return List.of(); @@ -442,7 +489,7 @@ public class QAEventServiceImpl implements QAEventService { @Override public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset, - int pageSize, UUID target) { + int pageSize, UUID target) { if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { return List.of(); @@ -480,49 +527,23 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public long countEventsByTopic(Context context, String source, String topic) { - if (isNotSupportedSource(source) - || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { + public long countEventsByTopic(Context context, String sourceName, String topic) { + EPerson 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, context.getCurrentUser(), - source); - solrQuery.setQuery(securityQuery.orElse("*:*")); - solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); - solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); - QueryResponse response = null; - try { - response = getSolr().query(solrQuery); - return response.getResults().getNumFound(); - } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); - } - } - @Override - public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target) { - if (isNotSupportedSource(source) - || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { - return 0; - } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, context.getCurrentUser(), - source); solrQuery.setQuery(securityQuery.orElse("*:*")); - if (target != null) { - solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); - } - solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); - solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); - QueryResponse response = null; + 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); } } @@ -534,15 +555,14 @@ public class QAEventServiceImpl implements QAEventService { @Override public QASource findSource(Context context, String sourceName, UUID target) { - - if (isNotSupportedSource(sourceName) - || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName)) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return null; } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + SolrQuery solrQuery = new SolrQuery(); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, context.getCurrentUser(), - sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setRows(0); solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); @@ -553,9 +573,8 @@ public class QAEventServiceImpl implements QAEventService { 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)) { @@ -568,7 +587,7 @@ public class QAEventServiceImpl implements QAEventService { } } } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } QASource source = new QASource(); @@ -581,12 +600,13 @@ public class QAEventServiceImpl implements QAEventService { @Override public List findAllSources(Context context, long offset, int pageSize) { return Arrays.stream(getSupportedSources()) - .map((sourceName) -> findSource(context, sourceName)) + .map((sourceName) -> findSource(context, sourceName)) .filter(Objects::nonNull) - .sorted(comparing(QASource::getTotalEvents).reversed()) - .skip(offset) - .limit(pageSize) - .collect(Collectors.toList()); + .sorted(comparing(QASource::getTotalEvents) + .reversed()) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); } @Override @@ -635,12 +655,7 @@ public class QAEventServiceImpl implements QAEventService { 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; } @@ -686,13 +701,173 @@ public class QAEventServiceImpl implements QAEventService { 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", + return configurationService.getArrayProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE, QAEvent.COAR_NOTIFY_SOURCE }); } + @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/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 7a09dd2e76..48f3b9f325 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -169,7 +169,7 @@ public class S3BitStoreService extends BaseBitStoreService { @Override public void init() throws IOException { - if (this.isInitialized()) { + if (this.isInitialized() || !this.isEnabled()) { return; } 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 b73111ba14..9f983ca918 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 @@ -11,6 +11,7 @@ import static java.util.List.of; import static org.dspace.content.QAEvent.COAR_NOTIFY_SOURCE; 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.containsString; @@ -57,10 +58,8 @@ import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; - /** * Integration tests for {@link OpenaireEventsImport}. * @@ -69,9 +68,8 @@ import org.junit.Test; */ 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); @@ -101,8 +99,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase .build(); context.restoreAuthSystemState(); - configurationService.setProperty("qaevent.sources", new String[] - { QAEvent.OPENAIRE_SOURCE }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); } @@ -151,7 +148,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase } @Test - @SuppressWarnings("unchecked") public void testManyEventsImportFromFile() throws Exception { context.turnOffAuthorisationSystem(); @@ -207,7 +203,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -226,12 +221,12 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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")); @@ -246,14 +241,11 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), - contains( + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -273,7 +265,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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")); @@ -285,14 +279,11 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), - contains( + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -310,13 +301,12 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, ORDER_FIELD, false), empty()); + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); verifyNoInteractions(mockBrokerClient); } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBroker() throws Exception { context.turnOffAuthorisationSystem(); @@ -371,18 +361,14 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20), - contains( + assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, "Egypt, crossroad of translations and literary interweavings", projectMessage, QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - List eventList = qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20); - assertThat(eventList, hasItem( + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), containsInAnyOrder( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); assertThat(eventList, hasItem( @@ -417,7 +403,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, ORDER_FIELD, false), empty()); + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); @@ -426,7 +412,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { context.turnOffAuthorisationSystem(); @@ -476,12 +461,8 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); - assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20), - hasSize(1)); - assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), - hasSize(2)); + assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1)); + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2)); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); @@ -489,7 +470,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); verifyNoMoreInteractions(mockBrokerClient); - } /** diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 7aae1cf271..30eeff2ddc 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -42,6 +43,7 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import io.findify.s3mock.S3Mock; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.BooleanUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.matcher.LambdaMatcher; import org.dspace.authorize.AuthorizeException; @@ -53,6 +55,8 @@ import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Utils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; @@ -60,6 +64,7 @@ import org.junit.Before; import org.junit.Test; + /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ @@ -77,9 +82,13 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private File s3Directory; + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @Before public void setup() throws Exception { + configurationService.setProperty("assetstore.s3.enabled", "true"); s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3"); s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath()); @@ -88,7 +97,8 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { amazonS3Client = createAmazonS3Client(); s3BitStoreService = new S3BitStoreService(amazonS3Client); - + s3BitStoreService.setEnabled(BooleanUtils.toBoolean( + configurationService.getProperty("assetstore.s3.enabled"))); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -382,6 +392,17 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); } + @Test + public void testDoNotInitializeConfigured() throws Exception { + String assetstores3enabledOldValue = configurationService.getProperty("assetstore.s3.enabled"); + configurationService.setProperty("assetstore.s3.enabled", "false"); + s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService.init(); + assertFalse(s3BitStoreService.isInitialized()); + assertFalse(s3BitStoreService.isEnabled()); + configurationService.setProperty("assetstore.s3.enabled", assetstores3enabledOldValue); + } + private byte[] generateChecksum(String content) { try { MessageDigest m = MessageDigest.getInstance("MD5"); 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 edb594bb18..1aab625578 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 class QAEventRelatedRestController { @RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainCurrentRequestContext(); - QAEvent qaevent = qaEventService.findEventByEventId(context, 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 c20668fcc5..15438a5e9a 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.BaseObjectRest; 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 @@ import org.springframework.stereotype.Component; 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 0000000000..58330fdfae --- /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 ec73cae0b0..ef0e8730da 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,6 +8,8 @@ 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; @@ -15,12 +17,14 @@ 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.NotifyQAEventMessageRest; +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.NotifyMessageDTO; +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; @@ -56,9 +60,9 @@ public class QAEventConverter implements DSpaceConverter { 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()); @@ -66,7 +70,8 @@ public class QAEventConverter implements DSpaceConverter { 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; @@ -78,6 +83,9 @@ public class QAEventConverter implements DSpaceConverter { } else if (dto instanceof NotifyMessageDTO) { return convertNotifyMessage(dto); } + if (dto instanceof CorrectionTypeMessageDTO) { + return convertCorrectionTypeMessage(dto); + } throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); } @@ -91,6 +99,13 @@ public class QAEventConverter implements DSpaceConverter { return message; } + 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/model/CorrectionTypeQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java new file mode 100644 index 0000000000..6929f39071 --- /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 0000000000..6a26313f92 --- /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 5ce25c8f9d..5a74d33531 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 @@ import org.dspace.app.rest.RestResourceController; */ @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 0000000000..cb1ba5552f --- /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 0000000000..14dc7d9fb2 --- /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/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index 48f835ba7a..06f480843d 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,14 +7,18 @@ */ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.UUID; 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; @@ -23,9 +27,12 @@ import org.dspace.content.Item; 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; @@ -56,6 +63,14 @@ 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("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QAEventRest findOne(Context context, String id) { @@ -84,11 +99,9 @@ public class QAEventRestRepository extends DSpaceRestRepository qaEvents = null; - long count = 0L; - qaEvents = qaEventService.findEventsByTopicAndPageAndTarget(context, sourceName, topicName, + List qaEvents = qaEventService.findEventsByTopicAndPageAndTarget(context, sourceName, topicName, pageable.getOffset(), pageable.getPageSize(), target); - count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target); + long count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target); if (qaEvents == null) { return null; } @@ -104,11 +117,6 @@ public class QAEventRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); - } - @Override @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'WRITE')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, @@ -130,6 +138,64 @@ public class QAEventRestRepository extends DSpaceRestRepository 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 9ad8a3a4e8..c3cb4260ec 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,7 +48,7 @@ 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("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, 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 a6fa9bfa55..abee3609be 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,7 +42,7 @@ 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("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, 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 5b2bd0e99a..a227f81dad 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 @@ -55,10 +55,10 @@ public class QASourceRestRepository extends DSpaceRestRepository findByTarget(@Parameter(value = "target", required = true) UUID target, - Pageable pageable) { + Pageable pageable) { Context context = obtainContext(); - List topics = qaEventService - .findAllSourcesByTarget(context, target, pageable.getOffset(), pageable.getPageSize()); + List topics = qaEventService.findAllSourcesByTarget(context, target, + pageable.getOffset(), pageable.getPageSize()); long count = qaEventService.countSourcesByTarget(context, target); if (topics == null) { return null; 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 038e60a3e9..096d0773b4 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 @@ -42,6 +42,11 @@ 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) { @@ -53,30 +58,21 @@ public class QATopicRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - throw new RepositoryMethodNotImplementedException("Method not allowed!", ""); + return (topic != null) ? converter.toRest(topic, utils.obtainProjection()) : null; } @SearchRestMethod(name = "bySource") @PreAuthorize("hasPermission(#source, 'QUALITYASSURANCETOPIC', 'READ')") public Page findBySource(@Parameter(value = "source", required = true) String source, - Pageable pageable) { + Pageable pageable) { Context context = obtainContext(); - long count = qaEventService.countTopicsBySource(context, source); 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.findAllTopicsBySource(context, source, - pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + long count = qaEventService.countTopicsBySource(context, source); if (topics == null) { return null; } @@ -86,20 +82,15 @@ public class QATopicRestRepository extends DSpaceRestRepository findByTarget(@Parameter(value = "target", required = true) UUID target, - @Parameter(value = "source", required = true) String source, - Pageable pageable) { + @Parameter(value = "source", required = true) String source, Pageable pageable) { Context context = obtainContext(); - List topics = qaEventService - .findAllTopicsBySourceAndTarget(context, source, target, pageable.getOffset(), - pageable.getPageSize(), null, true); - long count = qaEventService.countTopicsBySourceAndTarget(context, source, target); - - if (topics == null) { - return null; + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); - } - + List topics = qaEventService.findAllTopicsBySourceAndTarget(context, source, target, + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + long count = qaEventService.countTopicsBySourceAndTarget(context, source, target); @Override public Class getDomainClass() { return QATopicRest.class; 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 0000000000..f3776e217e --- /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/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 0000000000..6e720fd63f --- /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 e6b96acd8d..c2829273d8 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,10 +7,14 @@ */ 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.content.QAEvent.COAR_NOTIFY_SOURCE; import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +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; @@ -29,9 +33,11 @@ import java.math.BigDecimal; 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 org.dspace.app.ldn.NotifyServiceEntity; +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.matcher.QAEventMatcher; @@ -43,6 +49,7 @@ 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.NotifyServiceBuilder; import org.dspace.builder.QAEventBuilder; @@ -57,6 +64,8 @@ import org.dspace.eperson.EPerson; import org.dspace.qaevent.QANotifyPatterns; import org.dspace.qaevent.action.ASimpleMetadataAction; 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; @@ -71,6 +80,8 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private QAEventsDAO qaEventsDao; + @Autowired + private ConfigurationService configurationService; @Autowired private ItemService itemService; @@ -85,11 +96,17 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 @@ -131,48 +148,62 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @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(QANotifyPatterns.TOPIC_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 event5 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") - .withTopic(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(); context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId())) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test @@ -316,6 +347,52 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { } + @Test + public void findByTopicTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", 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 public void findByTopicPaginatedTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -362,39 +439,38 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .withSource(OPENAIRE_SOURCE) .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144304\"}").build(); - context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic") - .param("topic", QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID") - .param("size", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), Matchers.containsString("page=5"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) @@ -404,42 +480,42 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.page.totalElements", is(11))); getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic") - .param("topic", QAEvent.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", + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), Matchers.containsString("page=5"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.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("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) @@ -448,57 +524,57 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.page.totalElements", is(11))); getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic") - .param("topic", QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID") - .param("size", "2").param("page", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.hasItem( - QAEventMatcher.matchQAEventEntry(event5)))) - .andExpect(jsonPath("$._links.self.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), - Matchers.containsString("page=2"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.next.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), - Matchers.containsString("page=3"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.last.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), - Matchers.containsString("page=5"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.first.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=" + QAEvent.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=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), - Matchers.containsString("page=1"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalPages", is(6))) - .andExpect(jsonPath("$.page.totalElements", is(11))); + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID") + .param("size", "2").param("page", "2")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", + Matchers.hasItem( + QAEventMatcher.matchQAEventEntry(event5)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), + Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), + Matchers.containsString("page=3"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), + Matchers.containsString("page=5"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + QAEvent.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=" + QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"), + Matchers.containsString("page=1"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(6))) + .andExpect(jsonPath("$.page.totalElements", is(11))); // check if the pagination is working properly also when a security filter is in place authToken = getAuthToken(eperson.getEmail(), password); getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic") - .param("topic", QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID") - .param("size", "2")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasNoJsonPath("$._embedded.qualityassuranceevents"))) + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasNoJsonPath("$._embedded.qualityassuranceevents"))) .andExpect(jsonPath("$.page.size", is(2))) .andExpect(jsonPath("$.page.totalPages", is(0))) .andExpect(jsonPath("$.page.totalElements", is(0))); @@ -509,24 +585,22 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); - getClient() - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic") - .param("topic", QAEvent.OPENAIRE_SOURCE + ":ENRICH!MISSING!PID")) - .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID")) + .andExpect(status().isUnauthorized()); } @Test @@ -534,16 +608,16 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); @@ -557,18 +631,26 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT) .withMessage( @@ -598,10 +680,12 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); QAEvent eventMissingPID1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") + .build(); QAEvent eventMissingPID2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}") + .build(); QAEvent eventMissingUnknownPID = QAEventBuilder.createTarget(context, col1, "Science and Freedom URN PID") .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .withMessage( @@ -609,23 +693,31 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); QAEvent eventMorePID = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_PID) - .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}").build(); + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}") + .build(); QAEvent eventAbstract = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) - .withMessage("{\"abstracts[0]\": \"An abstract to add...\"}").build(); + .withMessage("{\"abstracts[0]\": \"An abstract to add...\"}") + .build(); QAEvent eventAbstractToDiscard = QAEventBuilder.createTarget(context, col1, "Science and Freedom 7") .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) - .withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}").build(); + .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); @@ -642,82 +734,95 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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) @@ -726,17 +831,18 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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/qualityassurancesources/" + QAEvent.OPENAIRE_SOURCE)) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.totalEvents", is(0))); + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalEvents", is(0))); } @Test @@ -949,39 +1055,49 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @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(QANotifyPatterns.TOPIC_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 event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") - .withTopic(QANotifyPatterns.TOPIC_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(); context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken) - .perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", - QAEvent.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!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", - QAEvent.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))); + 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 @@ -1002,7 +1118,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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(); @@ -1033,42 +1149,24 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { assertThat(processedEvent.getItem().getID().toString(), is(event.getTarget())); assertThat(processedEvent.getEventTimestamp(), notNullValue()); assertThat(processedEvent.getEperson().getID(), is(admin.getID())); - - getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId())) - .andExpect(status().isInternalServerError()); - - authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event2.getEventId())) - .andExpect(status().isForbidden()); } @Test - public void createQAEventsAndAcceptAutomaticallyByScoreAndFilterTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - Item item = ItemBuilder.createItem(context, col1).withTitle("demo").build(); + 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()); + } - QAEvent event = - QAEventBuilder.createTarget(context, item) - .withSource(COAR_NOTIFY_SOURCE) - .withTrust(0.8) - .withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_REVIEW) - .withMessage("{" - + "\"href\": \"https://doi.org/10.3214/987654\"," - + "\"relationship\": \"some-rel\"}") - .build(); - - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); - - getClient(authToken).perform(get("/api/core/items/" + item.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.metadata['datacite.relation.isReviewedBy'][0].value", - is("https://doi.org/10.3214/987654"))); - - getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event.getEventId())) - .andExpect(status().isNotFound()); + @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 @@ -1179,4 +1277,219 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); } + + @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 6833f1d813..a8d2dcb727 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; @@ -45,35 +46,31 @@ 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", "coar-notify", "test-source", "test-source-2" }); - + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire","coar-notify", "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("openaire", "TOPIC/OPENAIRE/2", "Title 4"); @@ -111,7 +108,6 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void testFindAllUnauthorized() throws Exception { - context.turnOffAuthorisationSystem(); createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); @@ -120,8 +116,7 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 66327e23f5..ae86f7f3d9 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; @@ -44,74 +46,60 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findAllNotImplementedTest() 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(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(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(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(org.dspace.qaevent.QANotifyPatterns.TOPIC_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().isMethodNotAllowed()); + 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("qaevent.sources", - new String[] { "openaire", "test-source" }); + 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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(QANotifyPatterns.TOPIC_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(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) - .withMessage( - "{\"test\": \"Test...\"}") - .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(); + .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/openaire:ENRICH!MISSING!PID")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2))); - getClient(authToken).perform(get("/api/integration/qualityassurancetopics/openaire:ENRICH!MISSING!ABSTRACT")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1))); - getClient(authToken).perform(get("/api/integration/qualityassurancetopics/test-source:TOPIC!TEST")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - QATopicMatcher.matchQATopicEntry("test-source", "TOPIC/TEST", 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 @@ -176,68 +164,77 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findBySourceTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty("qaevent.sources", - new String[] { QAEvent.OPENAIRE_SOURCE, "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(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) - .withSource(QAEvent.OPENAIRE_SOURCE) - .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(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) - .withSource(QAEvent.OPENAIRE_SOURCE) - .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(QANotifyPatterns.TOPIC_ENRICH_MORE_PID) - .withSource(QAEvent.OPENAIRE_SOURCE) - .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(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) - .withSource(QAEvent.OPENAIRE_SOURCE) - .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")) + .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))); + 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-source", "TEST/TOPIC/2", 1), - QATopicMatcher.matchQATopicEntry("test-source", "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 @@ -310,15 +307,19 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { 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(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID).build(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) + .build(); context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource") - .param("source", "openaire")) - .andExpect(status().isUnauthorized()); + .param("source", OPENAIRE_SOURCE)) + .andExpect(status().isUnauthorized()); } @Test 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 88a09157b8..8eb569468d 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.emptyOrNullString; 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; @@ -53,10 +55,11 @@ public class QAEventMatcher { 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 7a9c2dd8cb..c2a2e76d73 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 @@ -43,7 +43,6 @@ public class QATopicMatcher { ); } - public static Matcher matchQATopicEntry(String source, String topicName) { return allOf( hasJsonPath("$.type", is("qualityassurancetopic")), @@ -52,7 +51,8 @@ public class QATopicMatcher { ); } - public static Matcher matchQATopicEntry(String source, String topicName, String itemUuid, int totalEvents) { + public static Matcher matchQATopicEntry(String source, String topicName, String itemUuid, + int totalEvents) { return allOf( hasJsonPath("$.type", is("qualityassurancetopic")), hasJsonPath("$.name", is(topicName)), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index be0a27b4eb..54339aa1d6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -19,6 +19,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpEntity; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; @@ -94,6 +95,15 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit return getClient().getForEntity(getURL(path), String.class); } + /** + * Perform a request (defined by RequestEntity) and return response as a String + * @param request RequestEntity object which defines the GET request + * @return ResponseEntity with a String body + */ + public ResponseEntity responseAsString(RequestEntity request) { + return getClient().exchange(request, String.class); + } + /** * Perform an authenticated (via Basic Auth) GET request and return response as a String * @param path path to perform GET against @@ -107,10 +117,10 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit /** * Perform an authenticated (via Basic Auth) POST request and return response as a String. - * @param path path to perform GET against + * @param path path to perform POST against * @param username Username (may be null to perform an unauthenticated POST) * @param password Password - * @param requestEntity unknown -- not used. + * @param requestEntity HttpEntity to specify content/headers to POST * @return ResponseEntity with a String body */ public ResponseEntity postResponseAsString(String path, String username, String password, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index 95ec762514..64e3db7dfc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -9,19 +9,36 @@ package org.dspace.app.sword2; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.nio.file.Path; +import java.util.List; import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +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.services.ConfigurationService; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; +import org.springframework.util.LinkedMultiValueMap; /** * Integration test to verify the /swordv2 endpoint is responding as a valid SWORDv2 endpoint. @@ -41,11 +58,24 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { private ConfigurationService configurationService; // All SWORD v2 paths that we test against - private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; - private final String COLLECTION_PATH = "/swordv2/collection"; - private final String MEDIA_RESOURCE_PATH = "/swordv2/edit-media"; - private final String CONTAINER_PATH = "/swordv2/edit"; - private final String STATEMENT_PATH = "/swordv2/statement"; + private final String SWORD_PATH = "/swordv2"; + private final String SERVICE_DOC_PATH = SWORD_PATH + "/servicedocument"; + private final String COLLECTION_PATH = SWORD_PATH + "/collection"; + private final String MEDIA_RESOURCE_PATH = SWORD_PATH + "/edit-media"; + private final String EDIT_PATH = SWORD_PATH + "/edit"; + private final String STATEMENT_PATH = SWORD_PATH + "/statement"; + + // Content Types used + private final String ATOM_SERVICE_CONTENT_TYPE = "application/atomserv+xml;charset=UTF-8"; + private final String ATOM_FEED_CONTENT_TYPE = "application/atom+xml;type=feed;charset=UTF-8"; + private final String ATOM_ENTRY_CONTENT_TYPE = "application/atom+xml;type=entry;charset=UTF-8"; + + /** + * Create a global temporary upload folder which will be cleaned up automatically by JUnit. + * NOTE: As a ClassRule, this temp folder is shared by ALL tests below. + **/ + @ClassRule + public static final TemporaryFolder uploadTempFolder = new TemporaryFolder(); @Before public void onlyRunIfConfigExists() { @@ -60,7 +90,18 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Ensure SWORDv2 URL configurations are set correctly (based on our integration test server's paths) // SWORDv2 validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("swordv2-server.url", getURL(SWORD_PATH)); configurationService.setProperty("swordv2-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + configurationService.setProperty("swordv2-server.collection.url", getURL(COLLECTION_PATH)); + + // Override default value of SWORD upload directory to point at our JUnit TemporaryFolder (see above). + // This ensures uploaded files are saved in a place where JUnit can clean them up automatically. + configurationService.setProperty("swordv2-server.upload.tempdir", + uploadTempFolder.getRoot().getAbsolutePath()); + + // MUST be set to allow DELETE requests on Items which are in the archive. (This isn't enabled by default) + configurationService.setProperty("plugin.single.org.dspace.sword2.WorkflowManager", + "org.dspace.sword2.WorkflowManagerUnrestricted"); } @Test @@ -68,22 +109,20 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Attempt to GET the ServiceDocument without first authenticating ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test public void serviceDocumentTest() throws Exception { - // Attempt to GET the ServiceDocument as an Admin user. + // Attempt to GET the ServiceDocument as any user account ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, - admin.getEmail(), password); - // Expect a 200 response code, and an ATOM UTF-8 document - assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); - assertThat(response.getHeaders().getContentType().toString(), - equalTo("application/atomserv+xml;charset=UTF-8")); + eperson.getEmail(), password); + // Expect a 200 response code, and an ATOM service document + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_SERVICE_CONTENT_TYPE, response.getHeaders().getContentType().toString()); // Check for correct SWORD version in response body - assertThat(response.getBody(), - containsString("2.0")); + assertThat(response.getBody(), containsString("2.0")); } @Test @@ -91,44 +130,204 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Attempt to POST to /collection endpoint without sending authentication information ResponseEntity response = postResponseAsString(COLLECTION_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } + /** + * In DSpace the /collections/[handle-prefix]/[handle-suffix] endpoint gives a list of all Items + * which were deposited BY THE AUTHENTICATED USER into the given collection + */ @Test - @Ignore public void collectionTest() throws Exception { - // TODO: Actually test collection endpoint via SWORDv2. - // Currently, we are just ensuring the /collection endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create all content as the SAME EPERSON we will use to authenticate on this endpoint. + // THIS IS REQUIRED as the /collections endpoint will only show YOUR ITEM SUBMISSIONS. + context.setCurrentUser(eperson); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .build(); + + // Add one Item into that Collection. + String itemTitle = "Test SWORDv2 Item"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(itemTitle) + .withAuthor("Smith, Sam") + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // This Collection should exist on the /collection endpoint via its handle. + // Authenticate as the same user we used to create the test content above. + ResponseEntity response = getResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password); + + // Expect a 200 response code, and an ATOM feed document + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + // Check for response body to include the Item edit link + // NOTE: This endpoint will only list items which were submitted by the authenticated EPerson. + assertThat(response.getBody(), containsString(EDIT_PATH + "/" + item.getID().toString())); + // Check for response body to include the Item title text + assertThat(response.getBody(), containsString("" + itemTitle + "")); } @Test public void mediaResourceUnauthorizedTest() throws Exception { - // Attempt to POST to /mediaresource endpoint without sending authentication information + // Attempt to POST to /edit-media endpoint without sending authentication information ResponseEntity response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(response.getStatusCode(), HttpStatus.UNAUTHORIZED); + } + + /** + * This tests four different SWORDv2 actions, as these all require starting with a new deposit. + * 1. Depositing a new item via SWORD (via POST /collections/[collection-uuid]) + * 2. Reading the deposited item (via GET /edit/[item-uuid]) + * 3. Updating the deposited item's metadata (via PUT /edit/[item-uuid]) + * 4. Deleting the deposited item (via DELETE /edit/[item-uuid]). + */ + @Test + public void depositAndEditViaSwordTest() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + // Make sure our Collection allows the "eperson" user to submit into it + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .withSubmitterGroup(eperson) + .build(); + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // Add file + LinkedMultiValueMap multipart = new LinkedMultiValueMap<>(); + multipart.add("file", new FileSystemResource(Path.of("src", "test", "resources", + "org", "dspace", "app", "sword2", "example.zip"))); + // Add required headers + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setContentDisposition(ContentDisposition.attachment().filename("example.zip").build()); + headers.set("Packaging", "http://purl.org/net/sword/package/METSDSpaceSIP"); + headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML)); + + //---- + // STEP 1: Verify upload/submit via SWORDv2 works + //---- + // Send POST to upload Zip file via SWORD + ResponseEntity response = postResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password, + new HttpEntity<>(multipart, headers)); + + // Expect a 201 CREATED response with ATOM "entry" content returned + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(ATOM_ENTRY_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // MUST return a "Location" header which is the "/swordv2/edit/[uuid]" URI of the created item + assertNotNull(response.getHeaders().getLocation()); + + String editLink = response.getHeaders().getLocation().toString(); + + // Body should include that link as the rel="edit" URL + assertThat(response.getBody(), containsString("")); + + //---- + // STEP 2: Verify uploaded content can be read via SWORDv2 + //---- + // Edit URI should work when requested by the EPerson who did the deposit + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + RequestEntity request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // Body should include links to bitstreams from the zip. + // This just verifies at least one /swordv2/edit-media/bitstream/* link exists. + assertThat(response.getBody(), containsString(getURL(MEDIA_RESOURCE_PATH + "/bitstream"))); + // Verify Item title also is returned in the body + assertThat(response.getBody(), containsString("Attempts to detect retrotransposition")); + + //---- + // STEP 3: Verify uploaded content can be UPDATED via SWORDv2 (by an Admin ONLY) + //---- + // Edit URI can be used with PUT to update the metadata of the Item. + // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE + // must be done via a user with Admin privileges on the Item. + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + // This example simply changes the title. + String newTitle = "This is a new title updated via PUT"; + String newTitleEntry = "" + newTitle + ""; + request = RequestEntity.put(editLink) + .headers(authHeaders) + .contentType(MediaType.APPLICATION_ATOM_XML) + .body(newTitleEntry); + response = responseAsString(request); + // Expect a 200 OK response + assertEquals(HttpStatus.OK, response.getStatusCode()); + + //---- + // STEP 4: Verify content was successfully updated by reading content again + //---- + // Edit URI should work when requested by the EPerson who did the deposit + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + assertEquals(HttpStatus.OK, response.getStatusCode()); + // Verify the new Item title is now included in the response body + assertThat(response.getBody(), containsString(newTitle)); + + //---- + // STEP 5: Verify uploaded content can be DELETED via SWORDv2 (by an Admin ONLY) + //---- + // Edit URI should also allow user to DELETE the uploaded content + // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE + // must be done via a user with Admin privileges on the Item. + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + request = RequestEntity.delete(editLink) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 204 No Content response + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + + // Verify that Edit URI now returns a 404 (using eperson login info) + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 404 response as content was deleted + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } @Test - @Ignore - public void mediaResourceTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /mediaresource endpoint exists (see above) and isn't throwing a 404 - } - - @Test - public void containerUnauthorizedTest() throws Exception { - // Attempt to POST to /container endpoint without sending authentication information - ResponseEntity response = postResponseAsString(CONTAINER_PATH, null, null, null); + public void editUnauthorizedTest() throws Exception { + // Attempt to POST to /edit endpoint without sending authentication information + ResponseEntity response = postResponseAsString(EDIT_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); - } - - @Test - @Ignore - public void containerTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /container endpoint exists (see above) and isn't throwing a 404 + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test @@ -136,14 +335,64 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Attempt to GET /statement endpoint without sending authentication information ResponseEntity response = getResponseAsString(STATEMENT_PATH); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } + /** + * Statements exist for Items in DSpace (/statements/[item-uuid]) + * https://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#statement + */ @Test - @Ignore public void statementTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /statement endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create all content as the SAME EPERSON we will use to authenticate on this endpoint. + // THIS IS REQUIRED as the /statements endpoint will only show YOUR ITEM SUBMISSIONS. + context.setCurrentUser(eperson); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .build(); + + // Add one Item into that Collection. + String itemTitle = "Test SWORDv2 Item"; + String itemAuthor = "Smith, Samantha"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(itemTitle) + .withAuthor(itemAuthor) + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + // GET call to /statement MUST include an "Accept" header that matches one of the formats + // supported by 'SwordStatementDisseminator' (configured in swordv2-server.cfg) + RequestEntity request = RequestEntity.get(getURL(STATEMENT_PATH + "/" + item.getID().toString())) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + ResponseEntity response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + + // Body should include the statement path of the Item, as well as the title & author information + assertThat(response.getBody(), + containsString(STATEMENT_PATH + "/" + item.getID().toString())); + assertThat(response.getBody(), + containsString("" + itemTitle + "")); + assertThat(response.getBody(), + containsString("" + itemAuthor + "")); + // Also verify Item is in "archived" state + assertThat(response.getBody(), + containsString(" bundles = item.getBundles(); Bundle swordBundle = null; diff --git a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java index c145c9d367..6672027003 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java @@ -431,8 +431,7 @@ public class SWORDUrlManager { */ public String getBaseMediaLinkUrl() throws DSpaceSWORDException { - String mlUrl = configurationService.getProperty( - "sword-server", "media-link.url"); + String mlUrl = configurationService.getProperty("sword-server.media-link.url"); if (StringUtils.isBlank(mlUrl)) { if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSWORDException( diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index f3b2cf4396..eee3627c40 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -458,10 +458,9 @@ public class SwordUrlManager { throws DSpaceSwordException { WorkflowTools wft = new WorkflowTools(); - // if the item is in the workspace, we need to give it it's own special identifier + // if the item is in the workspace, we need to give it its own special identifier if (wft.isItemInWorkspace(context, item)) { - String urlTemplate = configurationService - .getProperty("swordv2-server", "workspace.url-template"); + String urlTemplate = configurationService.getProperty("swordv2-server.workspace.url-template"); if (urlTemplate != null) { return urlTemplate.replace("#wsid#", Integer.toString( wft.getWorkspaceItem(context, item).getID())); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 872e76bf5a..f1ece7f206 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1094,7 +1094,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 0000000000..6c75f89a8f --- /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 28cf0f35dc..e7d91ec40e 100644 --- a/dspace/config/modules/qaevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -37,4 +37,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/swordv2-server.cfg b/dspace/config/modules/swordv2-server.cfg index 2e60a7d2c2..ccb7760e6d 100644 --- a/dspace/config/modules/swordv2-server.cfg +++ b/dspace/config/modules/swordv2-server.cfg @@ -297,6 +297,7 @@ plugin.named.org.dspace.sword2.SwordContentDisseminator = \ plugin.named.org.dspace.sword2.SwordStatementDisseminator = \ org.dspace.sword2.AtomStatementDisseminator = atom, \ org.dspace.sword2.OreStatementDisseminator = rdf, \ + org.dspace.sword2.AtomStatementDisseminator = application/atom+xml, \ org.dspace.sword2.AtomStatementDisseminator = application/atom+xml_type_feed, \ org.dspace.sword2.OreStatementDisseminator = application/rdf+xml diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index e6b9f12415..898c2c79ee 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -157,5 +157,7 @@ + + diff --git a/dspace/config/spring/api/correction-types.xml b/dspace/config/spring/api/correction-types.xml new file mode 100644 index 0000000000..b79e2408d8 --- /dev/null +++ b/dspace/config/spring/api/correction-types.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index 0c58cc1de9..79e19e879e 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + + + + @@ -47,7 +46,9 @@ - + + + @@ -100,7 +101,7 @@ - + @@ -111,7 +112,7 @@ - + @@ -119,11 +120,11 @@ '{'!join from=search.resourceid to=resource_uuid fromIndex=${solr.multicorePrefix}search}submitter_authority:{0} - + - @@ -141,4 +142,28 @@ --> + + + + + + + + + + + + + + + + + + + + + original_id:{0} + + +