Merge remote-tracking branch 'origin/main' into coar-notify-7

This commit is contained in:
frabacche
2024-02-29 10:05:40 +01:00
60 changed files with 2922 additions and 726 deletions

View File

@@ -14,6 +14,7 @@ import java.util.Date;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.dspace.qaevent.service.dto.NotifyMessageDTO; 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.OpenaireMessageDTO;
import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO;
import org.dspace.util.RawJsonDeserializer; import org.dspace.util.RawJsonDeserializer;
@@ -31,7 +32,8 @@ public class QAEvent {
public static final String REJECTED = "rejected"; public static final String REJECTED = "rejected";
public static final String DISCARDED = "discarded"; 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"; public static final String COAR_NOTIFY_SOURCE = "coar-notify";
private String source; private String source;
@@ -65,8 +67,7 @@ public class QAEvent {
private String status = "PENDING"; private String status = "PENDING";
public QAEvent() { public QAEvent() {}
}
public QAEvent(String source, String originalId, String target, String title, public QAEvent(String source, String originalId, String target, String title,
String topic, double trust, String message, Date lastUpdate) { String topic, double trust, String message, Date lastUpdate) {
@@ -84,7 +85,6 @@ public class QAEvent {
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
} }
public String getOriginalId() { public String getOriginalId() {
@@ -209,10 +209,10 @@ public class QAEvent {
switch (getSource()) { switch (getSource()) {
case OPENAIRE_SOURCE: case OPENAIRE_SOURCE:
result = OpenaireMessageDTO.class; result = OpenaireMessageDTO.class;
break; break;
case COAR_NOTIFY_SOURCE: case COAR_NOTIFY_SOURCE:
result = NotifyMessageDTO.class; result = NotifyMessageDTO.class;
break; break;
default: default:
throw new IllegalArgumentException("Unknown event's source: " + getSource()); throw new IllegalArgumentException("Unknown event's source: " + getSource());
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.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<CorrectionType> correctionTypes;
@Override
public CorrectionType findOne(String id) {
return findAll().stream()
.filter(correctionType -> correctionType.getId().equals(id))
.findFirst()
.orElse(null);
}
@Override
public List<CorrectionType> findAll() {
return CollectionUtils.isNotEmpty(correctionTypes) ? correctionTypes : List.of();
}
@Override
public List<CorrectionType> findByItem(Context context, Item item) throws AuthorizeException, SQLException {
List<CorrectionType> 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);
}
}

View File

@@ -11,9 +11,9 @@ package org.dspace.qaevent;
* Constants for Quality Assurance configurations to be used into cfg and xml spring. * Constants for Quality Assurance configurations to be used into cfg and xml spring.
* *
* @author Francesco Bacchelli (francesco.bacchelli at 4science.it) * @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
*
*/ */
public class QANotifyPatterns { public class QANotifyPatterns {
public static final String TOPIC_ENRICH_MORE_PROJECT = "ENRICH/MORE/PROJECT"; 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_PROJECT = "ENRICH/MISSING/PROJECT";
public static final String TOPIC_ENRICH_MISSING_ABSTRACT = "ENRICH/MISSING/ABSTRACT"; 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_ENDORSEMENT = "ENRICH/MORE/ENDORSEMENT";
public static final String TOPIC_ENRICH_MORE_PID = "ENRICH/MORE/PID"; 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_MISSING_PID = "ENRICH/MISSING/PID";
public static final String TOPIC_ENRICH_MORE_LINK = "ENRICH/MORE/LINK";
/** /**
* Default constructor * Default constructor
*/ */
private QANotifyPatterns() { } private QANotifyPatterns() { }
} }

View File

@@ -17,16 +17,16 @@ import java.util.UUID;
* *
*/ */
public class QASource { 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 UUID focus;
private String name;
private long totalEvents;
private Date lastEvent; private Date lastEvent;
private long totalEvents;
public String getName() { public String getName() {
return name; return name;
@@ -36,14 +36,6 @@ public class QASource {
this.name = name; this.name = name;
} }
public UUID getFocus() {
return focus;
}
public void setFocus(UUID focus) {
this.focus = focus;
}
public long getTotalEvents() { public long getTotalEvents() {
return totalEvents; return totalEvents;
} }
@@ -60,6 +52,14 @@ public class QASource {
this.lastEvent = lastEvent; this.lastEvent = lastEvent;
} }
public UUID getFocus() {
return focus;
}
public void setFocus(UUID focus) {
this.focus = focus;
}
@Override @Override
public String toString() { public String toString() {
return name + focus + totalEvents; return name + focus + totalEvents;

View File

@@ -15,17 +15,22 @@ import java.util.UUID;
* topic represents a type of event and is therefore used to group events. * topic represents a type of event and is therefore used to group events.
* *
* @author Andrea Bollini (andrea.bollini at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it)
*
*/ */
public class QATopic { 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 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 Date lastEvent;
private long totalEvents;
public String getSource() { public String getSource() {
return source; return source;
@@ -65,5 +70,4 @@ public class QATopic {
public void setLastEvent(Date lastEvent) { public void setLastEvent(Date lastEvent) {
this.lastEvent = lastEvent; this.lastEvent = lastEvent;
} }
} }

View File

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

View File

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

View File

@@ -7,9 +7,9 @@
*/ */
package org.dspace.qaevent.script; package org.dspace.qaevent.script;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substringAfter; import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.dspace.core.Constants.ITEM;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -28,10 +28,14 @@ import eu.dnetlib.broker.BrokerClient;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; 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.content.QAEvent;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory; 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.OpenaireClientFactory;
import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.QAEventService;
import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.DSpaceRunnable;
@@ -71,6 +75,8 @@ import org.dspace.utils.DSpace;
public class OpenaireEventsImport public class OpenaireEventsImport
extends DSpaceRunnable<OpenaireEventsImportScriptConfiguration<OpenaireEventsImport>> { extends DSpaceRunnable<OpenaireEventsImportScriptConfiguration<OpenaireEventsImport>> {
private HandleService handleService;
private QAEventService qaEventService; private QAEventService qaEventService;
private String[] topicsToImport; private String[] topicsToImport;
@@ -103,7 +109,9 @@ public class OpenaireEventsImport
jsonMapper = new JsonMapper(); jsonMapper = new JsonMapper();
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 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(); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); brokerClient = OpenaireClientFactory.getInstance().getBrokerClient();
@@ -236,14 +244,47 @@ public class OpenaireEventsImport
private void storeOpenaireQAEvents(List<QAEvent> events) { private void storeOpenaireQAEvents(List<QAEvent> events) {
for (QAEvent event : events) { for (QAEvent event : events) {
try { 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); storeOpenaireQAEvent(event);
} catch (RuntimeException e) { } catch (RuntimeException | SQLException e) {
handler.logWarning("An error occurs storing the event with id " handler.logWarning("An error occurs storing the event with id "
+ event.getEventId() + ": " + getMessage(e)); + 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. * Store the given QAEvent, skipping it if it is not supported.
* *

View File

@@ -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 * QASecurity that restrict access to the QA Source and related events only to repository administrators
* *
* @author Andrea Bollini (andrea.bollini at 4science.com) * @author Andrea Bollini (andrea.bollini at 4science.com)
*
*/ */
public class AdministratorsOnlyQASecurity implements QASecurity { public class AdministratorsOnlyQASecurity implements QASecurity {
@Autowired @Autowired
private AuthorizeService authorizeService; private AuthorizeService authorizeService;
@@ -30,6 +30,7 @@ public class AdministratorsOnlyQASecurity implements QASecurity {
return Optional.empty(); return Optional.empty();
} }
@Override
public boolean canSeeQASource(Context context, EPerson user) { public boolean canSeeQASource(Context context, EPerson user) {
try { try {
return authorizeService.isAdmin(context, user); return authorizeService.isAdmin(context, user);
@@ -38,6 +39,7 @@ public class AdministratorsOnlyQASecurity implements QASecurity {
} }
} }
@Override
public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) {
try { try {
return authorizeService.isAdmin(context, user); return authorizeService.isAdmin(context, user);

View File

@@ -14,7 +14,9 @@ import org.dspace.core.Context;
import org.dspace.eperson.EPerson; 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) * @author Andrea Bollini (andrea.bollini at 4science.com)
* *

View File

@@ -23,18 +23,16 @@ import org.springframework.beans.factory.annotation.Autowired;
* uuid * uuid
* *
* @author Andrea Bollini (andrea.bollini at 4science.com) * @author Andrea Bollini (andrea.bollini at 4science.com)
*
*/ */
public class UserBasedFilterQASecurity implements QASecurity { public class UserBasedFilterQASecurity implements QASecurity {
@Autowired
private AuthorizeService authorizeService; private String filterTemplate;
private boolean allowAdmins = true;
@Autowired @Autowired
private QAEventService qaEventService; private QAEventService qaEventService;
@Autowired
private String filterTemplate; private AuthorizeService authorizeService;
private boolean allowAdmins = true;
public Optional<String> generateFilterQuery(Context context, EPerson user) { public Optional<String> generateFilterQuery(Context context, EPerson user) {
try { try {
@@ -68,4 +66,5 @@ public class UserBasedFilterQASecurity implements QASecurity {
public void setAllowAdmins(boolean allowAdmins) { public void setAllowAdmins(boolean allowAdmins) {
this.allowAdmins = allowAdmins; this.allowAdmins = allowAdmins;
} }
} }

View File

@@ -23,20 +23,20 @@ public interface QAEventSecurityService {
/** /**
* Check if the specified user can see a specific QASource * Check if the specified user can see a specific QASource
* @param context the context * @param context the context
* @param user the eperson to consider * @param user the eperson to consider
* @param qaSource the qaSource involved * @param sourceName the source name
* @return <code>true</code> if the specified user can eventually see events in the QASource * @return <code>true</code> 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 * 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. * cannot be accessed. So implementation of this method should enforce this rule.
* *
* @param context the context * @param context the context
* @param user the eperson to consider * @param user the eperson to consider
* @param qaEvent the qaevent to check * @param qaEvent the qaevent to check
* @return <code>true</code> if the specified user can see the specified event * @return <code>true</code> if the specified user can see the specified event
*/ */
boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent); 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 * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the
* specified user * specified user
* *
* @param context the context * @param context the context
* @param user the eperson to consider * @param user the eperson to consider
* @param qaSource the qaSource involved * @param sourceName the source name
* @return the solr filter query * @return the solr filter query
*/ */
public Optional<String> generateQAEventFilterQuery(Context context, EPerson user, String qaSource); public Optional<String> generateQAEventFilterQuery(Context context, EPerson user, String sourceName);
} }

View File

@@ -27,8 +27,11 @@ public interface QAEventService {
/** /**
* Find all the event's topics. * Find all the event's topics.
* *
* @param offset the offset to apply * @param context the DSpace context
* @return the topics list * @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<QATopic> findAllTopics(Context context, long offset, long count, String orderField, boolean ascending); public List<QATopic> 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. * Find all the event's topics related to the given source.
* *
* @param context the DSpace context * @param context the DSpace context
* @param source the source to search for * @param source the source to search for
* @param offset the offset to apply * @param offset the offset to apply
* @param count the page size * @param count the page size
* @return the topics list * @param orderField the field to order for
* @param ascending true if the order should be ascending, false otherwise
* @return the topics list
*/ */
public List<QATopic> findAllTopicsBySource(Context context, String source, long offset, long count, public List<QATopic> 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. * 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. * Find all the events by topic sorted by trust descending.
* *
* @param context the DSpace context * @param context the DSpace context
* @param source the source name * @param sourceName the source name
* @param topic the topic to search for * @param topic the topic to search for
* @param offset the offset to apply * @param offset the offset to apply
* @param pageSize the page size * @param size the page size
* @return the events * @return the events
*/ */
public List<QAEvent> findEventsByTopicAndPage(Context context, String source, String topic, long offset, public List<QAEvent> findEventsByTopic(Context context, String sourceName, String topic, long offset, int size,
int pageSize); String orderField, boolean ascending);
/** /**
* Find all the events by topic. * Find all the events by topic.
* *
* @param context the DSpace context * @param context the DSpace context
* @param topic the topic to search for * @param sourceName the source name
* @return the events count * @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. * Find an event by the given id.
*
* @param context the DSpace context
* @param id the id of the event to search for * @param id the id of the event to search for
*
* @return the event * @return the event
*/ */
public QAEvent findEventByEventId(Context context, String id); public QAEvent findEventByEventId(Context context, String id);
@@ -139,7 +144,7 @@ public interface QAEventService {
/** /**
* Find all the event's sources. * Find all the event's sources.
* *
* @param context the DSpace context * @param context the DSpace context
* @param offset the offset to apply * @param offset the offset to apply
* @param pageSize the page size * @param pageSize the page size
* @return the sources list * @return the sources list
@@ -154,6 +159,25 @@ public interface QAEventService {
*/ */
public long countSources(Context context); 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. * 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 * Find a list of QA events according to the pagination parameters for the specified topic and target sorted by
* trust descending * trust descending
* *
* @param context the DSpace context * @param context the DSpace context
* @param source the source name * @param source the source name
* @param topic the topic to search for * @param topic the topic to search for
@@ -203,16 +227,6 @@ public interface QAEventService {
public List<QATopic> findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, public List<QATopic> findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset,
long count, String orderField, boolean ascending); 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 * Find all the event's sources related to a specific item
* *
@@ -224,15 +238,6 @@ public interface QAEventService {
*/ */
public List<QASource> findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize); public List<QASource> 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 * Check if a qaevent with the provided id is visible to the current user according to the source security
* *

View File

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

View File

@@ -17,5 +17,4 @@ import org.dspace.content.QAEvent;
*/ */
public interface QAMessageDTO { public interface QAMessageDTO {
} }

View File

@@ -23,6 +23,7 @@ import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.QAEvent; import org.dspace.content.QAEvent;
@@ -41,7 +42,8 @@ import org.springframework.beans.factory.annotation.Autowired;
* *
*/ */
public class QAEventActionServiceImpl implements QAEventActionService { 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; private ObjectMapper jsonMapper;

View File

@@ -17,14 +17,27 @@ import org.dspace.eperson.EPerson;
import org.dspace.qaevent.security.QASecurity; import org.dspace.qaevent.security.QASecurity;
import org.dspace.qaevent.service.QAEventSecurityService; 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 { 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 static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventSecurityServiceImpl.class);
private Map<String, QASecurity> qaSecurityConfiguration; private Map<String, QASecurity> qaSecurityConfiguration;
private QASecurity defaultSecurity;
public void setQaSecurityConfiguration(Map<String, QASecurity> qaSecurityConfiguration) { public void setQaSecurityConfiguration(Map<String, QASecurity> qaSecurityConfiguration) {
this.qaSecurityConfiguration = qaSecurityConfiguration; this.qaSecurityConfiguration = qaSecurityConfiguration;
} }
@@ -47,8 +60,7 @@ public class QAEventSecurityServiceImpl implements QAEventSecurityService {
public boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent) { public boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent) {
String source = qaEvent.getSource(); String source = qaEvent.getSource();
QASecurity qaSecurity = getQASecurity(source); QASecurity qaSecurity = getQASecurity(source);
return qaSecurity.canSeeQASource(context, user) return qaSecurity.canSeeQASource(context, user) && qaSecurity.canSeeQAEvent(context, user, qaEvent);
&& qaSecurity.canSeeQAEvent(context, user, qaEvent);
} }
@Override @Override

View File

@@ -23,6 +23,7 @@ import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.commons.lang3.ArrayUtils; 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.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.FacetParams; import org.apache.solr.common.params.FacetParams;
import org.dspace.content.Item;
import org.dspace.content.QAEvent; import org.dspace.content.QAEvent;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson; 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.AutomaticProcessingAction;
import org.dspace.qaevent.QAEventAutomaticProcessingEvaluation; import org.dspace.qaevent.QAEventAutomaticProcessingEvaluation;
import org.dspace.qaevent.QASource; import org.dspace.qaevent.QASource;
@@ -58,6 +60,8 @@ import org.dspace.qaevent.service.QAEventSecurityService;
import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.QAEventService;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory; 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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@@ -84,9 +88,6 @@ public class QAEventServiceImpl implements QAEventService {
@Autowired(required = true) @Autowired(required = true)
protected ItemService itemService; protected ItemService itemService;
@Autowired
private HandleService handleService;
@Autowired @Autowired
private QAEventsDAOImpl qaEventsDao; private QAEventsDAOImpl qaEventsDao;
@@ -154,23 +155,19 @@ public class QAEventServiceImpl implements QAEventService {
} }
@Override @Override
public long countTopicsBySourceAndTarget(Context context, String source, UUID target) { public long countTopicsBySource(Context context, String sourceName) {
if (isNotSupportedSource(source) var currentUser = context.getCurrentUser();
|| !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) {
return 0; return 0;
} }
SolrQuery solrQuery = new SolrQuery(); SolrQuery solrQuery = new SolrQuery();
solrQuery.setRows(0); solrQuery.setRows(0);
Optional<String> securityQuery = qaSecurityService.generateQAEventFilterQuery(context, Optional<String> securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName);
context.getCurrentUser(), source);
solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setQuery(securityQuery.orElse("*:*"));
solrQuery.setFacet(true); solrQuery.setFacet(true);
solrQuery.setFacetMinCount(1); solrQuery.setFacetMinCount(1);
solrQuery.addFacetField(TOPIC); solrQuery.addFacetField(TOPIC);
solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); solrQuery.addFilterQuery("source:" + sourceName);
if (target != null) {
solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\"");
}
QueryResponse response; QueryResponse response;
try { try {
response = getSolr().query(solrQuery); response = getSolr().query(solrQuery);
@@ -180,6 +177,44 @@ public class QAEventServiceImpl implements QAEventService {
return response.getFacetField(TOPIC).getValueCount(); 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<String> 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 @Override
public void deleteEventByEventId(String id) { public void deleteEventByEventId(String id) {
try { try {
@@ -281,7 +316,6 @@ public class QAEventServiceImpl implements QAEventService {
try { try {
response = getSolr().query(solrQuery); response = getSolr().query(solrQuery);
FacetField facetField = response.getFacetField(TOPIC); FacetField facetField = response.getFacetField(TOPIC);
topics = new ArrayList<>();
int idx = 0; int idx = 0;
for (Count c : facetField.getValues()) { for (Count c : facetField.getValues()) {
if (idx < offset) { if (idx < offset) {
@@ -366,13 +400,45 @@ public class QAEventServiceImpl implements QAEventService {
} }
@Override /**
public QAEvent findEventByEventId(Context context, String eventId) { * Sends an email notification to the system administrator about a new
SolrQuery param = new SolrQuery("*:*"); * Quality Assurance (QA) request event. The email includes details such as the
param.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); * topic, target, and message associated with the QA event.
QueryResponse response; *
* @param qaEvent The Quality Assurance event for which the notification is generated.
*/
public void sentEmailToAdminAboutNewRequest(QAEvent qaEvent) {
try { 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) { if (response != null) {
SolrDocumentList list = response.getResults(); SolrDocumentList list = response.getResults();
if (list != null && list.size() == 1) { if (list != null && list.size() == 1) {
@@ -386,28 +452,9 @@ public class QAEventServiceImpl implements QAEventService {
return null; return null;
} }
@Override
public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source) {
SolrQuery solrQuery = new SolrQuery();
Optional<String> 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 @Override
public List<QAEvent> findEventsByTopicAndPage(Context context, String source, String topic, long offset, public List<QAEvent> findEventsByTopicAndPage(Context context, String source, String topic, long offset,
int pageSize) { int pageSize) {
if (isNotSupportedSource(source) if (isNotSupportedSource(source)
|| !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) {
return List.of(); return List.of();
@@ -442,7 +489,7 @@ public class QAEventServiceImpl implements QAEventService {
@Override @Override
public List<QAEvent> findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset, public List<QAEvent> findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset,
int pageSize, UUID target) { int pageSize, UUID target) {
if (isNotSupportedSource(source) if (isNotSupportedSource(source)
|| !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) {
return List.of(); return List.of();
@@ -480,49 +527,23 @@ public class QAEventServiceImpl implements QAEventService {
} }
@Override @Override
public long countEventsByTopic(Context context, String source, String topic) { public long countEventsByTopic(Context context, String sourceName, String topic) {
if (isNotSupportedSource(source) EPerson currentUser = context.getCurrentUser();
|| !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) {
return 0; return 0;
} }
SolrQuery solrQuery = new SolrQuery();
solrQuery.setRows(0);
Optional<String> 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 Optional<String> securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName);
public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target) {
if (isNotSupportedSource(source)
|| !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) {
return 0;
}
SolrQuery solrQuery = new SolrQuery(); SolrQuery solrQuery = new SolrQuery();
solrQuery.setRows(0); solrQuery.setRows(0);
Optional<String> securityQuery = qaSecurityService.generateQAEventFilterQuery(context, context.getCurrentUser(),
source);
solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setQuery(securityQuery.orElse("*:*"));
if (target != null) { solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\"");
solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/"));
}
solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\"");
solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\"");
QueryResponse response = null;
try { try {
response = getSolr().query(solrQuery); return getSolr().query(solrQuery).getResults().getNumFound();
return response.getResults().getNumFound();
} catch (SolrServerException | IOException e) { } catch (SolrServerException | IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e.getMessage(), e);
} }
} }
@@ -534,15 +555,14 @@ public class QAEventServiceImpl implements QAEventService {
@Override @Override
public QASource findSource(Context context, String sourceName, UUID target) { public QASource findSource(Context context, String sourceName, UUID target) {
EPerson currentUser = context.getCurrentUser();
if (isNotSupportedSource(sourceName) if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) {
|| !qaSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName)) {
return null; return null;
} }
Optional<String> securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName);
SolrQuery solrQuery = new SolrQuery(); SolrQuery solrQuery = new SolrQuery();
Optional<String> securityQuery = qaSecurityService.generateQAEventFilterQuery(context, context.getCurrentUser(),
sourceName);
solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setQuery(securityQuery.orElse("*:*"));
solrQuery.setRows(0); solrQuery.setRows(0);
solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\"");
@@ -553,9 +573,8 @@ public class QAEventServiceImpl implements QAEventService {
solrQuery.setFacetMinCount(1); solrQuery.setFacetMinCount(1);
solrQuery.addFacetField(SOURCE); solrQuery.addFacetField(SOURCE);
QueryResponse response;
try { try {
response = getSolr().query(solrQuery); QueryResponse response = getSolr().query(solrQuery);
FacetField facetField = response.getFacetField(SOURCE); FacetField facetField = response.getFacetField(SOURCE);
for (Count c : facetField.getValues()) { for (Count c : facetField.getValues()) {
if (c.getName().equalsIgnoreCase(sourceName)) { if (c.getName().equalsIgnoreCase(sourceName)) {
@@ -568,7 +587,7 @@ public class QAEventServiceImpl implements QAEventService {
} }
} }
} catch (SolrServerException | IOException e) { } catch (SolrServerException | IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e.getMessage(), e);
} }
QASource source = new QASource(); QASource source = new QASource();
@@ -581,12 +600,13 @@ public class QAEventServiceImpl implements QAEventService {
@Override @Override
public List<QASource> findAllSources(Context context, long offset, int pageSize) { public List<QASource> findAllSources(Context context, long offset, int pageSize) {
return Arrays.stream(getSupportedSources()) return Arrays.stream(getSupportedSources())
.map((sourceName) -> findSource(context, sourceName)) .map((sourceName) -> findSource(context, sourceName))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.sorted(comparing(QASource::getTotalEvents).reversed()) .sorted(comparing(QASource::getTotalEvents)
.skip(offset) .reversed())
.limit(pageSize) .skip(offset)
.collect(Collectors.toList()); .limit(pageSize)
.collect(Collectors.toList());
} }
@Override @Override
@@ -635,12 +655,7 @@ public class QAEventServiceImpl implements QAEventService {
doc.addField(TRUST, dto.getTrust()); doc.addField(TRUST, dto.getTrust());
doc.addField(MESSAGE, dto.getMessage()); doc.addField(MESSAGE, dto.getMessage());
doc.addField(LAST_UPDATE, new Date()); doc.addField(LAST_UPDATE, new Date());
final String resourceUUID = getResourceUUID(context, dto.getOriginalId()); doc.addField(RESOURCE_UUID, dto.getTarget());
if (resourceUUID == null) {
throw new IllegalArgumentException("Skipped event " + checksum +
" related to the oai record " + dto.getOriginalId() + " as the record was not found");
}
doc.addField(RESOURCE_UUID, resourceUUID);
doc.addField(RELATED_UUID, dto.getRelated()); doc.addField(RELATED_UUID, dto.getRelated());
return doc; return doc;
} }
@@ -686,13 +701,173 @@ public class QAEventServiceImpl implements QAEventService {
return item; return item;
} }
@Override
public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source) {
SolrQuery solrQuery = new SolrQuery();
Optional<String> 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<String> 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<QAEvent> 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<String> 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<QAEvent> responseItem = new ArrayList<>();
for (SolrDocument doc : list) {
QAEvent item = getQAEventFromSOLR(doc);
responseItem.add(item);
}
return responseItem;
}
} catch (SolrServerException | IOException e) {
throw new RuntimeException(e);
}
return List.of();
}
private boolean isNotSupportedSource(String source) { private boolean isNotSupportedSource(String source) {
return !ArrayUtils.contains(getSupportedSources(), source); return !ArrayUtils.contains(getSupportedSources(), source);
} }
private String[] getSupportedSources() { private String[] getSupportedSources() {
return configurationService.getArrayProperty("qaevent.sources", return configurationService.getArrayProperty(QAEVENTS_SOURCES,
new String[] { QAEvent.OPENAIRE_SOURCE, QAEvent.COAR_NOTIFY_SOURCE }); 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<String> 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<QASource> 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<QATopic> 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<String> 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<QATopic> 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);
}
}
} }

View File

@@ -169,7 +169,7 @@ public class S3BitStoreService extends BaseBitStoreService {
@Override @Override
public void init() throws IOException { public void init() throws IOException {
if (this.isInitialized()) { if (this.isInitialized() || !this.isEnabled()) {
return; return;
} }

View File

@@ -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.COAR_NOTIFY_SOURCE;
import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; import static org.dspace.content.QAEvent.OPENAIRE_SOURCE;
import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith; 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.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@@ -57,10 +58,8 @@ import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace; import org.dspace.utils.DSpace;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
/** /**
* Integration tests for {@link OpenaireEventsImport}. * Integration tests for {@link OpenaireEventsImport}.
* *
@@ -69,9 +68,8 @@ import org.junit.Test;
*/ */
public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase { 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 ORDER_FIELD = "topic";
private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/";
private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class); private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class);
@@ -101,8 +99,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
.build(); .build();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
configurationService.setProperty("qaevent.sources", new String[] configurationService.setProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE });
{ QAEvent.OPENAIRE_SOURCE });
((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient);
} }
@@ -151,7 +148,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testManyEventsImportFromFile() throws Exception { public void testManyEventsImportFromFile() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -207,7 +203,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d)));
verifyNoInteractions(mockBrokerClient); verifyNoInteractions(mockBrokerClient);
} }
@Test @Test
@@ -226,12 +221,12 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(), assertThat(handler.getWarningMessages(),
contains("An error occurs storing the event with id b4e09c71312cd7c397969f56c900823f: " contains("An error occurs storing the event with id 406fb9c5656c7f11cac8995abb746887: "
+ "Skipped event b4e09c71312cd7c397969f56c900823f related to the oai record " + "Skipped event 406fb9c5656c7f11cac8995abb746887 related to the oai record "
+ "oai:www.openstarts.units.it:123456789/99998 as the record was not found", + "oai:www.openstarts.units.it:123456789/99998 as the record was not found",
"An error occurs storing the event with id d050d2c4399c6c6ccf27d52d479d26e4: " "An error occurs storing the event with id eafd747feee49cca7603d30ba4e768dc: "
+ "Skipped event d050d2c4399c6c6ccf27d52d479d26e4 related to the oai record " + "Skipped event eafd747feee49cca7603d30ba4e768dc related to the oai record "
+ "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + "oai:www.openstarts.units.it:123456789/99998 as the record was not found"));
assertThat(handler.getInfoMessages(), contains( assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the provided file", "Trying to read the QA events from the provided file",
"Found 5 events in the given file")); "Found 5 events in the given file"));
@@ -246,14 +241,11 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains(
QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20),
contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication",
abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d)));
verifyNoInteractions(mockBrokerClient); verifyNoInteractions(mockBrokerClient);
} }
@Test @Test
@@ -273,7 +265,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getErrorMessages(), empty());
assertThat(handler.getWarningMessages(), 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( assertThat(handler.getInfoMessages(), contains(
"Trying to read the QA events from the provided file", "Trying to read the QA events from the provided file",
"Found 2 events in the given file")); "Found 2 events in the given file"));
@@ -285,14 +279,11 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains(
QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20),
contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2",
abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d)));
verifyNoInteractions(mockBrokerClient); verifyNoInteractions(mockBrokerClient);
} }
@Test @Test
@@ -310,13 +301,12 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); 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); verifyNoInteractions(mockBrokerClient);
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testImportFromOpenaireBroker() throws Exception { public void testImportFromOpenaireBroker() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -371,18 +361,14 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
+ "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\","
+ "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}";
assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains(
QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20),
contains(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem,
"Egypt, crossroad of translations and literary interweavings", projectMessage, "Egypt, crossroad of translations and literary interweavings", projectMessage,
QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d))); QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d)));
String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}";
List<QAEvent> eventList = qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), containsInAnyOrder(
QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20);
assertThat(eventList, hasItem(
pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication",
abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d)));
assertThat(eventList, hasItem( 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.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"); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com");
@@ -426,7 +412,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception {
context.turnOffAuthorisationSystem(); 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_PROJECT, 1L)));
assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L)));
assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE, assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1));
QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20), assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2));
hasSize(1));
assertThat(qaEventService.findEventsByTopicAndPage(context, OPENAIRE_SOURCE,
QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20),
hasSize(2));
verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com");
verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); 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()); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any());
verifyNoMoreInteractions(mockBrokerClient); verifyNoMoreInteractions(mockBrokerClient);
} }
/** /**

View File

@@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -42,6 +43,7 @@ import com.amazonaws.services.s3.model.ObjectMetadata;
import io.findify.s3mock.S3Mock; import io.findify.s3mock.S3Mock;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.BooleanUtils;
import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.app.matcher.LambdaMatcher; import org.dspace.app.matcher.LambdaMatcher;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
@@ -53,6 +55,8 @@ import org.dspace.content.Bitstream;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Utils; import org.dspace.core.Utils;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
@@ -60,6 +64,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
/** /**
* @author Luca Giamminonni (luca.giamminonni at 4science.com) * @author Luca Giamminonni (luca.giamminonni at 4science.com)
*/ */
@@ -77,9 +82,13 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase {
private File s3Directory; private File s3Directory;
private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
configurationService.setProperty("assetstore.s3.enabled", "true");
s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3"); s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3");
s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath()); s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath());
@@ -88,7 +97,8 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase {
amazonS3Client = createAmazonS3Client(); amazonS3Client = createAmazonS3Client();
s3BitStoreService = new S3BitStoreService(amazonS3Client); s3BitStoreService = new S3BitStoreService(amazonS3Client);
s3BitStoreService.setEnabled(BooleanUtils.toBoolean(
configurationService.getProperty("assetstore.s3.enabled")));
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context) parentCommunity = CommunityBuilder.createCommunity(context)
@@ -382,6 +392,17 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase {
assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); 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) { private byte[] generateChecksum(String content) {
try { try {
MessageDigest m = MessageDigest.getInstance("MD5"); MessageDigest m = MessageDigest.getInstance("MD5");

View File

@@ -78,7 +78,6 @@ public class QAEventRelatedRestController {
@RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { @RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException {
Context context = ContextUtil.obtainCurrentRequestContext(); Context context = ContextUtil.obtainCurrentRequestContext();
QAEvent qaevent = qaEventService.findEventByEventId(context, qaeventId); QAEvent qaevent = qaEventService.findEventByEventId(context, qaeventId);
if (qaevent == null) { if (qaevent == null) {
throw new ResourceNotFoundException("No such qa event: " + qaeventId); throw new ResourceNotFoundException("No such qa event: " + qaeventId);

View File

@@ -15,7 +15,6 @@ import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.model.VersionRest;
import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.projection.DefaultProjection;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.dspace.versioning.Version; 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") description = "It can be used to verify if the user can delete a version of an Item")
public class CanDeleteVersionFeature extends DeleteFeature { public class CanDeleteVersionFeature extends DeleteFeature {
@Autowired
private ItemService itemService;
@Autowired @Autowired
private ItemConverter itemConverter; private ItemConverter itemConverter;
@Autowired @Autowired

View File

@@ -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<CorrectionType, CorrectionTypeRest> {
@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<CorrectionType> getModelClass() {
return CorrectionType.class;
}
}

View File

@@ -8,6 +8,8 @@
package org.dspace.app.rest.converter; package org.dspace.app.rest.converter;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import com.fasterxml.jackson.core.JsonProcessingException; 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.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import org.dspace.app.rest.model.NotifyQAEventMessageRest; 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.OpenaireQAEventMessageRest;
import org.dspace.app.rest.model.QAEventMessageRest; import org.dspace.app.rest.model.QAEventMessageRest;
import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.QAEventRest;
import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.projection.Projection;
import org.dspace.content.QAEvent; import org.dspace.content.QAEvent;
import org.dspace.qaevent.service.dto.NotifyMessageDTO; 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.OpenaireMessageDTO;
import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
@@ -56,9 +60,9 @@ public class QAEventConverter implements DSpaceConverter<QAEvent, QAEventRest> {
rest.setId(modelObject.getEventId()); rest.setId(modelObject.getEventId());
try { try {
rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(),
modelObject.getMessageDtoClass()))); modelObject.getMessageDtoClass())));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e.getMessage(), e);
} }
rest.setSource(modelObject.getSource()); rest.setSource(modelObject.getSource());
rest.setOriginalId(modelObject.getOriginalId()); rest.setOriginalId(modelObject.getOriginalId());
@@ -66,7 +70,8 @@ public class QAEventConverter implements DSpaceConverter<QAEvent, QAEventRest> {
rest.setTitle(modelObject.getTitle()); rest.setTitle(modelObject.getTitle());
rest.setTopic(modelObject.getTopic()); rest.setTopic(modelObject.getTopic());
rest.setEventDate(modelObject.getLastUpdate()); 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 // right now only the pending status can be found in persisted qa events
rest.setStatus(modelObject.getStatus()); rest.setStatus(modelObject.getStatus());
return rest; return rest;
@@ -78,6 +83,9 @@ public class QAEventConverter implements DSpaceConverter<QAEvent, QAEventRest> {
} else if (dto instanceof NotifyMessageDTO) { } else if (dto instanceof NotifyMessageDTO) {
return convertNotifyMessage(dto); return convertNotifyMessage(dto);
} }
if (dto instanceof CorrectionTypeMessageDTO) {
return convertCorrectionTypeMessage(dto);
}
throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); throw new IllegalArgumentException("Unknown message type: " + dto.getClass());
} }
@@ -91,6 +99,13 @@ public class QAEventConverter implements DSpaceConverter<QAEvent, QAEventRest> {
return message; 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) { private QAEventMessageRest convertOpenaireMessage(QAMessageDTO dto) {
OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto; OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto;
OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest(); OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest();

View File

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

View File

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

View File

@@ -19,9 +19,9 @@ import org.dspace.app.rest.RestResourceController;
*/ */
@LinksRest( @LinksRest(
links = { links = {
@LinkRest(name = "topic", method = "getTopic"), @LinkRest(name = "topic", method = "getTopic"),
@LinkRest(name = "target", method = "getTarget"), @LinkRest(name = "target", method = "getTarget"),
@LinkRest(name = "related", method = "getRelated") @LinkRest(name = "related", method = "getRelated")
}) })
public class QAEventRest extends BaseObjectRest<String> { public class QAEventRest extends BaseObjectRest<String> {

View File

@@ -0,0 +1,27 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.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<CorrectionTypeRest> {
public CorrectionTypeResource(CorrectionTypeRest target, Utils utils) {
super(target, utils);
}
}

View File

@@ -0,0 +1,93 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.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<CorrectionTypeRest, String> {
@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<CorrectionTypeRest> findAll(Context context, Pageable pageable) {
return converter.toRestPage(correctionTypeService.findAll(), pageable, utils.obtainProjection());
}
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@SearchRestMethod(name = "findByItem")
public Page<CorrectionTypeRest> 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<CorrectionType> 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<CorrectionTypeRest> getDomainClass() {
return CorrectionTypeRest.class;
}
}

View File

@@ -7,14 +7,18 @@
*/ */
package org.dspace.app.rest.repository; package org.dspace.app.rest.repository;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.servlet.http.HttpServletRequest; 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.Parameter;
import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; 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.QAEventRest;
import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.model.patch.Patch;
import org.dspace.app.rest.repository.patch.ResourcePatch; 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.QAEvent;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.correctiontype.CorrectionType;
import org.dspace.correctiontype.service.CorrectionTypeService;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.qaevent.dao.QAEventsDAO;
import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.QAEventService;
import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO;
import org.dspace.util.UUIDUtils; import org.dspace.util.UUIDUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -56,6 +63,14 @@ public class QAEventRestRepository extends DSpaceRestRepository<QAEventRest, Str
@Autowired @Autowired
private ResourcePatch<QAEvent> resourcePatch; private ResourcePatch<QAEvent> resourcePatch;
@Autowired
private CorrectionTypeService correctionTypeService;
@Override
public Page<QAEventRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll");
}
@Override @Override
@PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')")
public QAEventRest findOne(Context context, String id) { public QAEventRest findOne(Context context, String id) {
@@ -84,11 +99,9 @@ public class QAEventRestRepository extends DSpaceRestRepository<QAEventRest, Str
String sourceName = topicIdSplitted[0]; String sourceName = topicIdSplitted[0];
String topicName = topicIdSplitted[1].replaceAll("!", "/"); String topicName = topicIdSplitted[1].replaceAll("!", "/");
UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null;
List<QAEvent> qaEvents = null; List<QAEvent> qaEvents = qaEventService.findEventsByTopicAndPageAndTarget(context, sourceName, topicName,
long count = 0L;
qaEvents = qaEventService.findEventsByTopicAndPageAndTarget(context, sourceName, topicName,
pageable.getOffset(), pageable.getPageSize(), target); pageable.getOffset(), pageable.getPageSize(), target);
count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target); long count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target);
if (qaEvents == null) { if (qaEvents == null) {
return null; return null;
} }
@@ -104,11 +117,6 @@ public class QAEventRestRepository extends DSpaceRestRepository<QAEventRest, Str
qaEventDao.storeEvent(context, id, eperson, item); qaEventDao.storeEvent(context, id, eperson, item);
} }
@Override
public Page<QAEventRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll");
}
@Override @Override
@PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'WRITE')") @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'WRITE')")
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, protected void patch(Context context, HttpServletRequest request, String apiCategory, String model,
@@ -130,6 +138,64 @@ public class QAEventRestRepository extends DSpaceRestRepository<QAEventRest, Str
} }
} }
@Override
@PreAuthorize("hasAuthority('AUTHENTICATED')")
protected QAEventRest createAndReturn(Context context) throws SQLException, AuthorizeException {
ServletRequest request = getRequestService().getCurrentRequest().getServletRequest();
String itemUUID = request.getParameter("target");
String relatedItemUUID = request.getParameter("related");
String correctionTypeStr = request.getParameter("correctionType");
if (StringUtils.isBlank(correctionTypeStr) || StringUtils.isBlank(itemUUID)) {
throw new UnprocessableEntityException("The target item and correctionType must be provided!");
}
Item targetItem = null;
Item relatedItem = null;
try {
targetItem = itemService.find(context, UUID.fromString(itemUUID));
relatedItem = StringUtils.isNotBlank(relatedItemUUID) ?
itemService.find(context, UUID.fromString(relatedItemUUID)) : null;
} catch (Exception e) {
throw new UnprocessableEntityException(e.getMessage(), e);
}
if (Objects.isNull(targetItem)) {
throw new UnprocessableEntityException("The target item UUID is not valid!");
}
CorrectionType correctionType = correctionTypeService.findOne(correctionTypeStr);
if (Objects.isNull(correctionType)) {
throw new UnprocessableEntityException("The given correction type in the request is not valid!");
}
if (correctionType.isRequiredRelatedItem() && Objects.isNull(relatedItem)) {
throw new UnprocessableEntityException("The given correction type in the request is not valid!");
}
if (!correctionType.isAllowed(context, targetItem)) {
throw new UnprocessableEntityException("This item cannot be processed by this correction type!");
}
ObjectMapper mapper = new ObjectMapper();
CorrectionTypeMessageDTO reason = null;
try {
reason = mapper.readValue(request.getInputStream(), CorrectionTypeMessageDTO.class);
} catch (IOException exIO) {
throw new UnprocessableEntityException("error parsing the body " + exIO.getMessage(), exIO);
}
QAEvent qaEvent;
if (correctionType.isRequiredRelatedItem()) {
qaEvent = correctionType.createCorrection(context, targetItem, relatedItem, reason);
} else {
qaEvent = correctionType.createCorrection(context, targetItem, reason);
}
return converter.toRest(qaEvent, utils.obtainProjection());
}
@Override @Override
public Class<QAEventRest> getDomainClass() { public Class<QAEventRest> getDomainClass() {
return QAEventRest.class; return QAEventRest.class;

View File

@@ -48,7 +48,7 @@ public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository im
* @param id the qa event id * @param id the qa event id
* @param pageable the optional pageable * @param pageable the optional pageable
* @param projection the projection object * @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')") @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')")
public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable,

View File

@@ -42,7 +42,7 @@ public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository imp
* @param id the qa event id * @param id the qa event id
* @param pageable the optional pageable * @param pageable the optional pageable
* @param projection the projection object * @param projection the projection object
* @return the qa topic rest representation * @return the qa topic rest representation
*/ */
@PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')")
public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable,

View File

@@ -55,10 +55,10 @@ public class QASourceRestRepository extends DSpaceRestRepository<QASourceRest, S
@SearchRestMethod(name = "byTarget") @SearchRestMethod(name = "byTarget")
@PreAuthorize("hasAuthority('AUTHENTICATED')") @PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<QASourceRest> findByTarget(@Parameter(value = "target", required = true) UUID target, public Page<QASourceRest> findByTarget(@Parameter(value = "target", required = true) UUID target,
Pageable pageable) { Pageable pageable) {
Context context = obtainContext(); Context context = obtainContext();
List<QASource> topics = qaEventService List<QASource> topics = qaEventService.findAllSourcesByTarget(context, target,
.findAllSourcesByTarget(context, target, pageable.getOffset(), pageable.getPageSize()); pageable.getOffset(), pageable.getPageSize());
long count = qaEventService.countSourcesByTarget(context, target); long count = qaEventService.countSourcesByTarget(context, target);
if (topics == null) { if (topics == null) {
return null; return null;

View File

@@ -42,6 +42,11 @@ public class QATopicRestRepository extends DSpaceRestRepository<QATopicRest, Str
private static final Logger log = LogManager.getLogger(); private static final Logger log = LogManager.getLogger();
@Override
public Page<QATopicRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException("Method not allowed!", "");
}
@Override @Override
@PreAuthorize("hasPermission(#id, 'QUALITYASSURANCETOPIC', 'READ')") @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCETOPIC', 'READ')")
public QATopicRest findOne(Context context, String id) { public QATopicRest findOne(Context context, String id) {
@@ -53,30 +58,21 @@ public class QATopicRestRepository extends DSpaceRestRepository<QATopicRest, Str
String topicName = topicIdSplitted[1].replaceAll("!", "/"); String topicName = topicIdSplitted[1].replaceAll("!", "/");
UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null;
QATopic topic = qaEventService.findTopicBySourceAndNameAndTarget(context, sourceName, topicName, target); QATopic topic = qaEventService.findTopicBySourceAndNameAndTarget(context, sourceName, topicName, target);
if (topic == null) { return (topic != null) ? converter.toRest(topic, utils.obtainProjection()) : null;
return null;
}
return converter.toRest(topic, utils.obtainProjection());
}
@Override
public Page<QATopicRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException("Method not allowed!", "");
} }
@SearchRestMethod(name = "bySource") @SearchRestMethod(name = "bySource")
@PreAuthorize("hasPermission(#source, 'QUALITYASSURANCETOPIC', 'READ')") @PreAuthorize("hasPermission(#source, 'QUALITYASSURANCETOPIC', 'READ')")
public Page<QATopicRest> findBySource(@Parameter(value = "source", required = true) String source, public Page<QATopicRest> findBySource(@Parameter(value = "source", required = true) String source,
Pageable pageable) { Pageable pageable) {
Context context = obtainContext(); Context context = obtainContext();
long count = qaEventService.countTopicsBySource(context, source);
boolean ascending = false; boolean ascending = false;
if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) {
ascending = pageable.getSort() ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC;
.getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC;
} }
List<QATopic> topics = qaEventService.findAllTopicsBySource(context, source, List<QATopic> 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) { if (topics == null) {
return null; return null;
} }
@@ -86,20 +82,15 @@ public class QATopicRestRepository extends DSpaceRestRepository<QATopicRest, Str
@SearchRestMethod(name = "byTarget") @SearchRestMethod(name = "byTarget")
@PreAuthorize("hasPermission(#target, 'ITEM', 'READ')") @PreAuthorize("hasPermission(#target, 'ITEM', 'READ')")
public Page<QATopicRest> findByTarget(@Parameter(value = "target", required = true) UUID target, public Page<QATopicRest> findByTarget(@Parameter(value = "target", required = true) UUID target,
@Parameter(value = "source", required = true) String source, @Parameter(value = "source", required = true) String source, Pageable pageable) {
Pageable pageable) {
Context context = obtainContext(); Context context = obtainContext();
List<QATopic> topics = qaEventService boolean ascending = false;
.findAllTopicsBySourceAndTarget(context, source, target, pageable.getOffset(), if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) {
pageable.getPageSize(), null, true); ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC;
long count = qaEventService.countTopicsBySourceAndTarget(context, source, target);
if (topics == null) {
return null;
} }
return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); List<QATopic> topics = qaEventService.findAllTopicsBySourceAndTarget(context, source, target,
} pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending);
long count = qaEventService.countTopicsBySourceAndTarget(context, source, target);
@Override @Override
public Class<QATopicRest> getDomainClass() { public Class<QATopicRest> getDomainClass() {
return QATopicRest.class; return QATopicRest.class;

View File

@@ -0,0 +1,72 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.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<CorrectionType> {
@Autowired
private CorrectionTypeService correctionTypeService;
@Override
@SuppressWarnings("rawtypes")
public boolean supports(List<String> uriList, String method,Class clazz) {
return clazz != CorrectionType.class ? false : true;
}
@Override
public CorrectionType handle(Context context, HttpServletRequest request, List<String> uriList)
throws SQLException, AuthorizeException {
return getObjectFromUriList(context, uriList);
}
@Override
public boolean validate(Context context, HttpServletRequest request, List<String> uriList)
throws AuthorizeException {
return uriList.size() > 1 ? false : true;
}
private CorrectionType getObjectFromUriList(Context context, List<String> 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;
}
}

View File

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

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest; package org.dspace.app.rest;
import static org.dspace.app.rest.matcher.QASourceMatcher.matchQASourceEntry; 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.contains;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -45,35 +46,31 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest
@Before @Before
public void setup() { public void setup() {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context) parentCommunity = CommunityBuilder.createCommunity(context)
.withTitle("Community") .withTitle("Community")
.build(); .build();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity) Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection") .withName("Collection")
.build(); .build();
target = ItemBuilder.createItem(context, collection) target = ItemBuilder.createItem(context, collection)
.withTitle("Item") .withTitle("Item")
.build(); .build();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
configurationService.setProperty("qaevent.sources", configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire","coar-notify", "test-source","test-source-2" });
new String[] { "openaire", "coar-notify", "test-source", "test-source-2" });
} }
@Test @Test
public void testFindAll() throws Exception { public void testFindAll() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 2"); createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 2");
context.setCurrentUser(eperson);
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3"); createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3");
createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 4"); createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 4");
@@ -111,7 +108,6 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest
@Test @Test
public void testFindAllUnauthorized() throws Exception { public void testFindAllUnauthorized() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1");
@@ -120,8 +116,7 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest
context.restoreAuthSystemState(); context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassurancesources")) getClient().perform(get("/api/integration/qualityassurancesources"))
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test @Test

View File

@@ -7,6 +7,8 @@
*/ */
package org.dspace.app.rest; 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.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -44,74 +46,60 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test @Test
public void findAllNotImplementedTest() throws Exception { public void findAllNotImplementedTest() throws Exception {
context.turnOffAuthorisationSystem(); String adminToken = getAuthToken(admin.getEmail(), password);
parentCommunity = CommunityBuilder.createCommunity(context) getClient(adminToken).perform(get("/api/integration/qualityassurancetopics"))
.withName("Parent Community") .andExpect(status().isMethodNotAllowed());
.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());
} }
@Test @Test
public void findOneTest() throws Exception { public void findOneTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
configurationService.setProperty("qaevent.sources", configurationService.setProperty(QAEVENTS_SOURCES, new String[] { OPENAIRE_SOURCE, "test-source" });
new String[] { "openaire", "test-source" });
parentCommunity = CommunityBuilder.createCommunity(context) parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community") .withName("Parent Community")
.build(); .build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom") QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .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();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .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();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_PID) .withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_PID)
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}")
.build();
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) .withTopic(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT)
.withMessage( .withMessage("{\"test\": \"Test...\"}")
"{\"test\": \"Test...\"}") .build();
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") QAEventBuilder.createTarget(context, col1, "Science and Freedom 4")
.withSource("test-source") .withSource("test-source")
.withTopic("TOPIC/TEST") .withTopic("TOPIC/TEST")
.withMessage( .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
"{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") .build();
.build();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/openaire:ENRICH!MISSING!PID")) String adminToken = getAuthToken(admin.getEmail(), password);
.andExpect(status().isOk()) getClient(adminToken).perform(
.andExpect(jsonPath("$", get("/api/integration/qualityassurancetopics/" + OPENAIRE_SOURCE + ":ENRICH!MISSING!PID"))
QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2))); .andExpect(status().isOk())
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/openaire:ENRICH!MISSING!ABSTRACT")) .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry(
.andExpect(status().isOk()) QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2)));
.andExpect(jsonPath("$",
QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1))); getClient(adminToken).perform(get("/api/integration/qualityassurancetopics/"
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/test-source:TOPIC!TEST")) + OPENAIRE_SOURCE + ":ENRICH!MISSING!ABSTRACT"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$", .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry(
QATopicMatcher.matchQATopicEntry("test-source", "TOPIC/TEST", 1))); 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 @Test
@@ -176,68 +164,77 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test @Test
public void findBySourceTest() throws Exception { public void findBySourceTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
configurationService.setProperty("qaevent.sources", configurationService.setProperty(QAEVENTS_SOURCES, new String[] {
new String[] { QAEvent.OPENAIRE_SOURCE, "test-source", "test-source-2" }); OPENAIRE_SOURCE, "test-source", "test-source-2" });
parentCommunity = CommunityBuilder.createCommunity(context) parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community") .withName("Parent Community")
.build(); .build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom") QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID)
.withSource(QAEvent.OPENAIRE_SOURCE) .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); .build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") QAEventBuilder.createTarget(context, col1, "Science and Freedom 2")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID)
.withSource(QAEvent.OPENAIRE_SOURCE) .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}")
.withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); .build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") QAEventBuilder.createTarget(context, col1, "Science and Freedom 3")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_PID) .withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_PID)
.withSource(QAEvent.OPENAIRE_SOURCE) .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}")
.withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); .build();
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) .withTopic(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT)
.withSource(QAEvent.OPENAIRE_SOURCE) .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
.withMessage( .build();
"{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") QAEventBuilder.createTarget(context, col1, "Science and Freedom 5")
.withTopic("TEST/TOPIC") .withTopic("TEST/TOPIC")
.withSource("test-source") .withSource("test-source")
.build(); .build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 6") QAEventBuilder.createTarget(context, col1, "Science and Freedom 6")
.withTopic("TEST/TOPIC") .withTopic("TEST/TOPIC")
.withSource("test-source") .withSource("test-source")
.build(); .build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom 7") QAEventBuilder.createTarget(context, col1, "Science and Freedom 7")
.withTopic("TEST/TOPIC/2") .withTopic("TEST/TOPIC/2")
.withSource("test-source") .withSource("test-source")
.build(); .build();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
String authToken = getAuthToken(admin.getEmail(), password); String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "openaire")) .param("source", OPENAIRE_SOURCE))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics", .andExpect(jsonPath("$._embedded.qualityassurancetopics",
Matchers.containsInAnyOrder( Matchers.containsInAnyOrder(
QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2), QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2),
QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1), QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1),
QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1)))) QATopicMatcher.matchQATopicEntry(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1)
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); )))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(3)));
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "test-source")) .param("source", "test-source"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics", .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder(
Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("test-source", "TEST/TOPIC/2", 1), QATopicMatcher.matchQATopicEntry("test-source", "TEST/TOPIC/2", 1),
QATopicMatcher.matchQATopicEntry("test-source", "TEST/TOPIC", 2)))) QATopicMatcher.matchQATopicEntry("test-source", "TEST/TOPIC", 2)
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); )))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(2)));
getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "test-source-2")) .param("source", "test-source-2"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.qualityassurancetopics").doesNotExist()) .andExpect(jsonPath("$._embedded.qualityassurancetopics").doesNotExist())
.andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); .andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
} }
@Test @Test
@@ -310,15 +307,19 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest {
public void findBySourceUnauthorizedTest() throws Exception { public void findBySourceUnauthorizedTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context) parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community") .withName("Parent Community")
.build(); .build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
QAEventBuilder.createTarget(context, col1, "Science and Freedom") QAEventBuilder.createTarget(context, col1, "Science and Freedom")
.withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID).build(); .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID)
.build();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource") getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource")
.param("source", "openaire")) .param("source", OPENAIRE_SOURCE))
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test @Test

View File

@@ -13,6 +13,8 @@ import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -53,10 +55,11 @@ public class QAEventMatcher {
public static Matcher<? super Object> matchQAEventEntry(QAEvent event) { public static Matcher<? super Object> matchQAEventEntry(QAEvent event) {
try { try {
ObjectMapper jsonMapper = new JsonMapper(); ObjectMapper jsonMapper = new JsonMapper();
DecimalFormat decimalFormat = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.ENGLISH));
return allOf(hasJsonPath("$.id", is(event.getEventId())), return allOf(hasJsonPath("$.id", is(event.getEventId())),
hasJsonPath("$.originalId", is(event.getOriginalId())), hasJsonPath("$.originalId", is(event.getOriginalId())),
hasJsonPath("$.title", is(event.getTitle())), 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("$.status", Matchers.equalToIgnoringCase(event.getStatus())),
hasJsonPath("$.message", hasJsonPath("$.message",
matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(),

View File

@@ -43,7 +43,6 @@ public class QATopicMatcher {
); );
} }
public static Matcher<? super Object> matchQATopicEntry(String source, String topicName) { public static Matcher<? super Object> matchQATopicEntry(String source, String topicName) {
return allOf( return allOf(
hasJsonPath("$.type", is("qualityassurancetopic")), 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<? super Object> matchQATopicEntry(String source, String topicName, String itemUuid,
int totalEvents) {
return allOf( return allOf(
hasJsonPath("$.type", is("qualityassurancetopic")), hasJsonPath("$.type", is("qualityassurancetopic")),
hasJsonPath("$.name", is(topicName)), hasJsonPath("$.name", is(topicName)),

View File

@@ -19,6 +19,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestExecutionListeners;
@@ -94,6 +95,15 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit
return getClient().getForEntity(getURL(path), String.class); 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<String> responseAsString(RequestEntity request) {
return getClient().exchange(request, String.class);
}
/** /**
* Perform an authenticated (via Basic Auth) GET request and return response as a String * Perform an authenticated (via Basic Auth) GET request and return response as a String
* @param path path to perform GET against * @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. * 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 username Username (may be null to perform an unauthenticated POST)
* @param password Password * @param password Password
* @param requestEntity unknown -- not used. * @param requestEntity HttpEntity to specify content/headers to POST
* @return ResponseEntity with a String body * @return ResponseEntity with a String body
*/ */
public ResponseEntity<String> postResponseAsString(String path, String username, String password, public ResponseEntity<String> postResponseAsString(String path, String username, String password,

View File

@@ -9,19 +9,36 @@
package org.dspace.app.sword2; package org.dspace.app.sword2;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.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.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.dspace.services.ConfigurationService;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired; 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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource; 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. * 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; private ConfigurationService configurationService;
// All SWORD v2 paths that we test against // All SWORD v2 paths that we test against
private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; private final String SWORD_PATH = "/swordv2";
private final String COLLECTION_PATH = "/swordv2/collection"; private final String SERVICE_DOC_PATH = SWORD_PATH + "/servicedocument";
private final String MEDIA_RESOURCE_PATH = "/swordv2/edit-media"; private final String COLLECTION_PATH = SWORD_PATH + "/collection";
private final String CONTAINER_PATH = "/swordv2/edit"; private final String MEDIA_RESOURCE_PATH = SWORD_PATH + "/edit-media";
private final String STATEMENT_PATH = "/swordv2/statement"; 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 @Before
public void onlyRunIfConfigExists() { 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) // 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 // 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.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 @Test
@@ -68,22 +109,20 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest {
// Attempt to GET the ServiceDocument without first authenticating // Attempt to GET the ServiceDocument without first authenticating
ResponseEntity<String> response = getResponseAsString(SERVICE_DOC_PATH); ResponseEntity<String> response = getResponseAsString(SERVICE_DOC_PATH);
// Expect a 401 response code // Expect a 401 response code
assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
} }
@Test @Test
public void serviceDocumentTest() throws Exception { public void serviceDocumentTest() throws Exception {
// Attempt to GET the ServiceDocument as an Admin user. // Attempt to GET the ServiceDocument as any user account
ResponseEntity<String> response = getResponseAsString(SERVICE_DOC_PATH, ResponseEntity<String> response = getResponseAsString(SERVICE_DOC_PATH,
admin.getEmail(), password); eperson.getEmail(), password);
// Expect a 200 response code, and an ATOM UTF-8 document // Expect a 200 response code, and an ATOM service document
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); assertEquals(HttpStatus.OK, response.getStatusCode());
assertThat(response.getHeaders().getContentType().toString(), assertEquals(ATOM_SERVICE_CONTENT_TYPE, response.getHeaders().getContentType().toString());
equalTo("application/atomserv+xml;charset=UTF-8"));
// Check for correct SWORD version in response body // Check for correct SWORD version in response body
assertThat(response.getBody(), assertThat(response.getBody(), containsString("<version xmlns=\"http://purl.org/net/sword/terms/\">2.0</version>"));
containsString("<version xmlns=\"http://purl.org/net/sword/terms/\">2.0</version>"));
} }
@Test @Test
@@ -91,44 +130,204 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest {
// Attempt to POST to /collection endpoint without sending authentication information // Attempt to POST to /collection endpoint without sending authentication information
ResponseEntity<String> response = postResponseAsString(COLLECTION_PATH, null, null, null); ResponseEntity<String> response = postResponseAsString(COLLECTION_PATH, null, null, null);
// Expect a 401 response code // 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 @Test
@Ignore
public void collectionTest() throws Exception { public void collectionTest() throws Exception {
// TODO: Actually test collection endpoint via SWORDv2. context.turnOffAuthorisationSystem();
// Currently, we are just ensuring the /collection endpoint exists (see above) and isn't throwing a 404 // 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<String> 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("<title type=\"text\">" + itemTitle + "</title>"));
} }
@Test @Test
public void mediaResourceUnauthorizedTest() throws Exception { 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<String> response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null); ResponseEntity<String> response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null);
// Expect a 401 response code // 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<Object, Object> 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<String> 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("<link href=\"" + editLink + "\" rel=\"edit\"/>"));
//----
// 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 = "<entry xmlns=\"http://www.w3.org/2005/Atom\"><title>" + newTitle + "</title></entry>";
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 @Test
@Ignore public void editUnauthorizedTest() throws Exception {
public void mediaResourceTest() throws Exception { // Attempt to POST to /edit endpoint without sending authentication information
// TODO: Actually test this endpoint via SWORDv2. ResponseEntity<String> response = postResponseAsString(EDIT_PATH, null, null, null);
// 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<String> response = postResponseAsString(CONTAINER_PATH, null, null, null);
// Expect a 401 response code // Expect a 401 response code
assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
@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
} }
@Test @Test
@@ -136,14 +335,64 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest {
// Attempt to GET /statement endpoint without sending authentication information // Attempt to GET /statement endpoint without sending authentication information
ResponseEntity<String> response = getResponseAsString(STATEMENT_PATH); ResponseEntity<String> response = getResponseAsString(STATEMENT_PATH);
// Expect a 401 response code // 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 @Test
@Ignore
public void statementTest() throws Exception { public void statementTest() throws Exception {
// TODO: Actually test this endpoint via SWORDv2. context.turnOffAuthorisationSystem();
// Currently, we are just ensuring the /statement endpoint exists (see above) and isn't throwing a 404 // 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<String> 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("<title type=\"text\">" + itemTitle + "</title>"));
assertThat(response.getBody(),
containsString("<author><name>" + itemAuthor + "</name></author>"));
// Also verify Item is in "archived" state
assertThat(response.getBody(),
containsString("<category term=\"http://dspace.org/state/archived\""));
} }
} }

View File

@@ -150,11 +150,7 @@ public class CollectionDepositor extends Depositor {
// for a moment // for a moment
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
String bundleName = configurationService.getProperty( String bundleName = configurationService.getProperty("sword-server.bundle.name", "SWORD");
"sword-server", "bundle.name");
if (bundleName == null || "".equals(bundleName)) {
bundleName = "SWORD";
}
Item item = result.getItem(); Item item = result.getItem();
List<Bundle> bundles = item.getBundles(); List<Bundle> bundles = item.getBundles();
Bundle swordBundle = null; Bundle swordBundle = null;

View File

@@ -431,8 +431,7 @@ public class SWORDUrlManager {
*/ */
public String getBaseMediaLinkUrl() public String getBaseMediaLinkUrl()
throws DSpaceSWORDException { throws DSpaceSWORDException {
String mlUrl = configurationService.getProperty( String mlUrl = configurationService.getProperty("sword-server.media-link.url");
"sword-server", "media-link.url");
if (StringUtils.isBlank(mlUrl)) { if (StringUtils.isBlank(mlUrl)) {
if (dspaceUrl == null || "".equals(dspaceUrl)) { if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSWORDException( throw new DSpaceSWORDException(

View File

@@ -458,10 +458,9 @@ public class SwordUrlManager {
throws DSpaceSwordException { throws DSpaceSwordException {
WorkflowTools wft = new WorkflowTools(); 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)) { if (wft.isItemInWorkspace(context, item)) {
String urlTemplate = configurationService String urlTemplate = configurationService.getProperty("swordv2-server.workspace.url-template");
.getProperty("swordv2-server", "workspace.url-template");
if (urlTemplate != null) { if (urlTemplate != null) {
return urlTemplate.replace("#wsid#", Integer.toString( return urlTemplate.replace("#wsid#", Integer.toString(
wft.getWorkspaceItem(context, item).getID())); wft.getWorkspaceItem(context, item).getID()));

View File

@@ -1094,7 +1094,6 @@ webui.preview.brand.fontpoint = 12
# Solr: # Solr:
# ItemCountDAO.class = org.dspace.browse.ItemCountDAOSolr # ItemCountDAO.class = org.dspace.browse.ItemCountDAOSolr
###### Browse Configuration ###### ###### Browse Configuration ######
# #
# Define the DAO class to use this must meet your storage choice for # Define the DAO class to use this must meet your storage choice for

View File

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

View File

@@ -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/ qaevents.openaire.pid-href-prefix.ncid = https://ci.nii.ac.jp/ncid/
# The URI used by the OPENAIRE broker client to import QA events # The URI used by the OPENAIRE broker client to import QA events
qaevents.openaire.broker-url = http://api.openaire.eu/broker 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}

View File

@@ -297,6 +297,7 @@ plugin.named.org.dspace.sword2.SwordContentDisseminator = \
plugin.named.org.dspace.sword2.SwordStatementDisseminator = \ plugin.named.org.dspace.sword2.SwordStatementDisseminator = \
org.dspace.sword2.AtomStatementDisseminator = atom, \ org.dspace.sword2.AtomStatementDisseminator = atom, \
org.dspace.sword2.OreStatementDisseminator = rdf, \ org.dspace.sword2.OreStatementDisseminator = rdf, \
org.dspace.sword2.AtomStatementDisseminator = application/atom+xml, \
org.dspace.sword2.AtomStatementDisseminator = application/atom+xml_type_feed, \ org.dspace.sword2.AtomStatementDisseminator = application/atom+xml_type_feed, \
org.dspace.sword2.OreStatementDisseminator = application/rdf+xml org.dspace.sword2.OreStatementDisseminator = application/rdf+xml

View File

@@ -157,5 +157,7 @@
<!-- Submission Config Service --> <!-- Submission Config Service -->
<bean class="org.dspace.submit.service.SubmissionConfigServiceImpl"/> <bean class="org.dspace.submit.service.SubmissionConfigServiceImpl"/>
<bean class="org.dspace.correctiontype.service.impl.CorrectionTypeServiceImpl"/>
</beans> </beans>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean id="withdrawnRequest" class="org.dspace.correctiontype.WithdrawnCorrectionType" >
<property name="id" value="request-withdrawn"/>
<property name="topic" value="REQUEST/WITHDRAWN"/>
</bean>
<bean id="reinstateRequest" class="org.dspace.correctiontype.ReinstateCorrectionType" >
<property name="id" value="request-reinstate"/>
<property name="topic" value="REQUEST/REINSTATE"/>
</bean>
</beans>

View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- Identifier Service Application Interface. Will be autowired with <!-- Identifier Service Application Interface. Will be autowired with
any Identifier Providers present in Spring context. any Identifier Providers present in Spring context.
--> -->
<bean id="org.dspace.identifier.service.IdentifierService" <bean id="org.dspace.identifier.service.IdentifierService"
@@ -12,6 +12,13 @@
autowire="byType" autowire="byType"
scope="singleton"/> scope="singleton"/>
<!-- If you disable versioning, you need to use the default HandleIdentifierProvider. -->
<!--
<bean id="org.dspace.identifier.HandleIdentifierProvider" class="org.dspace.identifier.HandleIdentifierProvider" scope="singleton">
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
</bean>
-->
<!-- If you enabled versioning, you should use one of the versioned <!-- If you enabled versioning, you should use one of the versioned
handle identifier provider instead of the default one. handle identifier provider instead of the default one.
The VersionedHandleIdentifierProvider creates a new versioned The VersionedHandleIdentifierProvider creates a new versioned
@@ -26,7 +33,7 @@
a new version is created the previous version gets a new a new version is created the previous version gets a new
handle. This leads to a handle that points always to the handle. This leads to a handle that points always to the
newest version, but there is no permanent handle, that newest version, but there is no permanent handle, that
will always keep pointing to the acutal newest one. will always keep pointing to the actual newest one.
--> -->
<!-- <!--
<bean id="org.dspace.identifier.HandleIdentifierProvider" class="org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles" scope="singleton"> <bean id="org.dspace.identifier.HandleIdentifierProvider" class="org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles" scope="singleton">

View File

@@ -21,8 +21,7 @@
<bean id="org.dspace.qaevent.service.QAEventActionService" class="org.dspace.qaevent.service.impl.QAEventActionServiceImpl"> <bean id="org.dspace.qaevent.service.QAEventActionService" class="org.dspace.qaevent.service.impl.QAEventActionServiceImpl">
<property name="topicsToActions"> <property name="topicsToActions">
<map> <map>
<!--The key are the TOPIC, the value must be a valid implementation of the <!--The key are the TOPIC, the value must be a valid implementation of the org.dspace.qaevent.QAEventAction interface -->
org.dspace.qaevent.QAEventAction interface -->
<entry value-ref="ProjectLinkedEntityAction"> <entry value-ref="ProjectLinkedEntityAction">
<key><util:constant static-field="org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT"/></key> <key><util:constant static-field="org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT"/></key>
</entry> </entry>
@@ -47,7 +46,9 @@
<entry value-ref="RelationMetadataAction"> <entry value-ref="RelationMetadataAction">
<key><util:constant static-field="org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MORE_LINK"/></key> <key><util:constant static-field="org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MORE_LINK"/></key>
</entry> </entry>
</map> <entry key="REQUEST/WITHDRAWN" value-ref="WithdrawnRequestAction" />
<entry key="REQUEST/REINSTATE" value-ref="ReinstateRequestAction" />
</map>
</property> </property>
</bean> </bean>
@@ -100,7 +101,7 @@
<entry key="default" value="datacite.relation.isReferencedBy" /> <entry key="default" value="datacite.relation.isReferencedBy" />
<entry key="http://purl.org/vocab/frbr/core#supplement" value="datacite.relation.isSupplementedBy" /> <entry key="http://purl.org/vocab/frbr/core#supplement" value="datacite.relation.isSupplementedBy" />
</map> </map>
</property> </property>
</bean> </bean>
<bean id="org.dspace.qaevent.service.QAEventSecurityService" class="org.dspace.qaevent.service.impl.QAEventSecurityServiceImpl"> <bean id="org.dspace.qaevent.service.QAEventSecurityService" class="org.dspace.qaevent.service.impl.QAEventSecurityServiceImpl">
@@ -111,7 +112,7 @@
<map> <map>
<entry key="coar-notify" value-ref="submitterQASecurity" /> <entry key="coar-notify" value-ref="submitterQASecurity" />
</map> </map>
</property> </property>
</bean> </bean>
<bean id="submitterQASecurity" class="org.dspace.qaevent.security.UserBasedFilterQASecurity"> <bean id="submitterQASecurity" class="org.dspace.qaevent.security.UserBasedFilterQASecurity">
@@ -119,11 +120,11 @@
<!-- we need to escape the { as it as a special meaning for the message format --> <!-- we need to escape the { as it as a special meaning for the message format -->
<!-- argument {0} will be replaced with the uuid of the loggedin user --> <!-- argument {0} will be replaced with the uuid of the loggedin user -->
<value>'{'!join from=search.resourceid to=resource_uuid fromIndex=${solr.multicorePrefix}search}submitter_authority:{0}</value> <value>'{'!join from=search.resourceid to=resource_uuid fromIndex=${solr.multicorePrefix}search}submitter_authority:{0}</value>
</property> </property>
</bean> </bean>
<!-- <!--
To configure rules to automatic process specific qaevent you must provide a qaAutomaticProcessingMap To configure rules to automatic process specific qaevent you must provide a qaAutomaticProcessingMap
where the keys are the qaevent source provider name and the value is a reference to a where the keys are the qaevent source provider name and the value is a reference to a
AutomaticProcessingEvaluation implementation. Below you can find an example of configuration defining AutomaticProcessingEvaluation implementation. Below you can find an example of configuration defining
some thresholds rules for the coar-notify generated QAEvent to be approved, rejected and ignored some thresholds rules for the coar-notify generated QAEvent to be approved, rejected and ignored
--> -->
@@ -141,4 +142,28 @@
<property name="itemFilterToApprove" ref="simple-demo_filter" /> <property name="itemFilterToApprove" ref="simple-demo_filter" />
</bean> </bean>
--> -->
<bean id="WithdrawnRequestAction" class="org.dspace.qaevent.action.QAWithdrawnRequestAction" />
<bean id="ReinstateRequestAction" class="org.dspace.qaevent.action.QAReinstateRequestAction" />
<bean id="org.dspace.qaevent.service.QAEventSecurityService" class="org.dspace.qaevent.service.impl.QAEventSecurityServiceImpl">
<property name="defaultSecurity">
<bean class="org.dspace.qaevent.security.AdministratorsOnlyQASecurity" />
</property>
<property name="qaSecurityConfiguration">
<map>
<entry key="DSpaceUsers" value-ref="submitterWithdrawnReinstateQASecurity" />
</map>
</property>
</bean>
<bean id="submitterWithdrawnReinstateQASecurity" class="org.dspace.qaevent.security.UserBasedFilterQASecurity">
<property name="filterTemplate">
<!-- we need to escape the { as it as a special meaning for the message format -->
<!-- argument {0} will be replaced with the uuid of the loggedin user -->
<value>original_id:{0}</value>
</property>
</bean>
</beans> </beans>