diff --git a/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java b/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java new file mode 100644 index 0000000000..ef0dd116d9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.exception; + +/** + * This class provides an exception to be used when a conflict on a resource + * occurs. + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + * + */ +public class ResourceConflictException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final Object resource; + + /** + * Create a ResourceConflictException with a message and the conflicting + * resource. + * + * @param message the error message + * @param resource the resource that caused the conflict + */ + public ResourceConflictException(String message, Object resource) { + super(message); + this.resource = resource; + } + + public Object getResource() { + return resource; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java new file mode 100644 index 0000000000..2ed19954e6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java @@ -0,0 +1,72 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.content.Item; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.discovery.indexobject.IndexableInProgressSubmission; +import org.dspace.discovery.indexobject.IndexableWorkflowItem; +import org.dspace.discovery.indexobject.IndexableWorkspaceItem; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; +import org.dspace.workflow.WorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SolrServiceSupervisionOrderIndexingPlugin implements SolrServiceIndexPlugin { + + @Autowired(required = true) + private WorkspaceItemService workspaceItemService; + + @Autowired(required = true) + private WorkflowItemService workflowItemService; + + @Autowired(required = true) + private SupervisionOrderService supervisionOrderService; + + @Override + public void additionalIndex(Context context, IndexableObject indexableObject, SolrInputDocument document) { + try { + + if (!(indexableObject instanceof IndexableWorkspaceItem) && + !(indexableObject instanceof IndexableWorkflowItem)) { + return; + } + + Item item = + (((IndexableInProgressSubmission) indexableObject).getIndexedObject()).getItem(); + + if (Objects.isNull(item)) { + return; + } + addSupervisedField(context, item, document); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private void addSupervisedField(Context context, Item item, SolrInputDocument document) throws SQLException { + List supervisionOrders = supervisionOrderService.findByItem(context, item); + if (CollectionUtils.isNotEmpty(supervisionOrders)) { + document.addField("supervised", true); + } else { + document.addField("supervised", false); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java index fd05be1cb5..1618494756 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java @@ -40,6 +40,11 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic */ public static final String DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME = "workflowAdmin"; + /** + * The name of the discover configuration used by administrators to search for workspace and workflow tasks + */ + public static final String DISCOVER_SUPERVISION_CONFIGURATION_NAME = "supervision"; + @Autowired(required = true) protected GroupService groupService; @@ -60,18 +65,22 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic ); boolean isWorkflowAdmin = isAdmin(context) && DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + + boolean isSupervision = + DISCOVER_SUPERVISION_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + EPerson currentUser = context.getCurrentUser(); // extra security check to avoid the possibility that an anonymous user // get access to workspace or workflow - if (currentUser == null && (isWorkflow || isWorkspace)) { + if (currentUser == null && (isWorkflow || isWorkspace || isSupervision)) { throw new IllegalStateException( "An anonymous user cannot perform a workspace or workflow search"); } if (isWorkspace) { // insert filter by submitter solrQuery.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")"); - } else if (isWorkflow && !isWorkflowAdmin) { + } else if ((isWorkflow && !isWorkflowAdmin) || (isSupervision && !isAdmin(context))) { // Retrieve all the groups the current user is a member of ! Set groups; try { diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java index d0b0f363e6..8a24b997ff 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java @@ -22,6 +22,8 @@ import org.dspace.discovery.indexobject.factory.CollectionIndexFactory; import org.dspace.discovery.indexobject.factory.InprogressSubmissionIndexFactory; import org.dspace.discovery.indexobject.factory.ItemIndexFactory; import org.dspace.eperson.EPerson; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.util.SolrUtils; import org.dspace.workflow.WorkflowItem; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +41,9 @@ public abstract class InprogressSubmissionIndexFactoryImpl @Autowired protected ItemIndexFactory indexableItemService; + @Autowired + protected SupervisionOrderService supervisionOrderService; + @Override public SolrInputDocument buildDocument(Context context, T indexableObject) throws SQLException, IOException { @@ -60,6 +65,8 @@ public abstract class InprogressSubmissionIndexFactoryImpl submitter.getFullName()); } + addSupervisedByFacetIndex(context, item, doc); + doc.addField("inprogress.item", new IndexableItem(inProgressSubmission.getItem()).getUniqueIndexID()); // get the location string (for searching by collection & community) @@ -82,4 +89,13 @@ public abstract class InprogressSubmissionIndexFactoryImpl indexableItemService.addDiscoveryFields(doc, context, item, discoveryConfigurations); indexableCollectionService.storeCommunityCollectionLocations(doc, locations); } + + private void addSupervisedByFacetIndex(Context context, Item item, SolrInputDocument doc) throws SQLException { + List supervisionOrders = supervisionOrderService.findByItem(context, item); + for (SupervisionOrder supervisionOrder : supervisionOrders) { + addFacetIndex(doc, "supervisedBy", supervisionOrder.getGroup().getID().toString(), + supervisionOrder.getGroup().getName()); + } + + } } diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java new file mode 100644 index 0000000000..52d5dacb74 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java @@ -0,0 +1,78 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.ReloadableEntity; +import org.dspace.eperson.Group; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Database entity representation of the supervision_orders table + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Entity +@Table(name = "supervision_orders") +public class SupervisionOrder implements ReloadableEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "supervision_orders_seq") + @SequenceGenerator(name = "supervision_orders_seq", sequenceName = "supervision_orders_seq", allocationSize = 1) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "eperson_group_id") + private Group group; + + /** + * Protected constructor, create object using: + * {@link SupervisionOrderService#create(Context, Item, Group)} + */ + protected SupervisionOrder() { + + } + + @Override + public Integer getID() { + return id; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java new file mode 100644 index 0000000000..21a54f085f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java @@ -0,0 +1,126 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.event.Event; +import org.dspace.supervision.dao.SupervisionOrderDao; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link SupervisionOrderService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceImpl implements SupervisionOrderService { + + @Autowired(required = true) + private SupervisionOrderDao supervisionDao; + + @Autowired(required = true) + private GroupService groupService; + + @Autowired(required = true) + private ItemService itemService; + + protected SupervisionOrderServiceImpl() { + + } + + @Override + public SupervisionOrder create(Context context) throws SQLException, AuthorizeException { + return supervisionDao.create(context, new SupervisionOrder()); + } + + @Override + public SupervisionOrder find(Context context, int id) throws SQLException { + return supervisionDao.findByID(context, SupervisionOrder.class, id); + } + + @Override + public void update(Context context, SupervisionOrder supervisionOrder) + throws SQLException, AuthorizeException { + supervisionDao.save(context, supervisionOrder); + } + + @Override + public void update(Context context, List supervisionOrders) + throws SQLException, AuthorizeException { + if (CollectionUtils.isNotEmpty(supervisionOrders)) { + for (SupervisionOrder supervisionOrder : supervisionOrders) { + supervisionDao.save(context, supervisionOrder); + } + } + } + + @Override + public void delete(Context context, SupervisionOrder supervisionOrder) throws SQLException, AuthorizeException { + supervisionDao.delete(context, supervisionOrder); + } + + @Override + public SupervisionOrder create(Context context, Item item, Group group) throws SQLException { + SupervisionOrder supervisionOrder = new SupervisionOrder(); + supervisionOrder.setItem(item); + supervisionOrder.setGroup(group); + SupervisionOrder supOrder = supervisionDao.create(context, supervisionOrder); + context.addEvent(new Event(Event.MODIFY, Constants.ITEM, item.getID(), null, + itemService.getIdentifiers(context, item))); + return supOrder; + } + + @Override + public List findAll(Context context) throws SQLException { + return supervisionDao.findAll(context, SupervisionOrder.class); + } + + @Override + public List findByItem(Context context, Item item) throws SQLException { + return supervisionDao.findByItem(context, item); + } + + @Override + public SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException { + return supervisionDao.findByItemAndGroup(context, item, group); + } + + @Override + public boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException { + List supervisionOrders = findByItem(context, item); + + if (CollectionUtils.isEmpty(supervisionOrders)) { + return false; + } + + return supervisionOrders + .stream() + .map(SupervisionOrder::getGroup) + .anyMatch(group -> isMember(context, ePerson, group)); + } + + private boolean isMember(Context context, EPerson ePerson, Group group) { + try { + return groupService.isMember(context, ePerson, group); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java new file mode 100644 index 0000000000..185e9d72a6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java @@ -0,0 +1,32 @@ +/** + * 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.supervision.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; + +/** + * Database Access Object interface class for the SupervisionOrder object. + * + * The implementation of this class is responsible for all database calls for the SupervisionOrder object + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface SupervisionOrderDao extends GenericDAO { + + List findByItem(Context context, Item item) throws SQLException; + SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java new file mode 100644 index 0000000000..09cd0841e7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java @@ -0,0 +1,59 @@ +/** + * 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.supervision.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.content.Item; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.SupervisionOrder_; +import org.dspace.supervision.dao.SupervisionOrderDao; + +/** + * Hibernate implementation of the Database Access Object interface class for the SupervisionOrder object. + * This class is responsible for all database calls for the SupervisionOrder object + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderDaoImpl extends AbstractHibernateDAO implements SupervisionOrderDao { + + @Override + public List findByItem(Context context, Item item) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SupervisionOrder.class); + + Root supervisionOrderRoot = criteriaQuery.from(SupervisionOrder.class); + criteriaQuery.select(supervisionOrderRoot); + criteriaQuery.where(criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.item), item)); + + return list(context, criteriaQuery, false, SupervisionOrder.class, -1, -1); + } + + @Override + public SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SupervisionOrder.class); + + Root supervisionOrderRoot = criteriaQuery.from(SupervisionOrder.class); + criteriaQuery.select(supervisionOrderRoot); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.item), item), + criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.group), group) + )); + + return singleResult(context, criteriaQuery); + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java new file mode 100644 index 0000000000..698e9d234e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java @@ -0,0 +1,17 @@ +/** + * 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.supervision.enumeration; + +/** + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public enum SupervisionOrderType { + OBSERVER, + EDITOR +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java new file mode 100644 index 0000000000..8577ee8b16 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.factory; + +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Abstract factory to get services for the supervision package, + * use SupervisionOrderServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public abstract class SupervisionOrderServiceFactory { + + public abstract SupervisionOrderService getSupervisionOrderService(); + + public static SupervisionOrderServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName("supervisionOrderServiceFactory", + SupervisionOrderServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java new file mode 100644 index 0000000000..407a79c689 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java @@ -0,0 +1,28 @@ +/** + * 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.supervision.factory; + +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation to get services for the supervision package, + * use SupervisionOrderServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceFactoryImpl extends SupervisionOrderServiceFactory { + + @Autowired(required = true) + private SupervisionOrderService supervisionOrderService; + + @Override + public SupervisionOrderService getSupervisionOrderService() { + return supervisionOrderService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java new file mode 100644 index 0000000000..f99090d6e6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java @@ -0,0 +1,32 @@ +/** + * 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.supervision.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.service.DSpaceCRUDService; +import org.dspace.supervision.SupervisionOrder; + +/** + * Service interface class for the SupervisionOrder object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface SupervisionOrderService extends DSpaceCRUDService { + + SupervisionOrder create(Context context, Item item, Group group) throws SQLException; + List findAll(Context context) throws SQLException; + List findByItem(Context context, Item item) throws SQLException; + SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException; +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 0000000000..33d3eb5c82 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -0,0 +1,20 @@ +-- +-- 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/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 0000000000..33d3eb5c82 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -0,0 +1,20 @@ +-- +-- 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/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 0000000000..33d3eb5c82 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -0,0 +1,20 @@ +-- +-- 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/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index 3306ced8f4..df3310cd24 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -49,6 +49,8 @@ import org.dspace.orcid.service.OrcidTokenService; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.factory.SupervisionOrderServiceFactory; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; @@ -102,6 +104,8 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; + static SupervisionOrderService supervisionOrderService; + protected Context context; @@ -161,6 +165,7 @@ public abstract class AbstractBuilder { orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); + supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); } @@ -194,6 +199,7 @@ public abstract class AbstractBuilder { requestItemService = null; versioningService = null; orcidTokenService = null; + supervisionOrderService = null; } diff --git a/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java new file mode 100644 index 0000000000..849e4cd4ff --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.builder; + +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Abstract builder to construct SupervisionOrder Objects + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderBuilder + extends AbstractBuilder { + + private static final Logger log = LogManager.getLogger(SupervisionOrderBuilder.class); + + private SupervisionOrder supervisionOrder; + + protected SupervisionOrderBuilder(Context context) { + super(context); + } + + public static SupervisionOrderBuilder createSupervisionOrder(Context context, Item item, Group group) { + SupervisionOrderBuilder builder = new SupervisionOrderBuilder(context); + return builder.create(context, item, group); + } + + private SupervisionOrderBuilder create(Context context, Item item, Group group) { + try { + this.context = context; + this.supervisionOrder = getService().create(context, item, group); + } catch (Exception e) { + log.error("Error in SupervisionOrderBuilder.create(..), error: ", e); + } + return this; + } + + @Override + public void cleanup() throws Exception { + delete(supervisionOrder); + } + + @Override + public SupervisionOrder build() throws SQLException, AuthorizeException { + try { + getService().update(context, supervisionOrder); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + log.error("Error in SupervisionOrderBuilder.build(), error: ", e); + } + return supervisionOrder; + } + + @Override + public void delete(Context context, SupervisionOrder supervisionOrder) throws Exception { + if (Objects.nonNull(supervisionOrder)) { + getService().delete(context, supervisionOrder); + } + } + + @Override + protected SupervisionOrderService getService() { + return supervisionOrderService; + } + + private void delete(SupervisionOrder supervisionOrder) throws Exception { + try (Context context = new Context()) { + context.turnOffAuthorisationSystem(); + context.setDispatcher("noindex"); + SupervisionOrder attached = context.reloadEntity(supervisionOrder); + if (attached != null) { + getService().delete(context, attached); + } + context.complete(); + indexingService.commit(); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java new file mode 100644 index 0000000000..e9ffb22446 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import java.util.Objects; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +/** + * This class is responsible to convert SupervisionOrder to its rest model + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component +public class SupervisionOrderConverter + implements DSpaceConverter { + + @Lazy + @Autowired + private ConverterService converter; + + @Override + public SupervisionOrderRest convert(SupervisionOrder modelObject, Projection projection) { + + SupervisionOrderRest rest = new SupervisionOrderRest(); + Item item = modelObject.getItem(); + Group group = modelObject.getGroup(); + + rest.setId(modelObject.getID()); + + if (Objects.nonNull(item)) { + rest.setItem(converter.toRest(item, projection)); + } + + if (Objects.nonNull(group)) { + rest.setGroup(converter.toRest(group, projection)); + } + + rest.setProjection(projection); + + return rest; + } + + @Override + public Class getModelClass() { + return SupervisionOrder.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index 1cbfd5c632..8ac1e7fe06 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -22,13 +22,19 @@ import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.exception.ResourceAlreadyExistsException; +import org.dspace.app.exception.ResourceConflictException; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.InvalidReCaptchaException; import org.dspace.orcid.exception.OrcidValidationException; import org.dspace.services.ConfigurationService; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.support.QueryMethodParameterConversionException; import org.springframework.http.HttpHeaders; @@ -60,6 +66,13 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { private static final Logger log = LogManager.getLogger(); + @Autowired + @Lazy + private ConverterService converterService; + + @Autowired + private Utils utils; + /** * Default collection of HTTP error codes to log as ERROR with full stack trace. */ @@ -235,6 +248,12 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH } + @ExceptionHandler(ResourceConflictException.class) + protected ResponseEntity resourceConflictException(ResourceConflictException ex) { + RestModel resource = converterService.toRest(ex.getResource(), utils.obtainProjection()); + return new ResponseEntity(resource, HttpStatus.CONFLICT); + } + /** * Send the error to the response. * 5xx errors will be logged as ERROR with a full stack trace. 4xx errors diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java new file mode 100644 index 0000000000..e114fdeb39 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java @@ -0,0 +1,72 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; +import org.dspace.supervision.SupervisionOrder; + +/** + * The REST Resource of {@link SupervisionOrder}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class SupervisionOrderRest extends BaseObjectRest { + + public static final String NAME = "supervisionorder"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private Integer id; + + @JsonIgnore + private ItemRest item; + + @JsonIgnore + private GroupRest group; + + @Override + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } + + public ItemRest getItem() { + return item; + } + + public void setItem(ItemRest item) { + this.item = item; + } + + public GroupRest getGroup() { + return group; + } + + public void setGroup(GroupRest group) { + this.group = group; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 69fefd1a9b..57a5ab5c7f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -14,10 +14,18 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = WorkspaceItemRest.SUPERVISION_ORDERS, + method = "getSupervisionOrders" + ) +}) public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String NAME = "workspaceitem"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; + public static final String SUPERVISION_ORDERS = "supervisionOrders"; + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java new file mode 100644 index 0000000000..06439f29ef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java @@ -0,0 +1,25 @@ +/** + * 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.SupervisionOrderRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * SupervisionOrder 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 dot it) + */ +@RelNameDSpaceResource(SupervisionOrderRest.NAME) +public class SupervisionOrderResource extends DSpaceResource { + public SupervisionOrderResource(SupervisionOrderRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java new file mode 100644 index 0000000000..7d7ede8189 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -0,0 +1,219 @@ +/** + * 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 static org.dspace.authorize.ResourcePolicy.TYPE_SUBMISSION; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.exception.ResourceConflictException; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.MissingParameterException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.enumeration.SupervisionOrderType; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage SupervisionOrderRest object + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME) +public class SupervisionOrderRestRepository extends DSpaceRestRepository { + + private static final Logger log = + org.apache.logging.log4j.LogManager.getLogger(SupervisionOrderRestRepository.class); + + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Autowired + private ConverterService converterService; + + @Autowired + private ItemService itemService; + + @Autowired + private GroupService groupService; + + @Autowired + private AuthorizeService authorizeService; + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public SupervisionOrderRest findOne(Context context, Integer id) { + try { + SupervisionOrder supervisionOrder = supervisionOrderService.find(context, id); + if (Objects.isNull(supervisionOrder)) { + throw new ResourceNotFoundException("Couldn't find supervision order for id: " + id); + } + return converterService.toRest(supervisionOrder, utils.obtainProjection()); + } catch (SQLException e) { + log.error("Something went wrong with getting supervision order with id:" + id, e); + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public Page findAll(Context context, Pageable pageable) { + try { + List supervisionOrders = supervisionOrderService.findAll(context); + return converterService.toRestPage(supervisionOrders, pageable, utils.obtainProjection()); + } catch (SQLException e) { + log.error("Something went wrong with getting supervision orders", e); + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public SupervisionOrderRest createAndReturn(Context context) throws AuthorizeException, SQLException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + SupervisionOrder supervisionOrder; + String itemId = req.getParameter("uuid"); + String groupId = req.getParameter("group"); + String type = req.getParameter("type"); + + validateParameters(itemId, groupId, type); + + Item item = itemService.find(context, UUID.fromString(itemId)); + if (item == null) { + throw new UnprocessableEntityException("Item with uuid: " + itemId + " not found"); + } + + Group group = groupService.find(context, UUID.fromString(groupId)); + if (group == null) { + throw new UnprocessableEntityException("Group with uuid: " + groupId + " not found"); + } + + supervisionOrder = supervisionOrderService.findByItemAndGroup(context, item, group); + if (Objects.nonNull(supervisionOrder)) { + throw new ResourceConflictException( + "There is a conflict supervision order with itemId <" + itemId + "> and groupId <" + groupId + ">", + supervisionOrder + ); + } + supervisionOrder = supervisionOrderService.create(context, item, group); + addGroupPoliciesToItem(context, item, group, type); + return converterService.toRest(supervisionOrder, utils.obtainProjection()); + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + protected void delete(Context context, Integer id) + throws AuthorizeException, RepositoryMethodNotImplementedException { + + try { + SupervisionOrder supervisionOrder = supervisionOrderService.find(context, id); + if (Objects.isNull(supervisionOrder)) { + throw new ResourceNotFoundException( + SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME + + " with id: " + id + " not found" + ); + } + supervisionOrderService.delete(context, supervisionOrder); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @SearchRestMethod(name = "byItem") + public Page findByItem(@Parameter(value = "uuid", required = true) String itemId, + Pageable pageable) { + try { + Context context = obtainContext(); + Item item = itemService.find(context, UUID.fromString(itemId)); + if (Objects.isNull(item)) { + throw new ResourceNotFoundException("no item is found for the uuid < " + itemId + " >"); + } + return converterService.toRestPage(supervisionOrderService.findByItem(context, item), + pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Class getDomainClass() { + return SupervisionOrderRest.class; + } + + private void validateParameters(String itemId, String groupId, String type) { + if (Objects.isNull(itemId)) { + throw new MissingParameterException("Missing item (uuid) parameter"); + } + + if (Objects.isNull(groupId)) { + throw new MissingParameterException("Missing group (uuid) parameter"); + } + + if (Objects.isNull(type)) { + throw new MissingParameterException("Missing type parameter"); + } else if (!type.equals(SupervisionOrderType.EDITOR.toString()) && + !type.equals(SupervisionOrderType.OBSERVER.toString())) { + throw new IllegalArgumentException("wrong type value, Type must be (EDITOR or OBSERVER)"); + } + + } + + private void addGroupPoliciesToItem(Context context, Item item, Group group, String type) + throws SQLException, AuthorizeException { + + if (StringUtils.isNotEmpty(type)) { + if (type.equals("EDITOR")) { + addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); + addGroupPolicyToItem(context, item, Constants.WRITE, group, TYPE_SUBMISSION); + } else if (type.equals("OBSERVER")) { + addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); + } + } + } + + private void addGroupPolicyToItem(Context context, Item item, int action, Group group, String policyType) + throws AuthorizeException, SQLException { + authorizeService.addPolicy(context, item, action, group, policyType); + List bundles = item.getBundles(); + for (Bundle bundle : bundles) { + authorizeService.addPolicy(context, bundle, action, group, policyType); + List bits = bundle.getBitstreams(); + for (Bitstream bitstream : bits) { + authorizeService.addPolicy(context, bitstream, action, group, policyType); + } + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java new file mode 100644 index 0000000000..e0d57ae0de --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for the supervision orders of an WorkspaceItem + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.SUPERVISION_ORDERS) +public class WorkspaceItemSupervisionOrdersLinkRepository + extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + WorkspaceItemService workspaceItemService; + + @Autowired + SupervisionOrderService supervisionOrderService; + + @PreAuthorize("hasAuthority('ADMIN')") + public Page getSupervisionOrders(@Nullable HttpServletRequest request, + Integer id, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem workspaceItem = workspaceItemService.find(context, id); + if (workspaceItem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + return converter.toRestPage( + supervisionOrderService.findByItem(context, workspaceItem.getItem()), + optionalPageable, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java index e225df067d..626290fdc3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java @@ -20,6 +20,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; @@ -56,6 +57,9 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE @Autowired private EPersonService ePersonService; + @Autowired + private SupervisionOrderService supervisionOrderService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -89,6 +93,11 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE if (claimedTaskService.findByWorkflowIdAndEPerson(context, workflowItem, ePerson) != null) { return true; } + + if (supervisionOrderService.isSupervisor(context, ePerson, workflowItem.getItem())) { + return true; + } + } catch (SQLException | AuthorizeException | IOException e) { log.error(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index 9a8de3675c..1b1a604b20 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -19,6 +19,7 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.dspace.services.model.Request; +import org.dspace.supervision.service.SupervisionOrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +42,9 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis @Autowired WorkspaceItemService wis; + @Autowired + private SupervisionOrderService supervisionOrderService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -82,6 +86,13 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis return true; } } + + if (witem.getItem() != null) { + if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { + return true; + } + } + } catch (SQLException e) { log.error(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index 237550326f..88278b531c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -64,6 +64,7 @@ import org.dspace.app.rest.model.PropertyRest; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.SupervisionOrderRest; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.EmbeddedPage; @@ -296,6 +297,9 @@ public class Utils { if (StringUtils.equals(modelPlural, "orcidhistories")) { return OrcidHistoryRest.NAME; } + if (StringUtils.equals(modelPlural, "supervisionorders")) { + return SupervisionOrderRest.NAME; + } return modelPlural.replaceAll("s$", ""); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index b604bcd958..740c9739b8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -9,11 +9,13 @@ package org.dspace.app.rest; import static com.google.common.net.UrlEscapers.urlPathSegmentEscaper; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.FacetValueMatcher.entrySupervisedBy; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -48,6 +50,7 @@ import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.PoolTaskBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; @@ -63,6 +66,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.SupervisionOrder; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; @@ -5987,4 +5991,491 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); } + + @Test + public void discoverFacetsSupervisedByTest() throws Exception { + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + //2. Two workspace items + WorkspaceItem wsItem1 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + //3. Two groups + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + //4. Four supervision orders + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupB).build(); + + SupervisionOrder supervisionOrderThree = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderFour = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupB).build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //The Admin user browses this endpoint to find the supervisedBy results by the facet + getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") + .param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //The name has to be 'supervisedBy' as that's the facet that we've called + .andExpect(jsonPath("$.name", is("supervisedBy"))) + //The facetType has to be `authority` because that's the default configuration for this facet + .andExpect(jsonPath("$.facetType", equalTo("authority"))) + //There always needs to be a self link available + .andExpect(jsonPath("$._links.self.href", + containsString("api/discover/facets/supervisedBy?configuration=supervision"))) + //This is how the page object must look like because it's the default with size 20 + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 20)))) + //The supervisedBy values need to be as specified below + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 2), + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))); + } + + @Test + public void discoverFacetsSupervisedByWithPrefixTest() throws Exception { + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + //2. Two workspace items + WorkspaceItem wsItem1 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOneA = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderOneB = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupB).build(); + + SupervisionOrder supervisionOrderTwoA = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderTwoB = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupB).build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //The Admin user browses this endpoint to find the supervisedBy results by the facet + getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") + .param("configuration", "supervision") + .param("prefix", "group B")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //The name has to be 'supervisedBy' as that's the facet that we've called + .andExpect(jsonPath("$.name", is("supervisedBy"))) + //The facetType has to be `authority` because that's the default configuration for this facet + .andExpect(jsonPath("$.facetType", equalTo("authority"))) + //There always needs to be a self link available + .andExpect(jsonPath("$._links.self.href", + containsString("api/discover/facets/supervisedBy?prefix=group%2520B&configuration=supervision"))) + //This is how the page object must look like because it's the default with size 20 + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 20)))) + //The supervisedBy values need to be as specified below + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))); + } + + @Test + /** + * This test is intent to verify that tasks are only visible to the admin users + * + * @throws Exception + */ + public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception { + + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. Two reviewers and two users and two groups + EPerson reviewer1 = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + EPerson reviewer2 = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + // 2. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + + // the second collection has two workflow steps active + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withWorkflowGroup(1, admin, reviewer1) + .withWorkflowGroup(2, reviewer2) + .build(); + + // 2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withAuthor("Testing, Works") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test 2") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withAuthor("Testing, Works") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withAuthor("test,test") + .withAuthor("test2, test2") + .withAuthor("Maybe, Maybe") + .withSubject("AnotherTest") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + context.setCurrentUser(eperson); + WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + XmlWorkflowItem wfItem1 = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withTitle("Workflow Item 1") + .withIssueDate("2010-11-03") + .build(); + + // create Four supervision orders for above items + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1.getItem(), groupA).build(); + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1.getItem(), groupB).build(); + + // 4. a claimed task from the administrator + ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") + .withIssueDate("2010-11-03") + .build(); + + // 5. other inprogress submissions made by the administrator + context.setCurrentUser(admin); + WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withIssueDate("2010-07-23") + .withTitle("Admin Workspace Item 1").build(); + + WorkspaceItem wsItem2Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workspace Item 2").build(); + + XmlWorkflowItem wfItem1Admin = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workflow Item 1").build(); + + // create Four supervision orders for above items + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1Admin.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2Admin.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1Admin.getItem(), groupA).build(); + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1Admin.getItem(), groupB).build(); + + // 6. a pool taks in the second step of the workflow + ClaimedTask cTask2 = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Pool Step2 Item") + .withIssueDate("2010-11-04") + .build(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + + getClient(adminToken).perform(post("/api/workflow/claimedtasks/" + cTask2.getID()) + .param("submit_approve", "true") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + context.restoreAuthSystemState(); + + // summary of the structure, we have: + // a simple collection + // a second collection with 2 workflow steps that have 1 reviewer each (reviewer1 and reviewer2) + // 3 public items + // 2 workspace items submitted by a regular submitter + // 2 workspace items submitted by the admin + // 4 workflow items: + // 1 pool task in step 1, submitted by the same regular submitter + // 1 pool task in step 1, submitted by the admin + // 1 claimed task in the first workflow step from the repository admin + // 1 pool task task in step 2, from the repository admin + // (This one is created by creating a claimed task for step 1 and approving it) + + //** WHEN ** + // the submitter should not see anything in the workflow configuration + getClient(epersonToken) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + // reviewer1 should not see pool items, as it is not an administrator + getClient(reviewer1Token) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", + containsString("/api/discover/search/objects"))); + + // admin should see seven pool items and a claimed task + // Three pool items from the submitter and Five from the admin + getClient(adminToken) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 8) + ))) + // These search results have to be shown in the embedded.objects section: + // three workflow items and one claimed task. + // For step 1 one submitted by the user and one submitted by the admin and one for step 2. + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Admin Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Pool Step2 Item", "2010-11-04"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Claimed Item", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1, + "Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem2, + "Workspace Item 2", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1Admin, + "Admin Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem2Admin, + "Admin Workspace Item 2", "2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + //property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.submitterFacet(false), + FacetEntryMatcher.supervisedByFacet(false) + ))) + //check supervisedBy Facet values + .andExpect(jsonPath("$._embedded.facets[4]._embedded.values", + contains( + entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 6), + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + // reviewer2 should not see pool items, as it is not an administrator + getClient(reviewer2Token) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java new file mode 100644 index 0000000000..7bd2553c9b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -0,0 +1,932 @@ +/** + * 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.JsonPath.read; +import static org.dspace.app.rest.matcher.ItemMatcher.matchItemWithTitleAndDateIssued; +import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.repository.SupervisionOrderRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SupervisionOrderBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test against class {@link SupervisionOrderRestRepository}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class SupervisionOrderRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Test + public void findAllByAnonymousUserTest() throws Exception { + getClient().perform(get("/api/core/supervisionorders/")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findAllByNotAdminUserTest() throws Exception { + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/")) + .andExpect(status().isForbidden()); + } + + @Test + public void findAllByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupB) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionorders", + containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString("/api/core/supervisionorders"))); + } + + @Test + public void findOneByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", matchSuperVisionOrder(supervisionOrder))); + } + + @Test + public void findOneByAdminAndItemIsWithdrawnTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); + + String adminToken = getAuthToken(admin.getEmail(), password); + // withdraw item + getClient(adminToken).perform(patch("/api/core/items/" + item.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) + .andExpect(jsonPath("$.withdrawn", Matchers.is(true))) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", matchSuperVisionOrder(context.reloadEntity(supervisionOrder)))); + + } + + @Test + public void findOneByAdminButNotFoundTest() throws Exception { + int fakeId = 12354326; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/supervisionorders/" + fakeId)) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, item, group).build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByItemByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, item, group).build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByItemByAdminButNotFoundItemTest() throws Exception { + String fakeItemId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", fakeItemId)) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, itemOne, groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, itemOne, groupB) + .build(); + + Item itemTwo = + ItemBuilder.createItem(context, col1) + .withTitle("item two title") + .build(); + + SupervisionOrder supervisionOrderItemTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, itemTwo, groupA) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", itemOne.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionorders", containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/core/supervisionorders/search/byItem?uuid=" + itemOne.getID())) + ); + + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", itemTwo.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.supervisionorders", contains( + matchSuperVisionOrder(supervisionOrderItemTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/core/supervisionorders/search/byItem?uuid=" + itemTwo.getID())) + ); + } + + @Test + public void createByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void createByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isForbidden()); + } + + @Test + public void createByAdminButMissingParametersTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createByAdminButIncorrectTypeParameterTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "WRONG") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createByAdminButNotFoundItemOrGroupTest() throws Exception { + + String fakeItemId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + String fakeGroupId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", fakeItemId) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", fakeGroupId) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createTheSameSupervisionOrderTwiceTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .withIssueDate("2017-10-17") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isConflict()); + } + + @Test + public void createByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test2@email.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .withIssueDate("2017-10-17") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + context.restoreAuthSystemState(); + + AtomicInteger supervisionOrderIdOne = new AtomicInteger(); + AtomicInteger supervisionOrderIdTwo = new AtomicInteger(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdOne + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdTwo + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.find(context, supervisionOrderIdOne.get()); + + SupervisionOrder supervisionOrderTwo = + supervisionOrderService.find(context, supervisionOrderIdTwo.get()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderOne.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderOne)))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderTwo.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderTwo)))); + + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + + String patchBody = getPatchContent(List.of( + new AddOperation("/metadata/dc.title", List.of(Map.of("value", "new title"))) + )); + + // update title of itemOne by userA is Ok + getClient(authTokenA).perform(patch("/api/core/items/" + itemOne.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is( + matchItemWithTitleAndDateIssued(context.reloadEntity(itemOne), + "new title", "2017-10-17") + ))); + + // update title of itemOne by userB is Forbidden + getClient(authTokenB).perform(patch("/api/core/items/" + itemOne.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteByAdminButNotFoundTest() throws Exception { + int fakeId = 12354326; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(delete("/api/core/supervisionorders/" + fakeId)) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 025a5f15a9..0bf9cd5297 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -46,6 +46,7 @@ import org.dspace.builder.ClaimedTaskBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; @@ -55,6 +56,7 @@ import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Step; @@ -2123,4 +2125,71 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT } } + @Test + public void testSupervisorFindOne() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson user = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("user@test.com") + .withPassword(password) + .build(); + + EPerson anotherUser = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("anotheruser@test.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = + CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withWorkflowGroup(1, admin) + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(user) + .build(); + + XmlWorkflowItem workflowItem = + WorkflowItemBuilder.createWorkflowItem(context, collection) + .withSubmitter(admin) + .withTitle("Workflow Item") + .withIssueDate("2017-10-17") + .withAuthor("Author one") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", workflowItem.getItem().getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(getAuthToken(anotherUser.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isForbidden()); + + getClient(getAuthToken(user.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isOk()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 4a9f03ffff..6c97526425 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -11,9 +11,12 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; +import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -70,6 +73,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -89,6 +93,7 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; +import org.dspace.supervision.SupervisionOrder; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -8291,4 +8296,274 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration assertTrue(date.equals(date2)); } + @Test + public void supervisionOrdersLinksTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2022-12-12") + .withAuthor("Eskander, Mohamed") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + SupervisionOrder supervisionOrderOne = SupervisionOrderBuilder + .createSupervisionOrder(context, witem.getItem(), group) + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", "2022-12-12", "ExtraEntry")))) + .andExpect(jsonPath("$._links.supervisionOrders.href", containsString( + "/api/submission/workspaceitems/" + witem.getID() + "/supervisionOrders") + )); + } + + @Test + public void supervisionOrdersEndpointTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem witemOne = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test item -- supervision orders") + .withIssueDate("2022-12-12") + .withAuthor("Eskander, Mohamed") + .grantLicense() + .build(); + + WorkspaceItem witemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test item -- no supervision orders") + .withIssueDate("2022-12-12") + .withAuthor("Eskander") + .grantLicense() + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = SupervisionOrderBuilder + .createSupervisionOrder(context, witemOne.getItem(), groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = SupervisionOrderBuilder + .createSupervisionOrder(context, witemOne.getItem(), groupB) + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authToken = getAuthToken(eperson.getEmail(), password); + + // Item's supervision orders endpoint of itemOne by not admin + getClient(authToken) + .perform(get("/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders")) + .andExpect(status().isForbidden()); + + // Item's supervision orders endpoint of itemOne by admin + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionOrders", containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders") + )); + + // Item's supervision orders endpoint of itemTwo by admin + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witemTwo.getID() + "/supervisionOrders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded.supervisionOrders", empty())) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/submission/workspaceitems/" + witemTwo.getID() + "/supervisionOrders") + )); + } + + @Test + public void patchBySupervisorTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); + + EPerson userC = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userC@test.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + String authTokenC = getAuthToken(userC.getEmail(), password); + + getClient(authTokenC).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isForbidden()); + + // a simple patch to update an existent metadata + List updateTitle = new ArrayList<>(); + Map value = new HashMap<>(); + value.put("value", "New Title"); + updateTitle.add(new ReplaceOperation("/sections/traditionalpageone/dc.title/0", value)); + String patchBody = getPatchContent(updateTitle); + + getClient(authTokenB).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry")))); + + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + + getClient(authTokenB).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 5e3c477506..82bedf4a92 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -67,6 +67,17 @@ public class FacetEntryMatcher { ); } + public static Matcher supervisedByFacet(boolean hasNext) { + return allOf( + hasJsonPath("$.name", is("supervisedBy")), + hasJsonPath("$.facetType", is("authority")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/supervisedBy")), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/supervisedBy")) + + ); + } + public static Matcher dateIssuedFacet(boolean hasNext) { return allOf( hasJsonPath("$.name", is("dateIssued")), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java index 2c7cebdbe9..eff068ea7b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java @@ -97,4 +97,16 @@ public class FacetValueMatcher { hasJsonPath("$._links.search.href", containsString(",equals")) ); } + + public static Matcher entrySupervisedBy(String label, String authority, int count) { + return allOf( + hasJsonPath("$.authorityKey", is(authority)), + hasJsonPath("$.count", is(count)), + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")), + hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), + hasJsonPath("$._links.search.href", containsString("f.supervisedBy=" + authority + ",authority")) + ); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java new file mode 100644 index 0000000000..1ba147879d --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.GroupMatcher.matchGroupEntry; +import static org.dspace.app.rest.matcher.ItemMatcher.matchItemProperties; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.hamcrest.Matcher; + +/** + * Utility class to construct a Matcher for an SupervisionOrder object + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderMatcher { + + private SupervisionOrderMatcher() { + } + + public static Matcher matchSuperVisionOrder(SupervisionOrder supervisionOrder) { + Group group = supervisionOrder.getGroup(); + return allOf( + hasJsonPath("$.id", is(supervisionOrder.getID())), + hasJsonPath("$._embedded.item", matchItemProperties(supervisionOrder.getItem())), + hasJsonPath("$._embedded.group", matchGroupEntry(group.getID(), group.getName())) + ); + } + +} diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 085ed0bd6e..7017db0dba 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -93,5 +93,7 @@ + + diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index ae4b5e6e3b..c5e2353170 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -64,5 +64,7 @@ + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index 20e5297b6c..cef906adc8 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -55,4 +55,6 @@ + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index a124ec830f..f7ddd90bc6 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -148,5 +148,7 @@ + + diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 57f4c07aee..c3606c407d 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -40,6 +40,9 @@ + + + @@ -61,10 +64,14 @@ + + + + @@ -894,6 +901,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2792,6 +2949,18 @@ + + + + + + + placeholder.placeholder.placeholder + + + + @@ -2866,4 +3035,21 @@ + + + + + + + + + + + + + + + + +