diff --git a/.gitattributes b/.gitattributes index 03b42152e9..c00b03e686 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,12 @@ # Auto detect text files and perform LF normalization * text=auto +# Ensure Unix files always keep Unix line endings +*.sh text eol=lf + +# Ensure Windows files always keep Windows line endings +*.bat text eol=crlf + # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain diff --git a/.travis.yml b/.travis.yml index 7de360a790..dfc4c31799 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,11 +32,12 @@ script: # license:check => Validate all source code license headers # -Dmaven.test.skip=false => Enable DSpace Unit Tests # -DskipITs=false => Enable DSpace Integration Tests + # -Pdspace-rest => Enable optional dspace-rest module as part of build # -P !assembly => Skip assembly of "dspace-installer" directory (as it can be memory intensive) # -B => Maven batch/non-interactive mode (recommended for CI) # -V => Display Maven version info before build # -Dsurefire.rerunFailingTestsCount=2 => try again for flakey tests, and keep track of/report on number of retries - - "mvn clean install license:check -Dmaven.test.skip=false -DskipITs=false -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2" + - "mvn clean install license:check -Dmaven.test.skip=false -DskipITs=false -Pdspace-rest -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2" # After a successful build and test (see 'script'), send code coverage reports to coveralls.io # These code coverage reports are generated by jacoco-maven-plugin (during test process above). diff --git a/Dockerfile b/Dockerfile index 2b2698a5d9..006f32f28e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,8 @@ USER dspace ADD --chown=dspace . /app/ COPY dspace/src/main/docker/local.cfg /app/local.cfg -# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small +# Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp) +# Copy the dspace-install directory to /install. Clean up the build to keep the docker image small RUN mvn package && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean @@ -54,12 +55,9 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m # Run the "server" webapp off the /server path (e.g. http://localhost:8080/server/) -# and the v6.x (deprecated) REST API off the "/rest" path -RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \ - ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server # If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN. # You also MUST update the URL in dspace/src/main/docker/local.cfg # Please note that server webapp should only run on one path at a time. #RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ -# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \ -# ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT diff --git a/Dockerfile.cli b/Dockerfile.cli index 5fe00e735f..116b251f2d 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -24,7 +24,7 @@ ADD --chown=dspace . /app/ COPY dspace/src/main/docker/local.cfg /app/local.cfg # Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small -RUN mvn package -P'!dspace-rest' && \ +RUN mvn package && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean diff --git a/Dockerfile.test b/Dockerfile.test index 2109ce3aa8..090f714e28 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -25,8 +25,9 @@ USER dspace ADD --chown=dspace . /app/ COPY dspace/src/main/docker/local.cfg /app/local.cfg -# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small -RUN mvn package && \ +# Build DSpace (including the optional, deprecated "dspace-rest" webapp) +# Copy the dspace-install directory to /install. Clean up the build to keep the docker image small +RUN mvn package -Pdspace-rest && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java index 194da3eba5..fc62f0df19 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java @@ -153,7 +153,7 @@ public class AuthorityValueServiceImpl implements AuthorityValueService { public List findByValue(Context context, String schema, String element, String qualifier, String value) { String field = fieldParameter(schema, element, qualifier); - return findByValue(context, field, qualifier); + return findByValue(context, field, value); } @Override diff --git a/dspace-api/src/main/java/org/dspace/authority/PersonAuthorityValue.java b/dspace-api/src/main/java/org/dspace/authority/PersonAuthorityValue.java index f937b5f7ed..2ffacae2a1 100644 --- a/dspace-api/src/main/java/org/dspace/authority/PersonAuthorityValue.java +++ b/dspace-api/src/main/java/org/dspace/authority/PersonAuthorityValue.java @@ -11,8 +11,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; @@ -140,8 +140,8 @@ public class PersonAuthorityValue extends AuthorityValue { @Override public void setValues(SolrDocument document) { super.setValues(document); - this.firstName = ObjectUtils.toString(document.getFieldValue("first_name")); - this.lastName = ObjectUtils.toString(document.getFieldValue("last_name")); + this.firstName = Objects.toString(document.getFieldValue("first_name"), ""); + this.lastName = Objects.toString(document.getFieldValue("last_name"), ""); nameVariants = new ArrayList(); Collection document_name_variant = document.getFieldValues("name_variant"); if (document_name_variant != null) { diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java index f4208e5835..b84055b8b0 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -8,6 +8,7 @@ package org.dspace.authorize; import java.util.Date; +import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -23,7 +24,6 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; -import org.apache.commons.lang3.ObjectUtils; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; @@ -123,16 +123,16 @@ public class ResourcePolicy implements ReloadableEntity { if (getAction() != other.getAction()) { return false; } - if (!ObjectUtils.equals(getEPerson(), other.getEPerson())) { + if (!Objects.equals(getEPerson(), other.getEPerson())) { return false; } - if (!ObjectUtils.equals(getGroup(), other.getGroup())) { + if (!Objects.equals(getGroup(), other.getGroup())) { return false; } - if (!ObjectUtils.equals(getStartDate(), other.getStartDate())) { + if (!Objects.equals(getStartDate(), other.getStartDate())) { return false; } - if (!ObjectUtils.equals(getEndDate(), other.getEndDate())) { + if (!Objects.equals(getEndDate(), other.getEndDate())) { return false; } return true; @@ -185,7 +185,7 @@ public class ResourcePolicy implements ReloadableEntity { /** * set the action this policy authorizes * - * @param myid action ID from {@link org.dspace.core.Constants#Constants Constants} + * @param myid action ID from {@link org.dspace.core.Constants Constants} */ public void setAction(int myid) { this.actionId = myid; diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 363b3a8df0..cc6d32b8c3 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -367,7 +367,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Override public void setWorkflowGroup(Context context, Collection collection, int step, Group group) - throws SQLException, AuthorizeException { + throws SQLException { Workflow workflow = null; try { workflow = workflowFactory.getWorkflow(collection); @@ -889,4 +889,4 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i throws SQLException { return collectionDAO.getCollectionsWithBitstreamSizesTotal(context); } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 1771473c62..64e86eef40 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -361,20 +361,18 @@ public class Context implements AutoCloseable { // If Context is no longer open/valid, just note that it has already been closed if (!isValid()) { log.info("complete() was called on a closed Context object. No changes to commit."); + return; } try { // As long as we have a valid, writeable database connection, - // rollback any changes if we are in read-only mode, - // otherwise, commit any changes made as part of the transaction - if (isReadOnly()) { - abort(); - } else { + // commit changes. Otherwise, we'll just close the DB connection (see below) + if (!isReadOnly()) { commit(); } } finally { if (dbConnection != null) { - // Free the DB connection + // Free the DB connection and invalidate the Context dbConnection.closeDBConnection(); dbConnection = null; } @@ -395,29 +393,24 @@ public class Context implements AutoCloseable { // If Context is no longer open/valid, just note that it has already been closed if (!isValid()) { log.info("commit() was called on a closed Context object. No changes to commit."); + return; } if (isReadOnly()) { throw new UnsupportedOperationException("You cannot commit a read-only context"); } - // Our DB Connection (Hibernate) will decide if an actual commit is required or not try { - // As long as we have a valid, writeable database connection, - // commit any changes made as part of the transaction - if (isValid()) { - // Dispatch events before committing changes to the database, - // as the consumers may change something too - dispatchEvents(); - } - + // Dispatch events before committing changes to the database, + // as the consumers may change something too + dispatchEvents(); } finally { if (log.isDebugEnabled()) { log.debug("Cache size on commit is " + getCacheSize()); } if (dbConnection != null) { - //Commit our changes + // Commit our changes (this closes the transaction but leaves database connection open) dbConnection.commit(); reloadContextBoundEntities(); } @@ -425,8 +418,12 @@ public class Context implements AutoCloseable { } + /** + * Dispatch any events (cached in current Context) to configured EventListeners (consumers) + * in the EventService. This should be called prior to any commit as some consumers may add + * to the current transaction. Once events are dispatched, the Context's event cache is cleared. + */ public void dispatchEvents() { - // Commit any changes made as part of the transaction Dispatcher dispatcher = null; try { @@ -462,6 +459,7 @@ public class Context implements AutoCloseable { /** * Add an event to be dispatched when this context is committed. + * NOTE: Read-only Contexts cannot add events, as they cannot modify objects. * * @param event event to be dispatched */ @@ -490,6 +488,10 @@ public class Context implements AutoCloseable { return events; } + /** + * Whether or not the context has events cached. + * @return true or false + */ public boolean hasEvents() { return !CollectionUtils.isEmpty(events); } @@ -521,22 +523,25 @@ public class Context implements AutoCloseable { // If Context is no longer open/valid, just note that it has already been closed if (!isValid()) { log.info("abort() was called on a closed Context object. No changes to abort."); + return; } try { - // Rollback ONLY if we have a database connection, and it is NOT Read Only - if (isValid() && !isReadOnly()) { + // Rollback ONLY if we have a database transaction, and it is NOT Read Only + if (!isReadOnly() && isTransactionAlive()) { dbConnection.rollback(); } } catch (SQLException se) { - log.error(se.getMessage(), se); + log.error("Error rolling back transaction during an abort()", se); } finally { try { - if (!dbConnection.isSessionAlive()) { + if (dbConnection != null) { + // Free the DB connection & invalidate the Context dbConnection.closeDBConnection(); + dbConnection = null; } } catch (Exception ex) { - log.error("Exception aborting context", ex); + log.error("Error closing the database connection", ex); } events = null; } @@ -558,7 +563,22 @@ public class Context implements AutoCloseable { */ public boolean isValid() { // Only return true if our DB connection is live - return dbConnection != null && dbConnection.isTransActionAlive(); + // NOTE: A transaction need not exist for our Context to be valid, as a Context may use multiple transactions. + return dbConnection != null && dbConnection.isSessionAlive(); + } + + /** + * Find out whether our context includes an open database transaction. + * Returns true if there is an open transaction. Returns + * false if the context is invalid (e.g. abort() or complete()) + * was called OR no current transaction exists (e.g. commit() was just called + * and no new transaction has begun) + * + * @return + */ + protected boolean isTransactionAlive() { + // Only return true if both Context is valid *and* transaction is alive + return isValid() && dbConnection.isTransActionAlive(); } /** @@ -571,21 +591,22 @@ public class Context implements AutoCloseable { return mode != null && mode == Mode.READ_ONLY; } + /** + * Add a group's UUID to the list of special groups cached in Context + * @param groupID UUID of group + */ public void setSpecialGroup(UUID groupID) { specialGroups.add(groupID); - - // System.out.println("Added " + groupID); } /** - * test if member of special group + * Test if a group is a special group * * @param groupID ID of special group to test * @return true if member */ public boolean inSpecialGroup(UUID groupID) { if (specialGroups.contains(groupID)) { - // System.out.println("Contains " + groupID); return true; } @@ -593,10 +614,9 @@ public class Context implements AutoCloseable { } /** - * Get an array of all of the special groups that current user is a member - * of. + * Get an array of all of the special groups that current user is a member of. * - * @return list of groups + * @return list of special groups * @throws SQLException if database error */ public List getSpecialGroups() throws SQLException { @@ -608,6 +628,10 @@ public class Context implements AutoCloseable { return myGroups; } + /** + * Close the context, aborting any open transactions (if any). + * @throws Throwable + */ @Override protected void finalize() throws Throwable { /* diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index c88cd0c1cd..57d823c743 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -35,6 +35,23 @@ import org.springframework.orm.hibernate5.SessionFactoryUtils; /** * Hibernate implementation of the DBConnection. + *

+ * NOTE: This class does NOT represent a single Hibernate database connection. Instead, it wraps + * Hibernate's Session object to obtain access to a database connection in order to execute one or more + * transactions. + *

+ * Per DSpace's current Hibernate configuration ([dspace]/config/core-hibernate.xml), we use the one-session-per-thread + * approach (ThreadLocalSessionContext). This means that Hibernate creates a single Session per thread (request), at the + * time when getCurrentSession() is first called. + *

+ * This Session may be reused for multiple Transactions, but if commit() is called, any objects (Entities) in + * the Session become disconnected and MUST be reloaded into the Session (see reloadEntity() method below). + *

+ * If an Error occurs, the Session itself is invalidated. No further Transactions can be run on that Session. + *

+ * DSpace generally follows the "Session-per-request" transactional pattern described here: + * https://docs.jboss.org/hibernate/orm/5.0/userguide/en-US/html/ch06.html#session-per-request + * * * @author kevinvandevelde at atmire.com */ @@ -47,32 +64,61 @@ public class HibernateDBConnection implements DBConnection { private boolean batchModeEnabled = false; private boolean readOnlyEnabled = false; + /** + * Retrieves the current Session from Hibernate (per our settings, Hibernate is configured to create one Session + * per thread). If Session doesn't yet exist, it is created. A Transaction is also initialized (or reinintialized) + * in the Session if one doesn't exist, or was previously closed (e.g. if commit() was previously called) + * @return Hibernate current Session object + * @throws SQLException + */ @Override public Session getSession() throws SQLException { + // If we don't yet have a live transaction, start a new one + // NOTE: a Session cannot be used until a Transaction is started. if (!isTransActionAlive()) { sessionFactory.getCurrentSession().beginTransaction(); configureDatabaseMode(); } + // Return the current Hibernate Session object (Hibernate will create one if it doesn't yet exist) return sessionFactory.getCurrentSession(); } + /** + * Check if the connection has a currently active Transaction. A Transaction is active if it has not yet been + * either committed or rolled back. + * @return + */ @Override public boolean isTransActionAlive() { Transaction transaction = getTransaction(); return transaction != null && transaction.isActive(); } + /** + * Retrieve the current Hibernate Transaction object from our Hibernate Session. + * @return current Transaction (may be active or inactive) or null + */ protected Transaction getTransaction() { return sessionFactory.getCurrentSession().getTransaction(); } + /** + * Check if Hibernate Session is still "alive" / open. An open Session may or may not have an open Transaction + * (so isTransactionAlive() may return false even if isSessionAlive() returns true). A Session may be reused for + * multiple transactions (e.g. if commit() is called, the Session remains alive while the Transaction is closed) + * + * @return true if Session is alive, false otherwise + */ @Override public boolean isSessionAlive() { - return sessionFactory.getCurrentSession() != null && sessionFactory.getCurrentSession() - .getTransaction() != null && sessionFactory - .getCurrentSession().getTransaction().getStatus().isOneOf(TransactionStatus.ACTIVE); + return sessionFactory.getCurrentSession() != null && sessionFactory.getCurrentSession().isOpen(); } + /** + * Rollback any changes applied to the current Transaction. This also closes the Transaction. A new Transaction + * may be opened the next time getSession() is called. + * @throws SQLException + */ @Override public void rollback() throws SQLException { if (isTransActionAlive()) { @@ -80,6 +126,14 @@ public class HibernateDBConnection implements DBConnection { } } + /** + * Close our current Database connection. This also closes & unbinds the Hibernate Session from our thread. + *

+ * NOTE: Because DSpace configures Hibernate to automatically create a Session per thread, a Session may still + * exist after this method is called (as Hibernate may automatically create a new Session for the current thread). + * However, Hibernate will automatically clean up any existing Session when the thread closes. + * @throws SQLException + */ @Override public void closeDBConnection() throws SQLException { if (sessionFactory.getCurrentSession() != null && sessionFactory.getCurrentSession().isOpen()) { @@ -87,11 +141,23 @@ public class HibernateDBConnection implements DBConnection { } } + /** + * Commits any current changes cached in the Hibernate Session to the database & closes the Transaction. + * To open a new Transaction, you may call getSession(). + *

+ * WARNING: When commit() is called, while the Session is still "alive", all previously loaded objects (entities) + * become disconnected from the Session. Therefore, if you continue to use the Session, you MUST reload any needed + * objects (entities) using reloadEntity() method. + * + * @throws SQLException + */ @Override public void commit() throws SQLException { if (isTransActionAlive() && !getTransaction().getStatus().isOneOf(TransactionStatus.MARKED_ROLLBACK, TransactionStatus.ROLLING_BACK)) { + // Flush synchronizes the database with in-memory objects in Session (and frees up that memory) getSession().flush(); + // Commit those results to the database & ends the Transaction getTransaction().commit(); } } @@ -132,6 +198,16 @@ public class HibernateDBConnection implements DBConnection { return getSession().getStatistics().getEntityCount(); } + /** + * Reload an entity into the Hibernate cache. This can be called after a call to commit() to re-cache an object + * in the Hibernate Session (see commit()). Failing to reload objects into the cache may result in a Hibernate + * throwing a "LazyInitializationException" if you attempt to use an object that has been disconnected from the + * Session cache. + * @param entity The DSpace object to reload + * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. + * @return the newly cached object. + * @throws SQLException + */ @Override @SuppressWarnings("unchecked") public E reloadEntity(final E entity) throws SQLException { @@ -167,10 +243,13 @@ public class HibernateDBConnection implements DBConnection { } /** - * Evict an entity from the hibernate cache. This is necessary when batch processing a large number of items. + * Evict an entity from the hibernate cache. + *

+ * When an entity is evicted, it frees up the memory used by that entity in the cache. This is often + * necessary when batch processing a large number of objects (to avoid out-of-memory exceptions). * - * @param entity The entity to reload - * @param The class of the enity. The entity must implement the {@link ReloadableEntity} interface. + * @param entity The entity to evict + * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. * @throws SQLException When reloading the entity from the database fails. */ @Override diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java index ca8efc09c4..398097a9ec 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java @@ -8,22 +8,59 @@ package org.dspace.submit.model; /** + * This class represents an option available in the submission upload section to + * set permission on a file. An option is defined by a name such as "open + * access", "embargo", "restricted access", etc. and some optional attributes to + * better clarify the constraints and input available to the user. For instance + * an embargo option could allow to set a start date not longer than 3 years, + * etc + * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ public class AccessConditionOption { - + /** An unique name identifying the access contion option **/ private String name; + /** + * the name of the group that will be bound to the resource policy created if + * such option is used + */ private String groupName; + /** + * this is in alternative to the {@link #groupName}. The sub-groups listed in + * the DSpace group identified by the name here specified will be available to + * the user to personalize the access condition. They can be for instance + * University Staff, University Students, etc. so that a "restricted access" + * option can be further specified without the need to create separate access + * condition options for each group + */ private String selectGroupName; + /** + * set to true if this option requires a start date to be indicated + * for the underlying resource policy to create + */ private Boolean hasStartDate; + /** + * set to true if this option requires an end date to be indicated + * for the underlying resource policy to create + */ private Boolean hasEndDate; + /** + * It contains, if applicable, the maximum start date (i.e. when the "embargo + * expires") that can be selected. It accepts date math via joda library (such as + * +3years) + */ private String startDateLimit; + /** + * It contains, if applicable, the maximum end date (i.e. when the "lease + * expires") that can be selected. It accepts date math via joda library (such as + * +3years) + */ private String endDateLimit; public String getName() { @@ -81,6 +118,4 @@ public class AccessConditionOption { public void setSelectGroupName(String selectGroupName) { this.selectGroupName = selectGroupName; } - - } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowFactoryImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowFactoryImpl.java index 1689ff0bb0..ffc62dcddb 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowFactoryImpl.java @@ -7,29 +7,53 @@ */ package org.dspace.xmlworkflow; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; /** - * The workflowfactory is responsible for parsing the - * workflow xml file and is used to retrieve the workflow for - * a certain collection + * The workflowfactory is responsible for parsing the workflow xml file and is used to retrieve info about the workflow: + * - the workflow for a certain collection + * - collections mapped to a certain workflow + * - collections not mapped to any workflow + * - configured workflows and the default workflow + * - workflow action by name * * @author Bram De Schouwer (bram.deschouwer at dot com) * @author Kevin Van de Velde (kevin at atmire dot com) * @author Ben Bosman (ben at atmire dot com) * @author Mark Diggory (markd at atmire dot com) + * @author Maria Verdonck (Atmire) on 11/12/2019 */ public class XmlWorkflowFactoryImpl implements XmlWorkflowFactory { public static final String LEGACY_WORKFLOW_NAME = "defaultWorkflow"; + private Logger log = org.apache.logging.log4j.LogManager.getLogger(XmlWorkflowFactoryImpl.class); + private Map workflowMapping; + @Autowired + protected CollectionService collectionService; + + @Autowired + protected HandleService handleService; + @Override public Workflow getWorkflow(Collection collection) throws WorkflowConfigurationException { // Attempt to retrieve our workflow object @@ -50,4 +74,93 @@ public class XmlWorkflowFactoryImpl implements XmlWorkflowFactory { public void setWorkflowMapping(Map workflowMapping) { this.workflowMapping = workflowMapping; } -} \ No newline at end of file + + @Override + public Workflow getWorkflowByName(String workflowName) throws WorkflowConfigurationException { + for (Workflow workflow : workflowMapping.values()) { + if (workflow.getID().equals(workflowName)) { + return workflow; + } + } + throw new WorkflowConfigurationException( + "Error while retrieving workflow by the following name: " + workflowName); + } + + @Override + public Workflow getDefaultWorkflow() { + return this.workflowMapping.get(LEGACY_WORKFLOW_NAME); + } + + @Override + public List getAllConfiguredWorkflows() { + return new ArrayList<>(this.workflowMapping.values()); + } + + @Override + public List getCollectionHandlesMappedToWorklow(Context context, String workflowName) { + List collectionsMapped = new ArrayList<>(); + for (String handle : this.workflowMapping.keySet()) { + if (this.workflowMapping.get(handle).getID().equals(workflowName)) { + try { + Collection collection = (Collection) handleService.resolveToObject(context, handle); + if (collection != null) { + collectionsMapped.add(collection); + } + } catch (SQLException e) { + log.error("SQLException in XmlWorkflowFactoryImpl.getCollectionHandlesMappedToWorklow trying to " + + "retrieve collection with handle: " + handle, e); + } + } + } + return collectionsMapped; + } + + @Override + public List getAllNonMappedCollectionsHandles(Context context) { + List nonMappedCollections = new ArrayList<>(); + try { + for (Collection collection : this.collectionService.findAll(context)) { + if (workflowMapping.get(collection.getHandle()) == null) { + nonMappedCollections.add(collection); + } + } + } catch (SQLException e) { + log.error("SQLException in XmlWorkflowFactoryImpl.getAllNonMappedCollectionsHandles trying to " + + "retrieve all collections", e); + } + return nonMappedCollections; + } + + @Override + public boolean workflowByThisNameExists(String workflowName) { + for (Workflow workflow : this.workflowMapping.values()) { + if (workflow.getID().equals(workflowName)) { + return true; + } + } + return false; + } + + + @Override + public boolean isDefaultWorkflow(String workflowName) { + if (StringUtils.isNotBlank(workflowName)) { + Workflow defaultWorkflow = this.getDefaultWorkflow(); + if (defaultWorkflow != null && StringUtils.isNotBlank(defaultWorkflow.getID())) { + return (defaultWorkflow.getID().equals(workflowName)); + } + } + return false; + } + + @Override + public WorkflowActionConfig getActionByName(String workflowActionName) { + return new DSpace().getServiceManager().getServiceByName(workflowActionName, WorkflowActionConfig.class); + } + + @Override + public Step getStepByName(String workflowStepName) { + return new DSpace().getServiceManager().getServiceByName(workflowStepName, Step.class); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index 3b7937bca1..1ee8f21461 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -309,7 +309,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { @Override public WorkflowActionConfig doState(Context c, EPerson user, HttpServletRequest request, int workflowItemId, Workflow workflow, WorkflowActionConfig currentActionConfig) - throws SQLException, AuthorizeException, IOException, MessagingException, WorkflowException { + throws SQLException, AuthorizeException, IOException, WorkflowException { try { XmlWorkflowItem wi = xmlWorkflowItemService.find(c, workflowItemId); Step currentStep = currentActionConfig.getStep(); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/factory/XmlWorkflowFactory.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/factory/XmlWorkflowFactory.java index 47626bed44..5d33843747 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/factory/XmlWorkflowFactory.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/factory/XmlWorkflowFactory.java @@ -7,23 +7,31 @@ */ package org.dspace.xmlworkflow.factory; +import java.util.List; + import org.dspace.content.Collection; +import org.dspace.core.Context; import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; /** - * The xmlworkflowfactory is responsible for parsing the - * workflow xml file and is used to retrieve the workflow for - * a certain collection + * The workflowfactory is responsible for parsing the workflow xml file and is used to retrieve info about the workflow: + * - the workflow for a certain collection + * - collections mapped to a certain workflow + * - collections not mapped to any workflow + * - configured workflows and the default workflow + * - workflow action by name * * @author Bram De Schouwer (bram.deschouwer at dot com) * @author Kevin Van de Velde (kevin at atmire dot com) * @author Ben Bosman (ben at atmire dot com) * @author Mark Diggory (markd at atmire dot com) + * @author Maria Verdonck (Atmire) on 11/12/2019 */ public interface XmlWorkflowFactory { - /** * Retrieve the workflow configuration for a single collection * @@ -32,4 +40,74 @@ public interface XmlWorkflowFactory { * @throws WorkflowConfigurationException occurs if there is a configuration error in the workflow */ public Workflow getWorkflow(Collection collection) throws WorkflowConfigurationException; + + /** + * Retrieves the workflow configuration by name + * + * @param workflowName the name for which we want our workflow + * @return the workflow configuration + * @throws WorkflowConfigurationException occurs if there is no workflow configured by that name + */ + public Workflow getWorkflowByName(String workflowName) throws WorkflowConfigurationException; + + /** + * Creates a list of all configured workflows, or returns the cache of this if it was already created + * + * @return List of all configured workflows + */ + public List getAllConfiguredWorkflows(); + + /** + * Check to see if there is a workflow configured by the given name + * + * @param workflowName Name of a possible configured workflow + * @return True if there is a workflow configured by this name, false otherwise + */ + public boolean workflowByThisNameExists(String workflowName); + + /** + * Check to see if the given workflowName is the workflow configured to be default for collections + * + * @param workflowName Name of workflow to check if default + * @return True if given workflowName is the workflow mapped to default for collections, otherwise false + */ + public boolean isDefaultWorkflow(String workflowName); + + /** + * Gets the default workflow, i.e. the workflow that is mapped to collection=default in workflow.xml + */ + public Workflow getDefaultWorkflow(); + + /** + * Return a list of collections that are mapped to the given workflow in the workflow configuration. + * * Makes use of a cache so it only retrieves the workflowName->List if it's not cached + * + * @param context Dspace context + * @param workflowName Name of workflow we want the collections of that are mapped to is + * @return List of collections mapped to the requested workflow + */ + public List getCollectionHandlesMappedToWorklow(Context context, String workflowName); + + /** + * Returns list of collections that are not mapped to any configured workflow, and thus use the default workflow + * + * @return List of collections not mapped to any workflow + */ + public List getAllNonMappedCollectionsHandles(Context context); + + /** + * Retrieves a {@link WorkflowActionConfig} object based on its name, should correspond with bean id in workflow-actions.xml + * + * @param workflowActionName Name of workflow action we want to retrieve + * @return Workflow action object corresponding to the given workflowActionName + */ + public WorkflowActionConfig getActionByName(String workflowActionName); + + /** + * Retrieves a {@link Step} object based on its name, should correspond with bean id in workflow.xml + * + * @param workflowStepName Name of workflow step we want to retrieve + * @return Workflow step object corresponding to the given workflowStepName + */ + public Step getStepByName(String workflowStepName); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java index ba481be3d9..c0c05b1745 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java @@ -36,8 +36,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; public abstract class Action { private WorkflowActionConfig parent; - private static String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields"; - + private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields"; public abstract void activate(Context c, XmlWorkflowItem wf) throws SQLException, IOException, AuthorizeException, WorkflowException; @@ -45,6 +44,26 @@ public abstract class Action { public abstract ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException, IOException, WorkflowException; + /** + * Returns a list of options that the user can select at this action which results in the next step in the workflow + * @return A list of options of this action, resulting in the next step of the workflow + */ + public abstract List getOptions(); + + /** + * Returns true if one of the options is a parameter of the request + * @param request Action request + * @return true if one of the options is a parameter of the request; false if none was found + */ + protected boolean isOptionInParam(HttpServletRequest request) { + for (String option: this.getOptions()) { + if (request.getParameter(option) != null) { + return true; + } + } + return false; + } + public WorkflowActionConfig getParent() { return parent; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java index 2c1a7b1fe8..1dc61888b1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java @@ -7,6 +7,8 @@ */ package org.dspace.xmlworkflow.state.actions; +import java.util.List; + import org.dspace.xmlworkflow.state.Step; /** @@ -59,4 +61,12 @@ public class WorkflowActionConfig { return step; } + /** + * Returns a list of options the user has on this action, resulting in the next step of the workflow + * @return A list of options of this action, resulting in the next step of the workflow + */ + public List getOptions() { + return this.processingAction.getOptions(); + } + } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java index 7d330aace9..cb74bcf22d 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java @@ -9,8 +9,11 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; import org.dspace.content.MetadataSchemaEnum; @@ -31,40 +34,51 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; */ public class AcceptEditRejectAction extends ProcessingAction { - public static final int MAIN_PAGE = 0; - public static final int REJECT_PAGE = 1; + private static final String SUBMIT_APPROVE = "submit_approve"; + private static final String SUBMIT_REJECT = "submit_reject"; //TODO: rename to AcceptAndEditMetadataAction @Override - public void activate(Context c, XmlWorkflowItem wf) throws SQLException { + public void activate(Context c, XmlWorkflowItem wf) { } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - - if (request.getParameter("submit_approve") != null) { - return processAccept(c, wfi, step, request); - } else { - if (request.getParameter("submit_reject") != null) { - return processRejectPage(c, wfi, step, request); + throws SQLException, AuthorizeException, IOException { + if (super.isOptionInParam(request)) { + switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { + case SUBMIT_APPROVE: + return processAccept(c, wfi); + case SUBMIT_REJECT: + return processRejectPage(c, wfi, request); + default: + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } } return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } - public ActionResult processAccept(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { + @Override + public List getOptions() { + List options = new ArrayList<>(); + options.add(SUBMIT_APPROVE); + options.add(SUBMIT_REJECT); + options.add(ProcessingAction.SUBMIT_EDIT_METADATA); + return options; + } + + public ActionResult processAccept(Context c, XmlWorkflowItem wfi) + throws SQLException, AuthorizeException { //Delete the tasks addApprovedProvenance(c, wfi); return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } - public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { + public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { String reason = request.getParameter("reason"); if (reason == null || 0 == reason.trim().length()) { addErrorField(request, "reason"); @@ -85,14 +99,14 @@ public class AcceptEditRejectAction extends ProcessingAction { // Get user's name + email address String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); + .getEPersonName(c.getCurrentUser()); String provDescription = getProvenanceStartId() + " Approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; + + usersName + " on " + now + " (GMT) "; // Add to item as a DC field itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); + provDescription); itemService.update(c, wfi.getItem()); } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java index 50686f3993..3c4e0ffc1d 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java @@ -7,10 +7,12 @@ */ package org.dspace.xmlworkflow.state.actions.processingaction; -import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; import org.dspace.content.MetadataSchemaEnum; @@ -31,29 +33,41 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; */ public class FinalEditAction extends ProcessingAction { + private static final String SUBMIT_APPROVE = "submit_approve"; @Override - public void activate(Context c, XmlWorkflowItem wf) throws SQLException { + public void activate(Context c, XmlWorkflowItem wf) { } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - return processMainPage(c, wfi, step, request); + throws SQLException, AuthorizeException { + return processMainPage(c, wfi, request); } - public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { - if (request.getParameter("submit_approve") != null) { - //Delete the tasks - addApprovedProvenance(c, wfi); - - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); - } else { - //We pressed the leave button so return to our submissions page - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException { + if (super.isOptionInParam(request)) { + switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { + case SUBMIT_APPROVE: + //Delete the tasks + addApprovedProvenance(c, wfi); + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + default: + //We pressed the leave button so return to our submissions page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } } + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); + } + + @Override + public List getOptions() { + List options = new ArrayList<>(); + options.add(SUBMIT_APPROVE); + options.add(ProcessingAction.SUBMIT_EDIT_METADATA); + return options; } private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { @@ -62,14 +76,14 @@ public class FinalEditAction extends ProcessingAction { // Get user's name + email address String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); + .getEPersonName(c.getCurrentUser()); String provDescription = getProvenanceStartId() + " Approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; + + usersName + " on " + now + " (GMT) "; // Add to item as a DC field itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); + provDescription); itemService.update(c, wfi.getItem()); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index 1fbb02710d..76e5cb5e9f 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -34,6 +34,8 @@ public abstract class ProcessingAction extends Action { @Autowired(required = true) protected ItemService itemService; + public static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata"; + public static final String SUBMIT_CANCEL = "submit_cancel"; @Override public boolean isAuthorized(Context context, HttpServletRequest request, XmlWorkflowItem wfi) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java index 6ccb2c53e6..474945048a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java @@ -9,9 +9,12 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; import org.dspace.content.MetadataSchemaEnum; @@ -34,6 +37,8 @@ public class ReviewAction extends ProcessingAction { public static final int MAIN_PAGE = 0; public static final int REJECT_PAGE = 1; + private static final String SUBMIT_APPROVE = "submit_approve"; + private static final String SUBMIT_REJECT = "submit_reject"; @Override public void activate(Context c, XmlWorkflowItem wfItem) { @@ -43,22 +48,30 @@ public class ReviewAction extends ProcessingAction { @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { - if (request.getParameter("submit_approve") != null) { - return processAccept(c, wfi, step, request); - } else { - if (request.getParameter("submit_reject") != null) { - return processRejectPage(c, wfi, step, request); + if (super.isOptionInParam(request)) { + switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { + case SUBMIT_APPROVE: + return processAccept(c, wfi); + case SUBMIT_REJECT: + return processRejectPage(c, wfi, step, request); + default: + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } } - return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } - public ActionResult processAccept(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { + @Override + public List getOptions() { + List options = new ArrayList<>(); + options.add(SUBMIT_APPROVE); + options.add(SUBMIT_REJECT); + return options; + } + + public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { //Delete the tasks addApprovedProvenance(c, wfi); - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } @@ -68,14 +81,14 @@ public class ReviewAction extends ProcessingAction { // Get user's name + email address String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); + .getEPersonName(c.getCurrentUser()); String provDescription = getProvenanceStartId() + " Approved for entry into archive by " + usersName + " on " + now + " (GMT) "; // Add to item as a DC field itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); + provDescription); itemService.update(c, wfi.getItem()); } @@ -90,8 +103,8 @@ public class ReviewAction extends ProcessingAction { //We have pressed reject, so remove the task the user has & put it back to a workspace item XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), - this.getProvenanceStartId(), reason); + .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), + this.getProvenanceStartId(), reason); return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java index df06c6b0de..a834641111 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java @@ -9,6 +9,7 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -17,7 +18,6 @@ import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.core.Context; -import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.state.Step; @@ -40,14 +40,13 @@ public class ScoreEvaluationAction extends ProcessingAction { private int minimumAcceptanceScore; @Override - public void activate(Context c, XmlWorkflowItem wf) - throws SQLException, IOException, AuthorizeException, WorkflowException { + public void activate(Context c, XmlWorkflowItem wf) { } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException, WorkflowException { + throws SQLException, AuthorizeException, IOException { boolean hasPassed = false; //Retrieve all our scores from the metadata & add em up List scores = itemService @@ -82,6 +81,11 @@ public class ScoreEvaluationAction extends ProcessingAction { } } + @Override + public List getOptions() { + return new ArrayList<>(); + } + public int getMinimumAcceptanceScore() { return minimumAcceptanceScore; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java index 07780704bc..c28fe2d93e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java @@ -7,14 +7,14 @@ */ package org.dspace.xmlworkflow.state.actions.processingaction; -import java.io.IOException; import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; @@ -32,20 +32,21 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; */ public class ScoreReviewAction extends ProcessingAction { + private static final String SUBMIT_SCORE = "submit_score"; + @Override - public void activate(Context c, XmlWorkflowItem wf) - throws SQLException, IOException, AuthorizeException, WorkflowException { + public void activate(Context c, XmlWorkflowItem wf) { } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException, WorkflowException { - if (request.getParameter("submit_score") != null) { + throws SQLException, AuthorizeException { + if (request.getParameter(SUBMIT_SCORE) != null) { int score = Util.getIntParameter(request, "score"); //Add our score to the metadata itemService.addMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, null, - String.valueOf(score)); + String.valueOf(score)); itemService.update(c, wfi.getItem()); return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); @@ -54,4 +55,9 @@ public class ScoreReviewAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); } } + + @Override + public List getOptions() { + return Arrays.asList(SUBMIT_SCORE); + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index e31756538f..b768b4ff8b 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -7,8 +7,8 @@ */ package org.dspace.xmlworkflow.state.actions.processingaction; -import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -18,7 +18,6 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; -import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; @@ -39,36 +38,38 @@ import org.springframework.beans.factory.annotation.Required; */ public class SelectReviewerAction extends ProcessingAction { - public static final int MAIN_PAGE = 0; public static final int SEARCH_RESULTS_PAGE = 1; public static final int RESULTS_PER_PAGE = 5; + private static final String SUBMIT_CANCEL = "submit_cancel"; + private static final String SUBMIT_SEARCH = "submit_search"; + private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer_"; + private Role role; @Autowired(required = true) - protected EPersonService ePersonService; + private EPersonService ePersonService; @Autowired(required = true) - protected WorkflowItemRoleService workflowItemRoleService; + private WorkflowItemRoleService workflowItemRoleService; @Override - public void activate(Context c, XmlWorkflowItem wf) - throws SQLException, IOException, AuthorizeException, WorkflowException { + public void activate(Context c, XmlWorkflowItem wf) { } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException, WorkflowException { - String submitButton = Util.getSubmitButton(request, "submit_cancel"); + throws SQLException, AuthorizeException { + String submitButton = Util.getSubmitButton(request, SUBMIT_CANCEL); //Check if our user has pressed cancel - if (submitButton.equals("submit_cancel")) { + if (submitButton.equals(SUBMIT_CANCEL)) { //Send us back to the submissions page return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); - } else if (submitButton.equals("submit_search")) { + } else if (submitButton.equals(SUBMIT_SEARCH)) { //Perform the search String query = request.getParameter("query"); int page = Util.getIntParameter(request, "result-page"); @@ -85,7 +86,7 @@ public class SelectReviewerAction extends ProcessingAction { request.setAttribute("result-page", page); request.setAttribute("page", SEARCH_RESULTS_PAGE); return new ActionResult(ActionResult.TYPE.TYPE_PAGE, SEARCH_RESULTS_PAGE); - } else if (submitButton.startsWith("submit_select_reviewer_")) { + } else if (submitButton.startsWith(SUBMIT_SELECT_REVIEWER)) { //Retrieve the identifier of the eperson which will do the reviewing UUID reviewerId = UUID.fromString(submitButton.substring(submitButton.lastIndexOf("_") + 1)); EPerson reviewer = ePersonService.find(c, reviewerId); @@ -102,6 +103,14 @@ public class SelectReviewerAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_ERROR); } + @Override + public List getOptions() { + List options = new ArrayList<>(); + options.add(SUBMIT_SEARCH); + options.add(SUBMIT_SELECT_REVIEWER); + return options; + } + public Role getRole() { return role; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java index 215eaaf645..d115832389 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java @@ -9,6 +9,8 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; @@ -38,6 +40,10 @@ public class SingleUserReviewAction extends ProcessingAction { public static final int OUTCOME_REJECT = 1; + protected static final String SUBMIT_APPROVE = "submit_approve"; + protected static final String SUBMIT_REJECT = "submit_reject"; + protected static final String SUBMIT_DECLINE_TASK = "submit_decline_task"; + @Override public void activate(Context c, XmlWorkflowItem wfItem) { @@ -58,19 +64,28 @@ public class SingleUserReviewAction extends ProcessingAction { } } + @Override + public List getOptions() { + List options = new ArrayList<>(); + options.add(SUBMIT_APPROVE); + options.add(SUBMIT_REJECT); + options.add(SUBMIT_DECLINE_TASK); + return options; + } + public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException { - if (request.getParameter("submit_approve") != null) { + if (request.getParameter(SUBMIT_APPROVE) != null) { //Delete the tasks addApprovedProvenance(c, wfi); return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); - } else if (request.getParameter("submit_reject") != null) { + } else if (request.getParameter(SUBMIT_REJECT) != null) { // Make sure we indicate which page we want to process request.setAttribute("page", REJECT_PAGE); // We have pressed reject item, so take the user to a page where he can reject return new ActionResult(ActionResult.TYPE.TYPE_PAGE); - } else if (request.getParameter("submit_decline_task") != null) { + } else if (request.getParameter(SUBMIT_DECLINE_TASK) != null) { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, OUTCOME_REJECT); } else { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java index 5b8d9b08b8..e837a8a893 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java @@ -8,6 +8,8 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.core.Context; @@ -34,6 +36,11 @@ public class AssignAction extends UserSelectionAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + @Override + public List getOptions() { + return new ArrayList<>(); + } + public void generateTasks() { } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java index 5786515a81..01d995ccf6 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java @@ -9,7 +9,9 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -112,6 +114,11 @@ public class AssignOriginalSubmitterAction extends UserSelectionAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + @Override + public List getOptions() { + return new ArrayList<>(); + } + /** * Create a claimed task for the user IF this user doesn't have a claimed action for this workflow item * diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java index 3c6ce50b0d..3f87c26029 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java @@ -9,6 +9,7 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -106,6 +107,11 @@ public class AutoAssignAction extends UserSelectionAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + @Override + public List getOptions() { + return new ArrayList<>(); + } + /** * Create a claimed task for the user IF this user doesn't have a claimed action for this workflow item * diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java index fb154aa7ed..78742c6553 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java @@ -10,6 +10,7 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.List; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -67,6 +68,11 @@ public class ClaimAction extends UserSelectionAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + @Override + public List getOptions() { + return new ArrayList<>(); + } + @Override public void alertUsersOnActivation(Context c, XmlWorkflowItem wfi, RoleMembers roleMembers) throws IOException, SQLException { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java index 5e134855a7..1ffce1afdb 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java @@ -8,6 +8,8 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.core.Context; @@ -37,6 +39,11 @@ public class InheritUsersAction extends UserSelectionAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + @Override + public List getOptions() { + return new ArrayList<>(); + } + @Override public boolean isFinished(XmlWorkflowItem wfi) { return false; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java index 35eeaf1a0e..d23a98cedb 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java @@ -7,14 +7,12 @@ */ package org.dspace.xmlworkflow.state.actions.userassignment; -import java.io.IOException; -import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.xmlworkflow.RoleMembers; -import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -34,12 +32,11 @@ public class NoUserSelectionAction extends UserSelectionAction { } @Override - public void regenerateTasks(Context c, XmlWorkflowItem wfi, RoleMembers roleMembers) throws SQLException { + public void regenerateTasks(Context c, XmlWorkflowItem wfi, RoleMembers roleMembers) { } @Override - public boolean isValidUserSelection(Context context, XmlWorkflowItem wfi, boolean hasUI) - throws WorkflowConfigurationException, SQLException { + public boolean isValidUserSelection(Context context, XmlWorkflowItem wfi, boolean hasUI) { return true; } @@ -49,12 +46,16 @@ public class NoUserSelectionAction extends UserSelectionAction { } @Override - public void activate(Context c, XmlWorkflowItem wf) throws SQLException, IOException { + public void activate(Context c, XmlWorkflowItem wf) { } @Override - public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { + public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + + @Override + public List getOptions() { + return new ArrayList<>(); + } } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml new file mode 100644 index 0000000000..b734c78937 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml new file mode 100644 index 0000000000..7381972961 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml index 0a6cf3cc4a..47f22c5d88 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml @@ -9,8 +9,8 @@ - - + + diff --git a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java index 43b47e83b2..8ff16f5baf 100644 --- a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java +++ b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java @@ -15,9 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.sql.SQLException; -import java.util.Arrays; import java.util.Iterator; -import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; @@ -47,7 +45,6 @@ import org.xmlunit.diff.ComparisonFormatter; import org.xmlunit.diff.DefaultComparisonFormatter; import org.xmlunit.diff.Diff; import org.xmlunit.diff.Difference; -import org.xmlunit.util.Predicate; /** * Tests of {@link StructBuilder}. @@ -314,9 +311,9 @@ public class StructBuilderIT } /** - * Reject uninteresting nodes. + * Reject uninteresting nodes. (currently commented out of tests above) */ - private static class MyNodeFilter implements Predicate { + /*private static class MyNodeFilter implements Predicate { private static final List dontCare = Arrays.asList( "description", "intro", @@ -330,5 +327,5 @@ public class StructBuilderIT String type = node.getLocalName(); return ! dontCare.contains(type); } - } + }*/ } diff --git a/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java b/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java index 9380c4a065..84e776b983 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java @@ -8,7 +8,7 @@ package org.dspace.app.util; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import java.util.ArrayList; diff --git a/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java b/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java index 5bd95a51bb..e2b49ab76a 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java @@ -14,7 +14,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.util.List; +import com.google.common.base.Splitter; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; @@ -85,7 +87,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { log.error("SQL Error in init", ex); fail("SQL Error in init: " + ex.getMessage()); } catch (IOException e) { - e.printStackTrace(); + log.error("IO Error in init", e); + fail("IO Error in init: " + e.getMessage()); } } @@ -119,8 +122,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { context.restoreAuthSystemState(); context.commit(); GoogleMetadata gm = new GoogleMetadata(this.context, it); - String[] urlSplitted = gm.getPDFURL().get(0).split("/"); - assertEquals("Pdf", urlSplitted[urlSplitted.length - 1]); + List urlSplitted = Splitter.on("/").splitToList(gm.getPDFURL().get(0)); + assertEquals("Pdf", urlSplitted.get(urlSplitted.size() - 1)); } /** @@ -154,8 +157,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { context.restoreAuthSystemState(); context.commit(); GoogleMetadata gm = new GoogleMetadata(this.context, it); - String[] urlSplitted = gm.getPDFURL().get(0).split("/"); - assertEquals("size9", urlSplitted[urlSplitted.length - 1]); + List urlSplitted = Splitter.on("/").splitToList(gm.getPDFURL().get(0)); + assertEquals("size9", urlSplitted.get(urlSplitted.size() - 1)); } /** @@ -189,8 +192,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { context.restoreAuthSystemState(); context.commit(); GoogleMetadata gm = new GoogleMetadata(this.context, it); - String[] urlSplitted = gm.getPDFURL().get(0).split("/"); - assertEquals("first", urlSplitted[urlSplitted.length - 1]); + List urlSplitted = Splitter.on("/").splitToList(gm.getPDFURL().get(0)); + assertEquals("first", urlSplitted.get(urlSplitted.size() - 1)); } /** @@ -225,8 +228,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { context.restoreAuthSystemState(); context.commit(); GoogleMetadata gm = new GoogleMetadata(this.context, it); - String[] urlSplitted = gm.getPDFURL().get(0).split("/"); - assertEquals("primary", urlSplitted[urlSplitted.length - 1]); + List urlSplitted = Splitter.on("/").splitToList(gm.getPDFURL().get(0)); + assertEquals("primary", urlSplitted.get(urlSplitted.size() - 1)); } /** @@ -261,8 +264,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { context.restoreAuthSystemState(); context.commit(); GoogleMetadata gm = new GoogleMetadata(this.context, it); - String[] urlSplitted = gm.getPDFURL().get(0).split("/"); - assertEquals("large", urlSplitted[urlSplitted.length - 1]); + List urlSplitted = Splitter.on("/").splitToList(gm.getPDFURL().get(0)); + assertEquals("large", urlSplitted.get(urlSplitted.size() - 1)); } @@ -285,7 +288,7 @@ public class GoogleMetadataTest extends AbstractUnitTest { @Test public void testGetPDFURLWithNoBitstreams() throws Exception { context.turnOffAuthorisationSystem(); - Bundle bundle = ContentServiceFactory.getInstance().getBundleService().create(context, it, "ORIGINAL"); + ContentServiceFactory.getInstance().getBundleService().create(context, it, "ORIGINAL"); context.restoreAuthSystemState(); context.commit(); @@ -319,8 +322,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { context.restoreAuthSystemState(); context.commit(); GoogleMetadata gm = new GoogleMetadata(this.context, it); - String[] urlSplitted = gm.getPDFURL().get(0).split("/"); - assertEquals("small", urlSplitted[urlSplitted.length - 1]); + List urlSplitted = Splitter.on("/").splitToList(gm.getPDFURL().get(0)); + assertEquals("small", urlSplitted.get(urlSplitted.size() - 1)); } @After @@ -334,12 +337,8 @@ public class GoogleMetadataTest extends AbstractUnitTest { community = context.reloadEntity(community); ContentServiceFactory.getInstance().getCommunityService().delete(context, community); community = null; - } catch (SQLException e) { - e.printStackTrace(); - } catch (AuthorizeException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + } catch (Exception e) { + throw new AssertionError("Error occurred in destroy()", e); } it = null; super.destroy(); diff --git a/dspace-api/src/test/java/org/dspace/authenticate/IPMatcherTest.java b/dspace-api/src/test/java/org/dspace/authenticate/IPMatcherTest.java index 3f5a08a648..511ea0da25 100644 --- a/dspace-api/src/test/java/org/dspace/authenticate/IPMatcherTest.java +++ b/dspace-api/src/test/java/org/dspace/authenticate/IPMatcherTest.java @@ -264,8 +264,8 @@ public class IPMatcherTest { assertFalse(ipMatcher.match("192.1.2.2")); } - - private ArrayList getAllIp4Except(ArrayList exceptions) { + // Commented out as this is currently not used in tests + /*private ArrayList getAllIp4Except(ArrayList exceptions) { int d1 = 0; int d2 = 0; int d3 = 0; @@ -284,7 +284,7 @@ public class IPMatcherTest { } } return ips; - } + }*/ private void verifyAllIp4Except(ArrayList exceptions, boolean asserted, IPMatcher ipMatcher) throws IPMatcherException { diff --git a/dspace-api/src/test/java/org/dspace/authorize/AuthorizeServiceTest.java b/dspace-api/src/test/java/org/dspace/authorize/AuthorizeServiceTest.java index 7cd5873e06..110700070f 100644 --- a/dspace-api/src/test/java/org/dspace/authorize/AuthorizeServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/authorize/AuthorizeServiceTest.java @@ -90,8 +90,7 @@ public class AuthorizeServiceTest extends AbstractUnitTest { @Test public void testauthorizeMethodRespectSpecialGroups() { - EPerson eperson1; - EPerson eperson2; + EPerson eperson; Group group1; Community dso; @@ -99,7 +98,7 @@ public class AuthorizeServiceTest extends AbstractUnitTest { context.turnOffAuthorisationSystem(); // create an eperson and a group - eperson1 = ePersonService.create(context); + eperson = ePersonService.create(context); group1 = groupService.create(context); // A group has to have a name, otherwise there are queries that break groupService.setName(group1, "My test group 2"); @@ -111,19 +110,19 @@ public class AuthorizeServiceTest extends AbstractUnitTest { // special group to the user. Then test if the action on the DSO // is allowed for the user authorizeService.addPolicy(context, dso, Constants.ADD, group1); - context.setCurrentUser(eperson1); + context.setCurrentUser(eperson); context.setSpecialGroup(group1.getID()); context.commit(); } catch (SQLException | AuthorizeException ex) { - throw new RuntimeException(ex); + throw new AssertionError(ex); } finally { context.restoreAuthSystemState(); } try { - Assert.assertTrue(authorizeService.authorizeActionBoolean(context, eperson1, dso, Constants.ADD, true)); + Assert.assertTrue(authorizeService.authorizeActionBoolean(context, eperson, dso, Constants.ADD, true)); } catch (SQLException ex) { - throw new RuntimeException(ex); + throw new AssertionError(ex); } } } diff --git a/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java b/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java index 436bc51589..958ddc0876 100644 --- a/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java @@ -232,7 +232,7 @@ public class BitstreamFormatTest extends AbstractUnitTest { // Disalow full Admin perms when(authorizeServiceSpy.isAdmin(context)).thenReturn(false); - BitstreamFormat found = bitstreamFormatService.create(context); + bitstreamFormatService.create(context); fail("Exception should have been thrown"); } diff --git a/dspace-api/src/test/java/org/dspace/content/BundleTest.java b/dspace-api/src/test/java/org/dspace/content/BundleTest.java index aecd99b006..bd8cca912b 100644 --- a/dspace-api/src/test/java/org/dspace/content/BundleTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BundleTest.java @@ -363,7 +363,7 @@ public class BundleTest extends AbstractDSpaceObjectTest { doThrow(new AuthorizeException()).when(authorizeServiceSpy).authorizeAction(context, b, Constants.ADD); File f = new File(testProps.get("test.bitstream").toString()); - Bitstream bs = bitstreamService.create(context, b, new FileInputStream(f)); + bitstreamService.create(context, b, new FileInputStream(f)); fail("Exception should be thrown"); } @@ -399,7 +399,7 @@ public class BundleTest extends AbstractDSpaceObjectTest { int assetstore = 0; //default assetstore File f = new File(testProps.get("test.bitstream").toString()); - Bitstream bs = bitstreamService.register(context, b, assetstore, f.getAbsolutePath()); + bitstreamService.register(context, b, assetstore, f.getAbsolutePath()); fail("Exception should be thrown"); } diff --git a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java index 2e51fb908b..0b50c53615 100644 --- a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java @@ -196,7 +196,7 @@ public class CollectionTest extends AbstractDSpaceObjectTest { // test creating collection with a specified handle which IS already in use // This should throw an exception - Collection created = collectionService.create(context, owningCommunity, inUseHandle); + collectionService.create(context, owningCommunity, inUseHandle); fail("Exception expected"); } @@ -291,7 +291,6 @@ public class CollectionTest extends AbstractDSpaceObjectTest { String itext = "introductory text"; String copy = "copyright declaration"; String sidebar = "side bar text"; - String tempItem = "3"; String provDesc = "provenance description"; String license = "license text"; @@ -370,7 +369,7 @@ public class CollectionTest extends AbstractDSpaceObjectTest { @Test(expected = AuthorizeException.class) public void testSetLogoNoAuth() throws Exception { File f = new File(testProps.get("test.bitstream").toString()); - Bitstream logo = collectionService.setLogo(context, collection, new FileInputStream(f)); + collectionService.setLogo(context, collection, new FileInputStream(f)); fail("Exception expected"); } @@ -393,7 +392,7 @@ public class CollectionTest extends AbstractDSpaceObjectTest { @Test(expected = AuthorizeException.class) public void testCreateWorkflowGroupNoAuth() throws Exception { int step = 1; - Group result = collectionService.createWorkflowGroup(context, collection, step); + collectionService.createWorkflowGroup(context, collection, step); fail("Exception expected"); } @@ -461,7 +460,7 @@ public class CollectionTest extends AbstractDSpaceObjectTest { */ @Test(expected = AuthorizeException.class) public void testCreateSubmittersNoAuth() throws Exception { - Group result = collectionService.createSubmitters(context, collection); + collectionService.createSubmitters(context, collection); fail("Exception expected"); } @@ -511,7 +510,7 @@ public class CollectionTest extends AbstractDSpaceObjectTest { */ @Test(expected = AuthorizeException.class) public void testCreateAdministratorsNoAuth() throws Exception { - Group result = collectionService.createAdministrators(context, collection); + collectionService.createAdministrators(context, collection); fail("Exception expected"); } diff --git a/dspace-api/src/test/java/org/dspace/content/CommunityTest.java b/dspace-api/src/test/java/org/dspace/content/CommunityTest.java index 0bd9e39fdf..812060d019 100644 --- a/dspace-api/src/test/java/org/dspace/content/CommunityTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CommunityTest.java @@ -195,7 +195,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { // test creating community with no parent (as a non-admin) // this should throw an exception - Community created = communityService.create(null, context); + communityService.create(null, context); fail("Exception expected"); } @@ -230,7 +230,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { // test creating community with a specified handle which IS already in use // This should throw an exception - Community created = communityService.create(null, context, inUseHandle); + communityService.create(null, context, inUseHandle); fail("Exception expected"); } @@ -378,7 +378,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { doThrow(new AuthorizeException()).when(authorizeServiceSpy).authorizeAction(context, c, Constants.WRITE); File f = new File(testProps.get("test.bitstream").toString()); - Bitstream logo = communityService.setLogo(context, c, new FileInputStream(f)); + communityService.setLogo(context, c, new FileInputStream(f)); fail("Exception expected"); } @@ -425,7 +425,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { */ @Test(expected = AuthorizeException.class) public void testCreateAdministratorsNoAuth() throws Exception { - Group result = communityService.createAdministrators(context, c); + communityService.createAdministrators(context, c); fail("Exception should have been thrown"); } @@ -617,7 +617,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { // Disallow current Community ADD perms doThrow(new AuthorizeException()).when(authorizeServiceSpy).authorizeAction(context, c, Constants.ADD); - Collection result = collectionService.create(context, c); + collectionService.create(context, c); fail("Exception expected"); } @@ -672,7 +672,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { // Disallow current Community ADD perms doThrow(new AuthorizeException()).when(authorizeServiceSpy).authorizeAction(context, c, Constants.ADD); - Community result = communityService.createSubcommunity(context, c); + communityService.createSubcommunity(context, c); fail("Exception expected"); } @@ -872,8 +872,9 @@ public class CommunityTest extends AbstractDSpaceObjectTest { Collection grandchildCol = collectionService.create(context, grandchild); // Create two separate items WorkspaceItem wsItem = workspaceItemService.create(context, childCol, false); - wsItem = workspaceItemService.create(context, childCol, false); Item item = installItemService.installItem(context, wsItem); + wsItem = workspaceItemService.create(context, grandchildCol, false); + Item item2 = installItemService.installItem(context, wsItem); // Done creating the objects. Turn auth system back on context.restoreAuthSystemState(); @@ -885,6 +886,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { UUID childColId = childCol.getID(); UUID grandchildColId = grandchildCol.getID(); UUID itemId = item.getID(); + UUID item2Id = item2.getID(); // Delete the parent of this entire hierarchy communityService.delete(context, parent); @@ -902,6 +904,8 @@ public class CommunityTest extends AbstractDSpaceObjectTest { collectionService.find(context, grandchildColId), nullValue()); assertThat("Item not deleted", itemService.find(context, itemId), nullValue()); + assertThat("Item not deleted", + itemService.find(context, item2Id), nullValue()); } /** @@ -1030,7 +1034,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest { assertThat("testGetParentObject 1", communityService.getParentObject(context, son), notNullValue()); assertThat("testGetParentObject 2", (Community) communityService.getParentObject(context, son), equalTo(c)); } catch (AuthorizeException ex) { - fail("Authorize exception catched"); + throw new AssertionError("AuthorizeException occurred", ex); } } diff --git a/dspace-api/src/test/java/org/dspace/content/DCDateTest.java b/dspace-api/src/test/java/org/dspace/content/DCDateTest.java index e827fb4434..8ed97bd46f 100644 --- a/dspace-api/src/test/java/org/dspace/content/DCDateTest.java +++ b/dspace-api/src/test/java/org/dspace/content/DCDateTest.java @@ -19,7 +19,6 @@ import java.util.Locale; import java.util.TimeZone; import org.apache.commons.lang3.time.DateUtils; -import org.apache.logging.log4j.Logger; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -28,11 +27,6 @@ import org.junit.Test; * @author pvillega */ public class DCDateTest { - /** - * log4j category - */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCDateTest.class); - /** * Object to use in the tests */ diff --git a/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java index 16b2d460a2..f4563cebf0 100644 --- a/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java @@ -8,16 +8,14 @@ package org.dspace.content; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.UUID; -import org.dspace.content.dao.RelationshipTypeDAO; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipService; @@ -60,10 +58,8 @@ public class EntityServiceImplTest { public void testfindByItemId() throws Exception { // Declare objects utilized in unit test Item item = mock(Item.class); - List relationshipList = new ArrayList<>(); Relationship relationship = mock(Relationship.class); relationship.setId(9); - relationshipList.add(relationship); // Mock the state of objects utilized in findByItemId() to meet the success criteria of an invocation when(itemService.find(any(), any())).thenReturn(item); @@ -79,10 +75,6 @@ public class EntityServiceImplTest { // Declare objects utilized in unit test Entity entity = mock(Entity.class); EntityTypeService entityTypeService = mock(EntityTypeService.class); - Item item = mock(Item.class); - List list = new ArrayList<>(); - MetadataValue metadataValue = mock(MetadataValue.class); - list.add(metadataValue); EntityType entityType = entityTypeService.findByEntityType(context, "testType"); // The returned EntityType should equal our defined entityType case @@ -151,11 +143,7 @@ public class EntityServiceImplTest { @Test public void testGetAllRelationshipTypes() throws Exception { // Declare objects utilized for this test - List list = new ArrayList<>(); - MetadataValue metadataValue = mock(MetadataValue.class); - list.add(metadataValue); Item item = mock(Item.class); - RelationshipTypeDAO relationshipTypeDAO = mock(RelationshipTypeDAO.class); Entity entity = mock(Entity.class); RelationshipType relationshipType = mock(RelationshipType.class); relationshipType.setLeftType(leftType); @@ -185,7 +173,7 @@ public class EntityServiceImplTest { RelationshipType relationshipType = mock(RelationshipType.class); // Currently this unit test will only test one case with one relationshipType - List relationshipTypeList = new LinkedList<>(); + List relationshipTypeList = new ArrayList<>(); relationshipTypeList.add(relationshipType); List metsList = new ArrayList<>(); MetadataValue metadataValue = mock(MetadataValue.class); @@ -213,7 +201,7 @@ public class EntityServiceImplTest { RelationshipType relationshipType = mock(RelationshipType.class); // Currently this unit test will only test one case with one relationshipType - List relationshipTypeList = new LinkedList<>(); + List relationshipTypeList = new ArrayList<>(); relationshipTypeList.add(relationshipType); List metsList = new ArrayList<>(); MetadataValue metadataValue = mock(MetadataValue.class); @@ -236,7 +224,7 @@ public class EntityServiceImplTest { @Test public void testGetRelationshipTypesByTypeName() throws Exception { // Declare objects utilized in unit test - List list = new LinkedList<>(); + List list = new ArrayList<>(); RelationshipType relationshipType = mock(RelationshipType.class); list.add(relationshipType); diff --git a/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java index 0ba78480b6..c54f0fc955 100644 --- a/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java @@ -8,7 +8,7 @@ package org.dspace.content; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; diff --git a/dspace-api/src/test/java/org/dspace/content/FormatIdentifierTest.java b/dspace-api/src/test/java/org/dspace/content/FormatIdentifierTest.java index 4e0711aac0..1e4854044e 100644 --- a/dspace-api/src/test/java/org/dspace/content/FormatIdentifierTest.java +++ b/dspace-api/src/test/java/org/dspace/content/FormatIdentifierTest.java @@ -14,7 +14,6 @@ import static org.junit.Assert.assertThat; import java.io.File; import java.io.FileInputStream; -import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; @@ -30,11 +29,6 @@ import org.junit.Test; */ public class FormatIdentifierTest extends AbstractUnitTest { - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(FormatIdentifierTest.class); - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); protected BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() .getBitstreamFormatService(); @@ -71,8 +65,8 @@ public class FormatIdentifierTest extends AbstractUnitTest { @Test public void testGuessFormat() throws Exception { File f = new File(testProps.get("test.bitstream").toString()); - Bitstream bs = null; - BitstreamFormat result = null; + Bitstream bs; + BitstreamFormat result; BitstreamFormat pdf = bitstreamFormatService.findByShortDescription(context, "Adobe PDF"); //test null filename diff --git a/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java b/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java index e7f9c29794..ca197e67a9 100644 --- a/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java +++ b/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.factory.ContentServiceFactory; @@ -48,11 +47,6 @@ import org.junit.Test; * @author tdonohue */ public class ITCommunityCollection extends AbstractIntegrationTest { - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ITCommunityCollection.class); - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); @@ -107,8 +101,8 @@ public class ITCommunityCollection extends AbstractIntegrationTest { //verify it works as expected assertThat("testCreateTree 0", parent.getParentCommunities().size(), is(0)); assertThat("testCreateTree 1", child1.getParentCommunities().get(0), equalTo(parent)); - assertThat("testCreateTree 2", (Community) collectionService.getParentObject(context, col1), equalTo(child1)); - assertThat("testCreateTree 3", (Community) collectionService.getParentObject(context, col2), equalTo(child1)); + assertThat("testCreateTree 2", collectionService.getParentObject(context, col1), equalTo(child1)); + assertThat("testCreateTree 3", collectionService.getParentObject(context, col2), equalTo(child1)); context.turnOffAuthorisationSystem(); communityService.delete(context, parent); @@ -133,8 +127,8 @@ public class ITCommunityCollection extends AbstractIntegrationTest { context.restoreAuthSystemState(); //verify it works as expected - assertThat("testCreateItems 0", (Collection) itemService.getParentObject(context, item1), equalTo(col1)); - assertThat("testCreateItems 1", (Collection) itemService.getParentObject(context, item2), equalTo(col2)); + assertThat("testCreateItems 0", itemService.getParentObject(context, item1), equalTo(col1)); + assertThat("testCreateItems 1", itemService.getParentObject(context, item2), equalTo(col2)); context.turnOffAuthorisationSystem(); communityService.delete(context, parent); @@ -158,8 +152,8 @@ public class ITCommunityCollection extends AbstractIntegrationTest { // Add same number of items to each collection for (int count = 0; count < items_per_collection; count++) { - Item item1 = installItemService.installItem(context, workspaceItemService.create(context, col1, false)); - Item item2 = installItemService.installItem(context, workspaceItemService.create(context, col2, false)); + installItemService.installItem(context, workspaceItemService.create(context, col1, false)); + installItemService.installItem(context, workspaceItemService.create(context, col2, false)); } // Finally, let's throw in a small wrench and add a mapped item @@ -229,7 +223,6 @@ public class ITCommunityCollection extends AbstractIntegrationTest { context.setCurrentUser(commAdmin); // Test deletion of single Bitstream as a Community Admin (delete just flags as deleted) - UUID bitstreamId = bitstream.getID(); bitstreamService.delete(context, bitstream); assertTrue("Community Admin unable to flag Bitstream as deleted", bitstream.isDeleted()); @@ -312,7 +305,6 @@ public class ITCommunityCollection extends AbstractIntegrationTest { context.setCurrentUser(collAdmin); // Test deletion of single Bitstream as a Collection Admin (delete just flags as deleted) - UUID bitstreamId = bitstream2.getID(); bitstreamService.delete(context, bitstream2); assertTrue("Collection Admin unable to flag Bitstream as deleted", bitstream2.isDeleted()); @@ -327,7 +319,6 @@ public class ITCommunityCollection extends AbstractIntegrationTest { // Test deletion of single Item as a Collection Admin UUID itemId = item.getID(); bundleId = bundle.getID(); - bitstreamId = bitstream.getID(); itemService.delete(context, item); assertThat("Collection Admin unable to delete sub-Item", itemService.find(context, itemId), nullValue()); diff --git a/dspace-api/src/test/java/org/dspace/content/ITMetadata.java b/dspace-api/src/test/java/org/dspace/content/ITMetadata.java index 10afd4d85f..651b712ba3 100644 --- a/dspace-api/src/test/java/org/dspace/content/ITMetadata.java +++ b/dspace-api/src/test/java/org/dspace/content/ITMetadata.java @@ -16,7 +16,6 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.factory.ContentServiceFactory; @@ -37,11 +36,6 @@ import org.junit.Test; * @author pvillega */ public class ITMetadata extends AbstractIntegrationTest { - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ITMetadata.class); - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); diff --git a/dspace-api/src/test/java/org/dspace/content/InProgressSubmissionTest.java b/dspace-api/src/test/java/org/dspace/content/InProgressSubmissionTest.java index 78be99bb01..1bcac66985 100644 --- a/dspace-api/src/test/java/org/dspace/content/InProgressSubmissionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/InProgressSubmissionTest.java @@ -7,7 +7,6 @@ */ package org.dspace.content; -import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.junit.After; import org.junit.Before; @@ -23,11 +22,6 @@ import org.junit.Test; */ public class InProgressSubmissionTest extends AbstractUnitTest { - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(InProgressSubmissionTest.class); - /** * This method will be run before every test as per @Before. It will * initialize resources required for the tests. diff --git a/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java b/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java index 73c8bc0eb5..54ff9ce026 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java @@ -132,8 +132,8 @@ public class ItemComparatorTest extends AbstractUnitTest { */ @Test public void testCompare() throws SQLException { - int result = 0; - ItemComparator ic = null; + int result; + ItemComparator ic; //one of the tiems has no value ic = new ItemComparator("test", "one", Item.ANY, true); @@ -246,7 +246,7 @@ public class ItemComparatorTest extends AbstractUnitTest { @SuppressWarnings( {"ObjectEqualsNull", "IncompatibleEquals"}) public void testEquals() { ItemComparator ic = new ItemComparator("test", "one", Item.ANY, true); - ItemComparator target = null; + ItemComparator target; assertFalse("testEquals 0", ic.equals(null)); assertFalse("testEquals 1", ic.equals("test one")); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index ed37b14fb7..8c3cfa5a04 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -41,7 +41,6 @@ import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.CollectionService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.core.Constants; @@ -76,8 +75,6 @@ public class ItemTest extends AbstractDSpaceObjectTest { .getBitstreamFormatService(); private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - private Collection collection; private Community owningCommunity; @@ -839,7 +836,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { @Test(expected = AuthorizeException.class) public void testCreateBundleNoAuth() throws Exception { String name = "bundle"; - Bundle created = bundleService.create(context, it, name); + bundleService.create(context, it, name); fail("Exception expected"); } @@ -941,7 +938,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { public void testCreateSingleBitstream_InputStream_StringNoAuth() throws Exception { String name = "new bundle"; File f = new File(testProps.get("test.bitstream").toString()); - Bitstream result = itemService.createSingleBitstream(context, new FileInputStream(f), it, name); + itemService.createSingleBitstream(context, new FileInputStream(f), it, name); fail("Exception expected"); } @@ -972,7 +969,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { @Test(expected = AuthorizeException.class) public void testCreateSingleBitstream_InputStreamNoAuth() throws Exception { File f = new File(testProps.get("test.bitstream").toString()); - Bitstream result = itemService.createSingleBitstream(context, new FileInputStream(f), it); + itemService.createSingleBitstream(context, new FileInputStream(f), it); fail("Expected exception"); } @@ -1624,7 +1621,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { assertThat("testGetParentObject 1", itemService.getParentObject(context, it), notNullValue()); assertThat("testGetParentObject 2", (Collection) itemService.getParentObject(context, it), equalTo(parent)); } catch (AuthorizeException ex) { - fail("Authorize exception catched"); + throw new AssertionError("Authorize Exception occurred", ex); } } diff --git a/dspace-api/src/test/java/org/dspace/content/LicenseUtilsTest.java b/dspace-api/src/test/java/org/dspace/content/LicenseUtilsTest.java index 25ed724bb9..dbba4366f0 100644 --- a/dspace-api/src/test/java/org/dspace/content/LicenseUtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/LicenseUtilsTest.java @@ -104,11 +104,11 @@ public class LicenseUtilsTest extends AbstractUnitTest { @Test public void testGetLicenseText_5args() throws SQLException, AuthorizeException, IOException { //parameters for the test - Locale locale = null; - Collection collection = null; - Item item = null; - EPerson person = null; - Map additionalInfo = null; + Locale locale; + Collection collection; + Item item; + EPerson person; + Map additionalInfo; // We don't test attribute 4 as this is the date, and the date often differs between when the test // is executed, and when the LicenceUtils code gets the current date/time which causes the test to fail @@ -195,10 +195,10 @@ public class LicenseUtilsTest extends AbstractUnitTest { @Test public void testGetLicenseText_4args() throws SQLException, AuthorizeException, IOException { //parameters for the test - Locale locale = null; - Collection collection = null; - Item item = null; - EPerson person = null; + Locale locale; + Collection collection; + Item item; + EPerson person; String template = "Template license: %1$s %2$s %3$s %5$s %6$s"; String templateResult = "Template license: first name last name testgetlicensetext_4args@email.com "; diff --git a/dspace-api/src/test/java/org/dspace/content/NonUniqueMetadataExceptionTest.java b/dspace-api/src/test/java/org/dspace/content/NonUniqueMetadataExceptionTest.java index b99e44c7f2..89d4df0c68 100644 --- a/dspace-api/src/test/java/org/dspace/content/NonUniqueMetadataExceptionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/NonUniqueMetadataExceptionTest.java @@ -9,8 +9,6 @@ package org.dspace.content; import static org.junit.Assert.assertTrue; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.junit.Test; /** @@ -21,12 +19,6 @@ import org.junit.Test; */ public class NonUniqueMetadataExceptionTest { - /** - * log4j category - */ - private static final Logger log = LogManager - .getLogger(NonUniqueMetadataExceptionTest.class); - /** * Dummy test to avoid initialization errors */ diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java index 6290d010e0..df12cccc23 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java @@ -463,8 +463,8 @@ public class RelationshipMetadataServiceTest extends AbstractUnitTest { WorkspaceItem is = workspaceItemService.create(context, col, false); Item secondItem = installItemService.installItem(context, is); itemService.addMetadata(context, secondItem, "relationship", "type", null, null, "Publication"); - Relationship secondRelationship = relationshipService.create(context, secondItem, rightItem, - isAuthorOfPublicationRelationshipType, 0, 0); + relationshipService.create(context, secondItem, rightItem, + isAuthorOfPublicationRelationshipType, 0, 0); context.restoreAuthSystemState(); assertThat(relationshipService.findNextRightPlaceByRightItem(context, rightItem), equalTo(2)); @@ -489,8 +489,8 @@ public class RelationshipMetadataServiceTest extends AbstractUnitTest { itemService.addMetadata(context, secondAuthor, "relationship", "type", null, null, "Author"); itemService.addMetadata(context, secondAuthor, "person", "familyName", null, null, "familyName"); itemService.addMetadata(context, secondAuthor, "person", "givenName", null, null, "firstName"); - Relationship secondRelationship = relationshipService.create(context, leftItem, secondAuthor, - isAuthorOfPublicationRelationshipType, 0, 0); + relationshipService.create(context, leftItem, secondAuthor, + isAuthorOfPublicationRelationshipType, 0, 0); context.restoreAuthSystemState(); assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, leftItem), equalTo(2)); diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java index 7af0278bc9..73d80a77c0 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java @@ -8,12 +8,11 @@ package org.dspace.content; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import org.dspace.authorize.service.AuthorizeService; @@ -137,7 +136,7 @@ public class RelationshipServiceImplTest { @Test public void testFindByItemAndRelationshipType() throws Exception { // Declare objects utilized in unit test - List relList = new LinkedList<>(); + List relList = new ArrayList<>(); Item item = mock(Item.class); RelationshipType testRel = new RelationshipType(); @@ -152,7 +151,7 @@ public class RelationshipServiceImplTest { @Test public void testFindByRelationshipType() throws Exception { // Declare objects utilized in unit test - List relList = new LinkedList<>(); + List relList = new ArrayList<>(); RelationshipType testRel = new RelationshipType(); // The Relationship(s) reported should match our our relList @@ -231,8 +230,6 @@ public class RelationshipServiceImplTest { public void testDelete() throws Exception { // Declare objects utilized in unit test - MetadataValue metVal = mock(MetadataValue.class); - List metsList = new ArrayList<>(); List leftTypelist = new ArrayList<>(); List rightTypelist = new ArrayList<>(); Item leftItem = mock(Item.class); @@ -246,7 +243,6 @@ public class RelationshipServiceImplTest { testRel.setRightwardType("Entitylabel"); testRel.setLeftMinCardinality(0); testRel.setRightMinCardinality(0); - metsList.add(metVal); relationship = getRelationship(leftItem, rightItem, testRel, 0,0); leftTypelist.add(relationship); rightTypelist.add(relationship); diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipTypeTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipTypeTest.java index adea72fe7d..2c57bef96e 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipTypeTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipTypeTest.java @@ -10,15 +10,14 @@ package org.dspace.content; import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Random; -import org.apache.logging.log4j.Logger; import org.dspace.content.dao.RelationshipTypeDAO; import org.dspace.core.Context; import org.junit.Before; @@ -30,9 +29,6 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class RelationshipTypeTest { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RelationshipTypeTest.class); - @InjectMocks private RelationshipTypeServiceImpl relationshipTypeService; @@ -102,7 +98,7 @@ public class RelationshipTypeTest { @Test public void testRelationshipTypeFindAll() throws Exception { // Declare objects utilized for this test - List mockedList = new LinkedList<>(); + List mockedList = new ArrayList<>(); mockedList.add(firstRelationshipType); mockedList.add(secondRelationshipType); @@ -120,7 +116,7 @@ public class RelationshipTypeTest { @Test public void testRelationshipTypeFindByLeftOrRightwardType() throws Exception { // Declare objects utilized for this test - List mockedList = new LinkedList<>(); + List mockedList = new ArrayList<>(); mockedList.add(firstRelationshipType); // Mock DAO to return our mockedList @@ -138,7 +134,7 @@ public class RelationshipTypeTest { @Test public void testRelationshipTypefindByEntityType() throws Exception { // Declare objects utilized for this test - List mockedList = new LinkedList<>(); + List mockedList = new ArrayList<>(); mockedList.add(firstRelationshipType); // Mock DAO to return our mockedList diff --git a/dspace-api/src/test/java/org/dspace/content/SiteTest.java b/dspace-api/src/test/java/org/dspace/content/SiteTest.java index 995e8d496f..02e868e19b 100644 --- a/dspace-api/src/test/java/org/dspace/content/SiteTest.java +++ b/dspace-api/src/test/java/org/dspace/content/SiteTest.java @@ -58,7 +58,7 @@ public class SiteTest extends AbstractUnitTest { try { //we have to create a new community in the database context.turnOffAuthorisationSystem(); - this.s = (Site) siteService.findSite(context); + this.s = siteService.findSite(context); //we need to commit the changes so we don't block the table for testing context.restoreAuthSystemState(); } catch (SQLException ex) { @@ -120,8 +120,7 @@ public class SiteTest extends AbstractUnitTest { */ @Test public void testSiteFind() throws Exception { - int id = 0; - Site found = (Site) siteService.findSite(context); + Site found = siteService.findSite(context); assertThat("testSiteFind 0", found, notNullValue()); assertThat("testSiteFind 1", found, equalTo(s)); } diff --git a/dspace-api/src/test/java/org/dspace/content/ThumbnailTest.java b/dspace-api/src/test/java/org/dspace/content/ThumbnailTest.java index 93e231da1d..454def2d25 100644 --- a/dspace-api/src/test/java/org/dspace/content/ThumbnailTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ThumbnailTest.java @@ -7,6 +7,7 @@ */ package org.dspace.content; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -48,11 +49,6 @@ public class ThumbnailTest extends AbstractUnitTest { */ private Bitstream orig; - /** - * Thumbnail instance for the tests, original copy - */ - private Thumbnail t; - /** * This method will be run before every test as per @Before. It will * initialize resources required for the tests. @@ -69,7 +65,9 @@ public class ThumbnailTest extends AbstractUnitTest { File f = new File(testProps.get("test.bitstream").toString()); thumb = bitstreamService.create(context, new FileInputStream(f)); orig = bitstreamService.create(context, new FileInputStream(f)); - t = new Thumbnail(thumb, orig); + Thumbnail t = new Thumbnail(thumb, orig); + assertEquals(orig, t.getOriginal()); + assertEquals(thumb, t.getThumb()); } catch (IOException ex) { log.error("IO Error in init", ex); fail("SQL Error in init: " + ex.getMessage()); @@ -89,9 +87,16 @@ public class ThumbnailTest extends AbstractUnitTest { @After @Override public void destroy() { - thumb = null; - orig = null; - t = null; + try { + context.turnOffAuthorisationSystem(); + bitstreamService.delete(context, thumb); + bitstreamService.delete(context, orig); + context.restoreAuthSystemState(); + thumb = null; + orig = null; + } catch (Exception e) { + throw new AssertionError("Error in destroy()", e); + } super.destroy(); } diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java index f66619f615..3662e43168 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java @@ -25,7 +25,6 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.ConfigurationManager; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.versioning.Version; @@ -49,7 +48,6 @@ public class VersioningTest extends AbstractUnitTest { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(VersioningTest.class); - private String originalHandle; private Item originalItem; private Item versionedItem; private String summary = "Unit test version"; @@ -65,7 +63,7 @@ public class VersioningTest extends AbstractUnitTest { //A regex that can be used to see if a handle contains the format of handle created by the org.dspace.identifier // .VersionedHandleIdentifierProvider* - protected String versionedHandleRegex = ConfigurationManager.getProperty("handle.prefix") + "\\/[0-9]*\\.[0-9]"; + //protected String versionedHandleRegex = ConfigurationManager.getProperty("handle.prefix") + "\\/[0-9]*\\.[0-9]"; /** * This method will be run before every test as per @Before. It will @@ -86,7 +84,6 @@ public class VersioningTest extends AbstractUnitTest { WorkspaceItem is = workspaceItemService.create(context, col, false); originalItem = installItemService.installItem(context, is); - originalHandle = originalItem.getHandle(); Version version = versionService.createNewVersion(context, originalItem, summary); WorkspaceItem wsi = workspaceItemService.findByItem(context, version.getItem()); diff --git a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java index 747530b838..77bc170d8a 100644 --- a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java @@ -160,8 +160,8 @@ public class WorkspaceItemTest extends AbstractUnitTest { // Allow Collection ADD perms doNothing().when(authorizeServiceSpy).authorizeAction(context, collection, Constants.ADD); - boolean template = false; - WorkspaceItem created = null; + boolean template; + WorkspaceItem created; template = false; created = workspaceItemService.create(context, collection, template); diff --git a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java index c6725dd17a..460522a6eb 100644 --- a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java +++ b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import com.google.common.base.Splitter; import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTest; import org.dspace.authorize.AuthorizeException; @@ -1194,9 +1195,9 @@ public class ITDSpaceAIP extends AbstractIntegrationTest { // Get the typeText & name of this object from the values String info = infoMap.get(key); - String[] values = info.split(valueseparator); - String typeText = values[0]; - String name = values[1]; + List values = Splitter.on(valueseparator).splitToList(info); + String typeText = values.get(0); + String name = values.get(1); // Also assert type and name are correct assertEquals("assertObjectsExist object " + key + " type", diff --git a/dspace-api/src/test/java/org/dspace/content/virtual/CollectedTest.java b/dspace-api/src/test/java/org/dspace/content/virtual/CollectedTest.java index 0e265c2454..4bf896cd08 100644 --- a/dspace-api/src/test/java/org/dspace/content/virtual/CollectedTest.java +++ b/dspace-api/src/test/java/org/dspace/content/virtual/CollectedTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; +import com.google.common.base.Splitter; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; @@ -85,18 +86,15 @@ public class CollectedTest { metadataValueList.add(metadataValue); String s = "dc.title"; list.add(s); - String[] splittedString = s.split("\\."); + List splittedString = Splitter.on('.').splitToList(s); collected.setFields(list); valueList.add("TestValue"); // Mock the state of objects utilized in getValues() to meet the success criteria of an invocation - when(itemService.getMetadata(item, splittedString.length > 0 ? splittedString[0] : - null, - splittedString.length > 1 ? splittedString[1] : - null, - splittedString.length > 2 ? splittedString[2] : - null, - Item.ANY, false)).thenReturn(metadataValueList); + when(itemService.getMetadata(item, splittedString.size() > 0 ? splittedString.get(0) : null, + splittedString.size() > 1 ? splittedString.get(1) : null, + splittedString.size() > 2 ? splittedString.get(2) : null, + Item.ANY, false)).thenReturn(metadataValueList); when(metadataValue.getValue()).thenReturn("TestValue"); // The reported value(s) should match our valueList diff --git a/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java b/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java index 8dd81fdc36..52457a23d7 100644 --- a/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java +++ b/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; +import com.google.common.base.Splitter; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; @@ -107,18 +108,15 @@ public class ConcatenateTest { metadataValueList.add(metadataValue); String s = "dc.title"; list.add(s); - String[] splittedString = s.split("\\."); + List splittedString = Splitter.on(".").splitToList(s); concatenate.setFields(list); valueList.add("TestValue"); // Mock the state of objects utilized in getValues() to meet the success criteria of an invocation - when(itemService.getMetadata(item, splittedString.length > 0 ? splittedString[0] : - null, - splittedString.length > 1 ? splittedString[1] : - null, - splittedString.length > 2 ? splittedString[2] : - null, - Item.ANY, false)).thenReturn(metadataValueList); + when(itemService.getMetadata(item, splittedString.size() > 0 ? splittedString.get(0) : null, + splittedString.size() > 1 ? splittedString.get(1) : null, + splittedString.size() > 2 ? splittedString.get(2) : null, + Item.ANY, false)).thenReturn(metadataValueList); when(metadataValue.getValue()).thenReturn("TestValue"); diff --git a/dspace-api/src/test/java/org/dspace/content/virtual/RelatedTest.java b/dspace-api/src/test/java/org/dspace/content/virtual/RelatedTest.java index dfa43ffefe..cb49554d21 100644 --- a/dspace-api/src/test/java/org/dspace/content/virtual/RelatedTest.java +++ b/dspace-api/src/test/java/org/dspace/content/virtual/RelatedTest.java @@ -13,7 +13,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -150,8 +149,8 @@ public class RelatedTest { assertEquals("TestGetValues 1", virtualMetadataConfiguration.getValues(context, item), related.getValues(context, item)); related.setPlace(2); - // No match should return empty LinkedList - assertEquals("TestGetValues 2", new LinkedList<>(), related.getValues(context, item)); + // No match should return empty List + assertEquals("TestGetValues 2", new ArrayList<>(), related.getValues(context, item)); } diff --git a/dspace-api/src/test/java/org/dspace/content/virtual/UUIDValueTest.java b/dspace-api/src/test/java/org/dspace/content/virtual/UUIDValueTest.java index cce6105a9a..29aa65cd2d 100644 --- a/dspace-api/src/test/java/org/dspace/content/virtual/UUIDValueTest.java +++ b/dspace-api/src/test/java/org/dspace/content/virtual/UUIDValueTest.java @@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -28,7 +28,7 @@ import org.mockito.junit.MockitoJUnitRunner; public class UUIDValueTest { @InjectMocks - private UUIDValue UUIDValue; + private UUIDValue uuidValue; @Mock private Context context; @@ -36,32 +36,32 @@ public class UUIDValueTest { @Test public void testGetValues() throws Exception { // Setup objects utilized in unit test - List list = new LinkedList<>(); + List list = new ArrayList<>(); Item item = mock(Item.class); UUID uuid = UUID.randomUUID(); when(item.getID()).thenReturn(uuid); list.add(String.valueOf(uuid)); // The reported value(s) should match our defined list - assertEquals("TestGetValues 0", list, UUIDValue.getValues(context, item)); + assertEquals("TestGetValues 0", list, uuidValue.getValues(context, item)); } @Test public void testSetUseForPlace() { // Setup objects utilized in unit test - UUIDValue.setUseForPlace(true); + uuidValue.setUseForPlace(true); // The reported boolean should return true - assertEquals("TestSetUseForPlace 0", true, UUIDValue.getUseForPlace()); + assertEquals("TestSetUseForPlace 0", true, uuidValue.getUseForPlace()); } @Test public void testGetUseForPlace() { // Setup objects utilized in unit test - UUIDValue.setUseForPlace(true); + uuidValue.setUseForPlace(true); // The reported boolean should return true - assertEquals("TestGetUseForPlace 0", true, UUIDValue.getUseForPlace()); + assertEquals("TestGetUseForPlace 0", true, uuidValue.getUseForPlace()); } } diff --git a/dspace-api/src/test/java/org/dspace/core/ContextTest.java b/dspace-api/src/test/java/org/dspace/core/ContextTest.java index fe8a2a9ff7..f5697a72dc 100644 --- a/dspace-api/src/test/java/org/dspace/core/ContextTest.java +++ b/dspace-api/src/test/java/org/dspace/core/ContextTest.java @@ -10,12 +10,15 @@ package org.dspace.core; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.Locale; @@ -84,7 +87,7 @@ public class ContextTest extends AbstractUnitTest { * Test of setCurrentUser method, of class Context. */ @Test - public void testSetCurrentUser() throws SQLException, AuthorizeException { + public void testSetCurrentUser() throws SQLException, AuthorizeException, IOException { // Allow full Admin perms when(authorizeServiceSpy.isAdmin(context)).thenReturn(true); @@ -105,6 +108,9 @@ public class ContextTest extends AbstractUnitTest { // Restore the previous current user context.setCurrentUser(oldUser); + + // Cleanup our new user + ePersonService.delete(context, newUser); } /** @@ -255,6 +261,60 @@ public class ContextTest extends AbstractUnitTest { cleanupContext(instance); } + /** + * Test of commit method, of class Context. + */ + @Test + public void testCommit() throws SQLException, AuthorizeException, IOException { + // To test commit() we need a new Context object + Context instance = new Context(); + + // By default, we should have a new DB connection, so let's make sure it is there + assertThat("HibernateDBConnection should exist", instance.getDBConnection(), notNullValue()); + assertTrue("Context should be valid", instance.isValid()); + assertTrue("Transaction should be open", instance.isTransactionAlive()); + + // Allow full Admin perms (in new context) + when(authorizeServiceSpy.isAdmin(instance)).thenReturn(true); + + // Create a new EPerson (to be committed) + String createdEmail = "myfakeemail@example.com"; + EPerson newUser = ePersonService.create(instance); + newUser.setFirstName(instance, "Tim"); + newUser.setLastName(instance, "Smith"); + newUser.setEmail(createdEmail); + newUser.setCanLogIn(true); + newUser.setLanguage(instance, I18nUtil.getDefaultLocale().getLanguage()); + + // Now, call commit() + instance.commit(); + + // We expect our DB connection to still exist + assertThat("HibernateDBConnection should still be open", instance.getDBConnection(), notNullValue()); + // We expect the Context to be valid + assertTrue("Context should still be valid", instance.isValid()); + // However, the transaction should now be closed + assertFalse("DB transaction should be closed", instance.isTransactionAlive()); + + // ReloadEntity and verify changes saved + // NOTE: reloadEntity() is required, see commit() method Javadocs + newUser = instance.reloadEntity(newUser); + assertEquals("New user should be created", newUser.getEmail(), createdEmail); + + // Change the email and commit again (a Context should support multiple commit() calls) + String newEmail = "myrealemail@example.com"; + newUser.setEmail(newEmail); + instance.commit(); + + // Reload entity and new value should be there. + newUser = instance.reloadEntity(newUser); + assertEquals("New email address should be saved", newUser.getEmail(), newEmail); + + // Cleanup our new object & context + ePersonService.delete(instance, newUser); + cleanupContext(instance); + } + /** * Test of abort method, of class Context. */ @@ -269,11 +329,11 @@ public class ContextTest extends AbstractUnitTest { // Create a new EPerson (DO NOT COMMIT IT) String createdEmail = "susie@email.com"; EPerson newUser = ePersonService.create(instance); - newUser.setFirstName(context, "Susan"); - newUser.setLastName(context, "Doe"); + newUser.setFirstName(instance, "Susan"); + newUser.setLastName(instance, "Doe"); newUser.setEmail(createdEmail); newUser.setCanLogIn(true); - newUser.setLanguage(context, I18nUtil.getDefaultLocale().getLanguage()); + newUser.setLanguage(instance, I18nUtil.getDefaultLocale().getLanguage()); // Abort our context instance.abort(); @@ -304,12 +364,11 @@ public class ContextTest extends AbstractUnitTest { // Create a new EPerson (DO NOT COMMIT IT) EPerson newUser = ePersonService.create(instance); - newUser.setFirstName(context, "Susan"); - newUser.setLastName(context, "Doe"); + newUser.setFirstName(instance, "Susan"); + newUser.setLastName(instance, "Doe"); newUser.setEmail(createdEmail); newUser.setCanLogIn(true); - newUser.setLanguage(context, I18nUtil.getDefaultLocale().getLanguage()); - + newUser.setLanguage(instance, I18nUtil.getDefaultLocale().getLanguage()); } // Open a new context, let's make sure that EPerson isn't there @@ -433,7 +492,7 @@ public class ContextTest extends AbstractUnitTest { * Test of getSpecialGroups method, of class Context. */ @Test - public void testGetSpecialGroups() throws SQLException, AuthorizeException { + public void testGetSpecialGroups() throws SQLException, AuthorizeException, IOException { // To test special groups we need a new Context object Context instance = new Context(); @@ -456,7 +515,8 @@ public class ContextTest extends AbstractUnitTest { assertThat("testGetSpecialGroup 1", specialGroups.get(0), equalTo(group)); assertThat("testGetSpecialGroup 1", specialGroups.get(1), equalTo(adminGroup)); - // Cleanup our context + // Cleanup our context & group + groupService.delete(instance, group); cleanupContext(instance); } diff --git a/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java b/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java new file mode 100644 index 0000000000..093f693d56 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java @@ -0,0 +1,229 @@ +/** + * 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.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; + +import org.dspace.AbstractUnitTest; +import org.dspace.eperson.EPerson; +import org.dspace.utils.DSpace; +import org.hibernate.Session; +import org.junit.Before; +import org.junit.Test; + +/** + * Perform some basic unit tests for HibernateDBConnection + * + * @author tdonohue + */ +public class HibernateDBConnectionTest extends AbstractUnitTest { + + private HibernateDBConnection connection; + + /** + * This method will be run before every test as per @Before. It will + * initialize resources required for the tests. + * + * Other methods can be annotated with @Before here or in subclasses + * but no execution order is guaranteed + */ + @Before + @Override + public void init() { + super.init(); + // Get a DB connection to test with + connection = new DSpace().getServiceManager() + .getServiceByName(null, HibernateDBConnection.class); + } + + /** + * Test of getSession method + */ + @Test + public void testGetSession() throws SQLException { + assertNotNull("DB connection should not be null", connection); + // Connection should begin with an active transaction + assertTrue("A transaction should be open by default", connection.getTransaction().isActive()); + + // Rollback current transaction + connection.getTransaction().rollback(); + + // Transaction should be closed + assertFalse("Transaction should be closed after rollback", connection.getTransaction().isActive()); + + //Now call getSession(), saving a reference to the session + Session currentSession = connection.getSession(); + + // New transaction should be initialized + assertTrue("New transaction should be open after getSession() call", + connection.getTransaction().isActive()); + + // Call getSession again. The same Session should still be returned + assertEquals("Multiple calls to getSession should return same Session", currentSession, + connection.getSession()); + } + + /** + * Test of isTransactionAlive method + */ + @Test + public void testIsTransactionAlive() { + assertNotNull("DB connection should not be null", connection); + assertNotNull("Transaction should not be null", connection.getTransaction()); + // Connection should begin with a transaction + assertTrue("A transaction should be open by default", connection.isTransActionAlive()); + + // Rollback current transaction + connection.getTransaction().rollback(); + + // Transaction should be closed + assertFalse("Transaction should be closed after rollback", connection.isTransActionAlive()); + } + + /** + * Test of isSessionAlive method + */ + @Test + public void testIsSessionAlive() throws SQLException { + assertNotNull("DB connection should not be null", connection); + assertNotNull("Session should not be null", connection.getSession()); + assertTrue("A Session should be alive by default", connection.isSessionAlive()); + + // Rollback current transaction, closing it + connection.getTransaction().rollback(); + + // Session should still be alive even after transaction closes + assertTrue("A Session should still be alive if transaction closes", connection.isSessionAlive()); + + // NOTE: Because we configure Hibernate Session objects to be bound to a thread + // (see 'hibernate.current_session_context_class' in hibernate.cfg.xml), a Session is ALWAYS ALIVE until + // the thread closes (at which point Hibernate will clean it up automatically). + // This means that essentially isSessionAlive() will always return true, unless the connection is severed + // in some unexpected way. See also "testCloseDBConnection()" + } + + /** + * Test of closeDBConnection method + */ + @Test + public void testCloseDBConnection() throws SQLException { + // Get a reference to the current Session + Session initialSession = connection.getSession(); + + // Close the DB connection / Session + // NOTE: Because of our Hibernate configuration, Hibernate automatically creates a new Session per thread. + // Even though we "close" the connection, Hibernate will reopen a new one immediately. So, all this actually + // does is create a *new* Session + connection.closeDBConnection(); + + Session newSession = connection.getSession(); + assertNotEquals("New Session expected",initialSession, newSession); + } + + /** + * Test of commit method + */ + @Test + public void testCommit() throws SQLException { + // Ensure a transaction exists + connection.getSession(); + assertTrue("Transaction should be active", connection.getTransaction().isActive()); + + connection.commit(); + assertFalse("Commit should close transaction", connection.getTransaction().isActive()); + + // A second commit should be a no-op (no error thrown) + connection.commit(); + } + + /** + * Test of rollback method + */ + @Test + public void testRollback() throws SQLException { + // Ensure a transaction exists + connection.getSession(); + assertTrue("Transaction should be active", connection.getTransaction().isActive()); + + connection.rollback(); + assertFalse("Rollback should close transaction", connection.getTransaction().isActive()); + + // A second rollback should be a no-op (no error thrown) + connection.rollback(); + } + + /** + * Test of reloadEntity method + */ + @Test + public void testReloadEntityAfterRollback() throws SQLException { + // Get DBConnection associated with DSpace Context + HibernateDBConnection dbConnection = (HibernateDBConnection) context.getDBConnection(); + EPerson person = context.getCurrentUser(); + + assertTrue("Current user should be cached in session", dbConnection.getSession() + .contains(person)); + + dbConnection.rollback(); + assertFalse("Current user should be gone from cache", dbConnection.getSession() + .contains(person)); + + person = dbConnection.reloadEntity(person); + assertTrue("Current user should be cached back in session", dbConnection.getSession() + .contains(person)); + } + + /** + * Test of reloadEntity method + */ + @Test + public void testReloadEntityAfterCommit() throws SQLException { + // Get DBConnection associated with DSpace Context + HibernateDBConnection dbConnection = (HibernateDBConnection) context.getDBConnection(); + EPerson person = context.getCurrentUser(); + + assertTrue("Current user should be cached in session", dbConnection.getSession() + .contains(person)); + + dbConnection.commit(); + assertFalse("Current user should be gone from cache", dbConnection.getSession() + .contains(person)); + + person = dbConnection.reloadEntity(person); + assertTrue("Current user should be cached back in session", dbConnection.getSession() + .contains(person)); + } + + /** + * Test of uncacheEntity method + */ + @Test + public void testUncacheEntity() throws SQLException { + // Get DBConnection associated with DSpace Context + HibernateDBConnection dbConnection = (HibernateDBConnection) context.getDBConnection(); + EPerson person = context.getCurrentUser(); + + assertTrue("Current user should be cached in session", dbConnection.getSession() + .contains(person)); + + dbConnection.uncacheEntity(person); + assertFalse("Current user should be gone from cache", dbConnection.getSession() + .contains(person)); + + // Test ability to reload an uncached entity + person = dbConnection.reloadEntity(person); + assertTrue("Current user should be cached back in session", dbConnection.getSession() + .contains(person)); + } +} diff --git a/dspace-api/src/test/java/org/dspace/core/PathsClassLoaderTest.java b/dspace-api/src/test/java/org/dspace/core/PathsClassLoaderTest.java index 2d3f040984..1b337a25ab 100644 --- a/dspace-api/src/test/java/org/dspace/core/PathsClassLoaderTest.java +++ b/dspace-api/src/test/java/org/dspace/core/PathsClassLoaderTest.java @@ -8,7 +8,7 @@ package org.dspace.core; import static org.apache.bcel.Const.ACC_PUBLIC; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; import java.io.File; import java.io.FileOutputStream; @@ -136,12 +136,12 @@ public class PathsClassLoaderTest { jarFile.getCanonicalPath()}; PathsClassLoader instance = new PathsClassLoader(parentCL, classpath); Class result = instance.findClass(className); - assertTrue("Should return a Class from file", result instanceof Class); + assertNotNull("Should return a Class from file", result); classpath[0] = jarFile.getCanonicalPath(); instance = new PathsClassLoader(parentCL, classpath); result = instance.findClass(jarClassName); - assertTrue("Should return a Class from JAR", result instanceof Class); + assertNotNull("Should return a Class from JAR", result); } } diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java index b8dc7b501b..e5a86f1f56 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java @@ -8,8 +8,8 @@ package org.dspace.external.provider.impl; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -28,10 +28,12 @@ public class MockDataProvider implements ExternalDataProvider { * Generic getter for the sourceIdentifier * @return the sourceIdentifier value of this MockDataProvider */ + @Override public String getSourceIdentifier() { return sourceIdentifier; } + @Override public Optional getExternalDataObject(String id) { ExternalDataObject externalDataObject = mockLookupMap.get(id); if (externalDataObject == null) { @@ -41,8 +43,9 @@ public class MockDataProvider implements ExternalDataProvider { } } + @Override public List searchExternalDataObjects(String query, int start, int limit) { - List listToReturn = new LinkedList<>(); + List listToReturn = new ArrayList<>(); for (Map.Entry entry : mockLookupMap.entrySet()) { if (StringUtils.containsIgnoreCase(entry.getKey(), query)) { listToReturn.add(entry.getValue()); @@ -72,7 +75,7 @@ public class MockDataProvider implements ExternalDataProvider { public void init() throws IOException { mockLookupMap = new HashMap<>(); - List externalDataObjectsToMake = new LinkedList<>(); + List externalDataObjectsToMake = new ArrayList<>(); externalDataObjectsToMake.add("one"); externalDataObjectsToMake.add("two"); externalDataObjectsToMake.add("three"); @@ -83,7 +86,7 @@ public class MockDataProvider implements ExternalDataProvider { externalDataObject.setId(id); externalDataObject.setValue(id); externalDataObject.setDisplayValue(id); - List list = new LinkedList<>(); + List list = new ArrayList<>(); list.add(new MetadataValueDTO("dc", "contributor", "author", null, "Donald, Smith")); externalDataObject.setMetadata(list); diff --git a/dspace-api/src/test/java/org/dspace/handle/dao/impl/HandleDAOImplTest.java b/dspace-api/src/test/java/org/dspace/handle/dao/impl/HandleDAOImplTest.java index 1560a5d04c..938dab44b0 100644 --- a/dspace-api/src/test/java/org/dspace/handle/dao/impl/HandleDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/handle/dao/impl/HandleDAOImplTest.java @@ -128,12 +128,8 @@ public class HandleDAOImplTest extends AbstractUnitTest { owningCommunity = context.reloadEntity(owningCommunity); ContentServiceFactory.getInstance().getCommunityService().delete(context, owningCommunity); owningCommunity = null; - } catch (SQLException e) { - e.printStackTrace(); - } catch (AuthorizeException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + } catch (Exception e) { + throw new AssertionError("Error occurred in destroy()", e); } item1 = null; item2 = null; diff --git a/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.java b/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.java index 4e407d2e2e..e9242c6a9a 100644 --- a/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.java +++ b/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.java @@ -11,7 +11,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import org.dspace.AbstractUnitTest; @@ -121,7 +121,7 @@ public class DSpaceCommandLineParameterTest extends AbstractUnitTest { String value3 = null; DSpaceCommandLineParameter dSpaceCommandLineParameter3 = new DSpaceCommandLineParameter(key3, value3); - List dSpaceCommandLineParameterList = new LinkedList<>(); + List dSpaceCommandLineParameterList = new ArrayList<>(); dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter); dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter1); dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter2); diff --git a/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java b/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java index b255db3e04..61325c652c 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java @@ -11,11 +11,11 @@ package org.dspace.statistics.util; import java.io.BufferedReader; import java.io.IOException; import java.security.Principal; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -114,7 +114,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { * @param headerValue The value of the header */ public void addHeader(String headerName, String headerValue) { - List values = headers.computeIfAbsent(headerName, k -> new LinkedList<>()); + List values = headers.computeIfAbsent(headerName, k -> new ArrayList<>()); values.add(headerValue); } /* (non-Javadoc) @@ -292,6 +292,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl() */ @Override + @Deprecated public boolean isRequestedSessionIdFromUrl() { // TODO Auto-generated method stub return false; @@ -502,6 +503,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { * @see javax.servlet.ServletRequest#getRealPath(java.lang.String) */ @Override + @Deprecated public String getRealPath(String arg0) { // TODO Auto-generated method stub return null; diff --git a/dspace-api/src/test/java/org/dspace/workflowbasic/BasicWorkflowAuthorizationIT.java b/dspace-api/src/test/java/org/dspace/workflowbasic/BasicWorkflowAuthorizationIT.java index f4c5eb615e..7164204d25 100644 --- a/dspace-api/src/test/java/org/dspace/workflowbasic/BasicWorkflowAuthorizationIT.java +++ b/dspace-api/src/test/java/org/dspace/workflowbasic/BasicWorkflowAuthorizationIT.java @@ -271,7 +271,7 @@ public class BasicWorkflowAuthorizationIT Item item = wsi.getItem(); Bundle bundle = bundleService.create(context, item, "ORIGINAL"); File f = new File(AbstractDSpaceTest.testProps.get("test.bitstream").toString()); - Bitstream bs = bitstreamService.create(context, bundle, new FileInputStream(f)); + bitstreamService.create(context, bundle, new FileInputStream(f)); bundleService.update(context, bundle); itemService.update(context, item); workspaceItemService.update(context, wsi); @@ -323,7 +323,7 @@ public class BasicWorkflowAuthorizationIT Item item = wsi.getItem(); Bundle bundle = bundleService.create(context, item, "ORIGINAL"); File f = new File(AbstractDSpaceTest.testProps.get("test.bitstream").toString()); - Bitstream bs = bitstreamService.create(context, bundle, new FileInputStream(f)); + bitstreamService.create(context, bundle, new FileInputStream(f)); bundleService.update(context, bundle); itemService.update(context, item); workspaceItemService.update(context, wsi); @@ -365,7 +365,7 @@ public class BasicWorkflowAuthorizationIT item.setSubmitter(submitter); Bundle bundle = bundleService.create(context, item, "ORIGINAL"); File f = new File(AbstractDSpaceTest.testProps.get("test.bitstream").toString()); - Bitstream bs = bitstreamService.create(context, bundle, new FileInputStream(f)); + bitstreamService.create(context, bundle, new FileInputStream(f)); bundleService.update(context, bundle); itemService.update(context, item); workspaceItemService.update(context, wsi); diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java index 83c23e1e8d..262804f12b 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java @@ -31,49 +31,48 @@ public class RoleTest extends AbstractUnitTest { @Test public void defaultWorkflow_RoleReviewer() { Role role = defaultWorkflow.getRoles().get("Reviewer"); - assertEquals(role.getDescription(), - "The people responsible for this step are able to edit the metadata of incoming submissions, " + - "and then accept or reject them."); - assertEquals(role.getName(), "Reviewer"); - assertEquals(role.getScope(), Role.Scope.COLLECTION); + assertEquals("The people responsible for this step are able to edit the metadata of incoming submissions, " + + "and then accept or reject them.", role.getDescription()); + assertEquals("Reviewer", role.getName()); + assertEquals(Role.Scope.COLLECTION, role.getScope()); } @Test public void defaultWorkflow_RoleEditor() { Role role = defaultWorkflow.getRoles().get("Editor"); - assertEquals(role.getDescription(), "The people responsible for this step are able to edit the " + - "metadata of incoming submissions, and then accept or reject them."); - assertEquals(role.getName(), "Editor"); - assertEquals(role.getScope(), Role.Scope.COLLECTION); + assertEquals("The people responsible for this step are able to edit the " + + "metadata of incoming submissions, and then accept or reject them.", role.getDescription()); + assertEquals("Editor", role.getName()); + assertEquals(Role.Scope.COLLECTION, role.getScope()); } @Test public void defaultWorkflow_RoleFinalEditor() { Role role = defaultWorkflow.getRoles().get("Final Editor"); - assertEquals(role.getDescription(), "The people responsible for this step are able to edit the " + - "metadata of incoming submissions, but will not be able to reject them."); - assertEquals(role.getName(), "Final Editor"); - assertEquals(role.getScope(), Role.Scope.COLLECTION); + assertEquals("The people responsible for this step are able to edit the " + + "metadata of incoming submissions, but will not be able to reject them.", role.getDescription()); + assertEquals("Final Editor", role.getName()); + assertEquals(Role.Scope.COLLECTION, role.getScope()); } @Test public void selectSingleReviewer_RoleReviewManagers() { Role role = selectSingleReviewer.getRoles().get("ReviewManagers"); - assertEquals(role.getName(), "ReviewManagers"); - assertEquals(role.getScope(), Role.Scope.REPOSITORY); + assertEquals("ReviewManagers", role.getName()); + assertEquals(Role.Scope.REPOSITORY, role.getScope()); } @Test public void selectSingleReviewer_RoleReviewer() { Role role = selectSingleReviewer.getRoles().get("Reviewer"); - assertEquals(role.getName(), "Reviewer"); - assertEquals(role.getScope(), Role.Scope.ITEM); + assertEquals("Reviewer", role.getName()); + assertEquals(Role.Scope.ITEM, role.getScope()); } @Test public void scoreReview_RoleScoreReviewers() { Role role = scoreReview.getRoles().get("ScoreReviewers"); - assertEquals(role.getName(), "ScoreReviewers"); - assertEquals(role.getScope(), Role.Scope.COLLECTION); + assertEquals("ScoreReviewers", role.getName()); + assertEquals(Role.Scope.COLLECTION, role.getScope()); } } diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java index a2e94c7a64..676285a2b2 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java @@ -25,6 +25,7 @@ import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Workflow; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -42,6 +43,8 @@ public class XmlWorkflowFactoryTest extends AbstractUnitTest { = new DSpace().getServiceManager().getServiceByName("xmlWorkflowFactory", XmlWorkflowFactoryImpl.class); private Community owningCommunity; + private Collection mappedCollection; + private Collection nonMappedCollection; /** * log4j category @@ -63,6 +66,9 @@ public class XmlWorkflowFactoryTest extends AbstractUnitTest { //we have to create a new community in the database context.turnOffAuthorisationSystem(); this.owningCommunity = communityService.create(null, context); + this.mappedCollection = + this.collectionService.create(context, owningCommunity, "123456789/workflow-test-1"); + this.nonMappedCollection = this.collectionService.create(context, owningCommunity, "123456789/999"); //we need to commit the changes so we don't block the table for testing context.restoreAuthSystemState(); } catch (SQLException e) { @@ -74,38 +80,46 @@ public class XmlWorkflowFactoryTest extends AbstractUnitTest { } } + /** + * This method will be run after every test as per @After. It will + * clean resources initialized by the @Before methods. + * + * Other methods can be annotated with @After here or in subclasses + * but no execution order is guaranteed + */ + @After + @Override + public void destroy() { + context.turnOffAuthorisationSystem(); + + try { + this.collectionService.delete(context, this.nonMappedCollection); + this.collectionService.delete(context, this.mappedCollection); + this.communityService.delete(context, this.owningCommunity); + } catch (Exception e) { + log.error("Error in destroy", e); + } + + context.restoreAuthSystemState(); + this.owningCommunity = null; + this.nonMappedCollection = null; + this.mappedCollection = null; + try { + super.destroy(); + } catch (Exception e) { + log.error("Error in destroy", e); + } + } + @Test public void workflowMapping_NonMappedCollection() throws WorkflowConfigurationException { - Collection collection = this.findOrCreateCollectionWithHandle("123456789/6"); - Workflow workflow = xmlWorkflowFactory.getWorkflow(collection); + Workflow workflow = xmlWorkflowFactory.getWorkflow(this.nonMappedCollection); assertEquals(workflow.getID(), "defaultWorkflow"); } @Test public void workflowMapping_MappedCollection() throws WorkflowConfigurationException { - Collection collection = this.findOrCreateCollectionWithHandle("123456789/4"); - Workflow workflow = xmlWorkflowFactory.getWorkflow(collection); + Workflow workflow = xmlWorkflowFactory.getWorkflow(this.mappedCollection); assertEquals(workflow.getID(), "selectSingleReviewer"); } - - private Collection findOrCreateCollectionWithHandle(String handle) { - try { - context.turnOffAuthorisationSystem(); - for (Collection collection : this.collectionService.findAll(context)) { - if (collection.getHandle().equalsIgnoreCase(handle)) { - return collection; - } - } - Collection collection = this.collectionService.create(context, owningCommunity, handle); - context.restoreAuthSystemState(); - return collection; - } catch (SQLException e) { - log.error("SQL Error in findOrCreateCollectionWithHandle", e); - fail("SQL Error in findOrCreateCollectionWithHandle: " + e.getMessage()); - } catch (AuthorizeException e) { - log.error("Authorization Error in findOrCreateCollectionWithHandle", e); - fail("Authorization Error in findOrCreateCollectionWithHandle: " + e.getMessage()); - } - return null; - } } diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/state/StepTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/state/StepTest.java index b14210b432..def0239e68 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/state/StepTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/state/StepTest.java @@ -7,8 +7,9 @@ */ package org.dspace.xmlworkflow.state; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.util.List; @@ -35,70 +36,70 @@ public class StepTest extends AbstractUnitTest { @Test public void defaultWorkflow_ReviewStep() throws WorkflowConfigurationException { Step step = defaultWorkflow.getStep("reviewstep"); - assertEquals(step.getUserSelectionMethod().getId(), "claimaction"); - assertEquals(step.getRole().getName(), "Reviewer"); + assertEquals("claimaction", step.getUserSelectionMethod().getId()); + assertEquals("Reviewer", step.getRole().getName()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "reviewaction")); - assertEquals(step.getNextStep(0).getId(), "editstep"); + assertTrue(this.containsActionNamed(actions, "reviewaction")); + assertEquals("editstep", step.getNextStep(0).getId()); } @Test public void defaultWorkflow_EditStep() throws WorkflowConfigurationException { Step step = defaultWorkflow.getStep("editstep"); - assertEquals(step.getUserSelectionMethod().getId(), "claimaction"); - assertEquals(step.getRole().getName(), "Editor"); + assertEquals("claimaction", step.getUserSelectionMethod().getId()); + assertEquals("Editor", step.getRole().getName()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "editaction")); - assertEquals(step.getNextStep(0).getId(), "finaleditstep"); + assertTrue(this.containsActionNamed(actions, "editaction")); + assertEquals("finaleditstep", step.getNextStep(0).getId()); } @Test public void defaultWorkflow_FinalEditStep() throws WorkflowConfigurationException { Step step = defaultWorkflow.getStep("finaleditstep"); - assertEquals(step.getUserSelectionMethod().getId(), "claimaction"); - assertEquals(step.getRole().getName(), "Final Editor"); + assertEquals("claimaction", step.getUserSelectionMethod().getId()); + assertEquals("Final Editor", step.getRole().getName()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "finaleditaction")); + assertTrue(this.containsActionNamed(actions, "finaleditaction")); assertNull(step.getNextStep(0)); } @Test public void selectSingleReviewer_SelectReviewerStep() throws WorkflowConfigurationException { Step step = selectSingleReviewer.getStep("selectReviewerStep"); - assertEquals(step.getUserSelectionMethod().getId(), "claimaction"); - assertEquals(step.getRole().getName(), "ReviewManagers"); + assertEquals("claimaction", step.getUserSelectionMethod().getId()); + assertEquals("ReviewManagers", step.getRole().getName()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "selectrevieweraction")); - assertEquals(step.getNextStep(0).getId(), "singleUserReviewStep"); + assertTrue(this.containsActionNamed(actions, "selectrevieweraction")); + assertEquals("singleUserReviewStep", step.getNextStep(0).getId()); } @Test public void selectSingleReviewer_SingleUserReviewStep() throws WorkflowConfigurationException { Step step = selectSingleReviewer.getStep("singleUserReviewStep"); - assertEquals(step.getUserSelectionMethod().getId(), "autoassignAction"); - assert (step.getRole().getName().equals("Reviewer")); + assertEquals("autoassignAction", step.getUserSelectionMethod().getId()); + assertEquals("Reviewer", step.getRole().getName()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "singleuserreviewaction")); - assertEquals(step.getNextStep(1).getId(), "selectReviewerStep"); + assertTrue(this.containsActionNamed(actions, "singleuserreviewaction")); + assertEquals("selectReviewerStep", step.getNextStep(1).getId()); } @Test public void scoreReview_ScoreReviewStep() throws WorkflowConfigurationException { Step step = scoreReview.getStep("scoreReviewStep"); - assertEquals(step.getUserSelectionMethod().getId(), "claimaction"); - assertEquals(step.getRole().getName(), "ScoreReviewers"); + assertEquals("claimaction", step.getUserSelectionMethod().getId()); + assertEquals("ScoreReviewers", step.getRole().getName()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "scorereviewaction")); - assertEquals(step.getNextStep(0).getId(), "evaluationStep"); - assertEquals(step.getRequiredUsers(), 2); + assertTrue(this.containsActionNamed(actions, "scorereviewaction")); + assertEquals("evaluationStep", step.getNextStep(0).getId()); + assertEquals(2, step.getRequiredUsers()); } @Test public void scoreReview_EvaluationStep() throws WorkflowConfigurationException { Step step = scoreReview.getStep("evaluationStep"); - assertEquals(step.getUserSelectionMethod().getId(), "noUserSelectionAction"); + assertEquals("noUserSelectionAction", step.getUserSelectionMethod().getId()); List actions = step.getActions(); - assert (this.containsActionNamed(actions, "evaluationaction")); + assertTrue(this.containsActionNamed(actions, "evaluationaction")); assertNull(step.getNextStep(0)); } diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/state/WorkflowTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/state/WorkflowTest.java index dc988696f4..85182a6440 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/state/WorkflowTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/state/WorkflowTest.java @@ -7,7 +7,8 @@ */ package org.dspace.xmlworkflow.state; -import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.List; @@ -31,30 +32,30 @@ public class WorkflowTest extends AbstractUnitTest { @Test public void defaultWorkflow() { - assertEquals(defaultWorkflow.getFirstStep().getId(), "reviewstep"); + assertEquals("reviewstep", defaultWorkflow.getFirstStep().getId()); List steps = defaultWorkflow.getSteps(); - assertEquals(steps.size(), 3); - assert (this.containsStepNamed(steps, "reviewstep")); - assert (this.containsStepNamed(steps, "editstep")); - assert (this.containsStepNamed(steps, "finaleditstep")); + assertEquals(3, steps.size()); + assertTrue(this.containsStepNamed(steps, "reviewstep")); + assertTrue(this.containsStepNamed(steps, "editstep")); + assertTrue(this.containsStepNamed(steps, "finaleditstep")); } @Test public void selectSingleReviewer() { - assertEquals(selectSingleReviewer.getFirstStep().getId(), "selectReviewerStep"); + assertEquals("selectReviewerStep", selectSingleReviewer.getFirstStep().getId()); List steps = selectSingleReviewer.getSteps(); - assertEquals(steps.size(), 2); - assert (this.containsStepNamed(steps, "selectReviewerStep")); - assert (this.containsStepNamed(steps, "singleUserReviewStep")); + assertEquals(2, steps.size()); + assertTrue(this.containsStepNamed(steps, "selectReviewerStep")); + assertTrue(this.containsStepNamed(steps, "singleUserReviewStep")); } @Test public void scoreReview() { - assertEquals(scoreReview.getFirstStep().getId(), "scoreReviewStep"); + assertEquals("scoreReviewStep", scoreReview.getFirstStep().getId()); List steps = scoreReview.getSteps(); - assertEquals(steps.size(), 2); - assert (this.containsStepNamed(steps, "scoreReviewStep")); - assert (this.containsStepNamed(steps, "evaluationStep")); + assertEquals(2, steps.size()); + assertTrue(this.containsStepNamed(steps, "scoreReviewStep")); + assertTrue(this.containsStepNamed(steps, "evaluationStep")); } private boolean containsStepNamed(List steps, String stepName) { diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 183bf62fd7..4caec1c3b2 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -15,7 +15,7 @@ ${basedir}/.. - 3.2.11 + 3.3.0 5.87.0.RELEASE @@ -89,7 +89,7 @@ org.apache.commons commons-lang3 - + log4j log4j @@ -103,6 +103,11 @@ org.codehaus.woodstox wstx-asl + + + org.dom4j + dom4j + diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java index 1738373420..d668261a3c 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java @@ -22,7 +22,7 @@ import org.dspace.core.ConfigurationManager; public class DSpaceResourceResolver implements ResourceResolver { private static final TransformerFactory transformerFactory = TransformerFactory - .newInstance(); + .newInstance("net.sf.saxon.TransformerFactoryImpl", null); private final String basePath = ConfigurationManager.getProperty("oai", "config.dir"); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java index 6cdd59bd75..c544ec1659 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java @@ -26,11 +26,12 @@ public class DSpaceSolrServerResolver implements SolrServerResolver { @Override public SolrClient getServer() throws SolrServerException { if (server == null) { + String serverUrl = configurationService.getProperty("oai.solr.url"); try { - server = new HttpSolrClient.Builder(configurationService.getProperty("oai", "solr.url")).build(); - log.debug("Solr Server Initialized"); + server = new HttpSolrClient.Builder(serverUrl).build(); + log.debug("OAI Solr Server Initialized"); } catch (Exception e) { - log.error(e.getMessage(), e); + log.error("Could not initialize OAI Solr Server at " + serverUrl , e); } } return server; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java b/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java index 3c92ea667e..99c071c6b9 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java @@ -30,12 +30,12 @@ public class DSpaceSolrServer { public static SolrClient getServer() throws SolrServerException { if (_server == null) { + String serverUrl = ConfigurationManager.getProperty("oai.solr.url"); try { - _server = new HttpSolrClient.Builder( - ConfigurationManager.getProperty("oai", "solr.url")).build(); - log.debug("Solr Server Initialized"); + _server = new HttpSolrClient.Builder(serverUrl).build(); + log.debug("OAI Solr Server Initialized"); } catch (Exception e) { - log.error(e.getMessage(), e); + log.error("Could not initialize OAI Solr Server at " + serverUrl , e); } } return _server; diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/AbstractXSLTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/AbstractXSLTest.java index 2262c1bbe8..6fab56b526 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/AbstractXSLTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/AbstractXSLTest.java @@ -19,7 +19,8 @@ import javax.xml.transform.stream.StreamSource; import org.apache.commons.io.IOUtils; public abstract class AbstractXSLTest { - private static final TransformerFactory factory = TransformerFactory.newInstance(); + private static final TransformerFactory factory = TransformerFactory + .newInstance("net.sf.saxon.TransformerFactoryImpl", null); protected TransformBuilder apply(String xslLocation) throws Exception { return new TransformBuilder(xslLocation); diff --git a/dspace-rest/README.md b/dspace-rest/README.md index 353b249931..07d71d66ed 100644 --- a/dspace-rest/README.md +++ b/dspace-rest/README.md @@ -1,7 +1,9 @@ -#DSpace REST API (Jersey) +#DSpace REST API (Jersey) - DEPRECATED A RESTful web services API for DSpace, built using JAX-RS1 JERSEY. +_This REST API has been deprecated and will be removed in v8. Please use the Server API (/server) webapp instead._ + ##Getting Started This REST API is integrated directly into the DSpace codebase. diff --git a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java b/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java index c09e924536..26b1150229 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java +++ b/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java @@ -59,7 +59,9 @@ public class RestIndex { // TODO Better graphics, add arguments to all methods. (limit, offset, item and so on) return "DSpace REST - index" + "" - + "

DSpace REST API

" + + + "

DSpace REST API (Deprecated)

" + + "This REST API is deprecated and will be removed in v8." + + " Please use the new Server API webapp instead.
" + "Server path: " + servletContext.getContextPath() + "

Index

" + "
    " + diff --git a/dspace-rest/src/main/webapp/WEB-INF/web.xml b/dspace-rest/src/main/webapp/WEB-INF/web.xml index 1b33aac885..34d74d9630 100644 --- a/dspace-rest/src/main/webapp/WEB-INF/web.xml +++ b/dspace-rest/src/main/webapp/WEB-INF/web.xml @@ -35,7 +35,7 @@ - DSpace REST API + DSpace REST API (Deprecated) org.glassfish.jersey.servlet.ServletContainer @@ -47,7 +47,7 @@ - DSpace REST API + DSpace REST API (Deprecated) /* @@ -59,7 +59,7 @@ - DSpace REST API + DSpace REST API (Deprecated) /* diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index ba2c66c734..7149996d4d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -27,7 +27,6 @@ import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.hateoas.BitstreamResource; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.MultipartFileSender; import org.dspace.app.rest.utils.Utils; @@ -243,7 +242,7 @@ public class BitstreamRestController { context.commit(); - BitstreamRest bitstreamRest = converter.toRest(context.reloadEntity(bitstream), Projection.DEFAULT); + BitstreamRest bitstreamRest = converter.toRest(context.reloadEntity(bitstream), utils.obtainProjection()); return converter.toResource(bitstreamRest); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java new file mode 100644 index 0000000000..db545736f1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java @@ -0,0 +1,311 @@ +/** + * 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 java.util.regex.Pattern.compile; +import static org.apache.http.HttpStatus.SC_NO_CONTENT; +import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY; +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_UUID; +import static org.dspace.app.util.AuthorizeUtil.authorizeManageAdminGroup; +import static org.dspace.app.util.AuthorizeUtil.authorizeManageSubmittersGroup; +import static org.dspace.app.util.AuthorizeUtil.authorizeManageWorkflowsGroup; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.utils.GroupUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; +import org.dspace.xmlworkflow.storedcomponents.CollectionRole; +import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * This will be the entry point for the api/eperson/groups endpoint with additional paths to it + */ +@RestController +@RequestMapping("/api/" + GroupRest.CATEGORY + "/" + GroupRest.GROUPS) +public class GroupRestController { + + @Autowired + private GroupService groupService; + + @Autowired + private EPersonService ePersonService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private CollectionRoleService collectionRoleService; + + @Autowired + Utils utils; + + @Autowired + GroupUtil groupUtil; + + /** + * Method to add one or more subgroups to a group. + * The subgroups to be added should be provided in the request body as a uri-list. + * Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by + * using the 'checkAuthorization' method. + * + * @param uuid the uuid of the group to add the subgroups to + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = POST, path = "/{uuid}/subgroups", consumes = {"text/uri-list"}) + public void addChildGroups(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = obtainContext(request); + + Group parentGroup = groupService.find(context, uuid); + if (parentGroup == null) { + throw new ResourceNotFoundException("parent group is not found for uuid: " + uuid); + } + + checkAuthorization(context, parentGroup); + + List groupLinks = utils.getStringListFromRequest(request); + + List childGroups = new ArrayList<>(); + for (String groupLink : groupLinks) { + Optional childGroup = findGroup(context, groupLink); + if (!childGroup.isPresent() || !canAddGroup(context, parentGroup, childGroup.get())) { + throw new UnprocessableEntityException("cannot add child group: " + groupLink); + } + childGroups.add(childGroup.get()); + } + + for (Group childGroup : childGroups) { + groupService.addMember(context, parentGroup, childGroup); + } + + context.complete(); + + response.setStatus(SC_NO_CONTENT); + } + + private Optional findGroup(Context context, String groupLink) throws SQLException { + + Group group = null; + + Pattern linkPattern = compile("^.*/(" + REGEX_UUID + ")/?$"); + Matcher matcher = linkPattern.matcher(groupLink); + if (matcher.matches()) { + group = groupService.find(context, UUID.fromString(matcher.group(1))); + } + + return Optional.ofNullable(group); + } + + private boolean canAddGroup(Context context, Group parentGroup, Group childGroup) throws SQLException { + + return !groupService.isParentOf(context, childGroup, parentGroup); + } + + /** + * Method to add one or more members to a group. + * The members to be added should be provided in the request body as a uri-list. + * Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by + * using the 'checkAuthorization' method. + * + * @param uuid the uuid of the group to add the members to + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = POST, path = "/{uuid}/epersons", consumes = {"text/uri-list"}) + public void addMembers(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = obtainContext(request); + + Group parentGroup = groupService.find(context, uuid); + if (parentGroup == null) { + throw new ResourceNotFoundException("parent group is not found for uuid: " + uuid); + } + + checkAuthorization(context, parentGroup); + + List memberLinks = utils.getStringListFromRequest(request); + + List members = new ArrayList<>(); + for (String memberLink : memberLinks) { + Optional member = findEPerson(context, memberLink); + if (!member.isPresent()) { + throw new UnprocessableEntityException("cannot add child group: " + memberLink); + } + members.add(member.get()); + } + + for (EPerson member : members) { + groupService.addMember(context, parentGroup, member); + } + + context.complete(); + + response.setStatus(SC_NO_CONTENT); + } + + private Optional findEPerson(Context context, String groupLink) throws SQLException { + + EPerson ePerson = null; + + Pattern linkPattern = compile("^.*/(" + REGEX_UUID + ")/?$"); + Matcher matcher = linkPattern.matcher(groupLink); + if (matcher.matches()) { + ePerson = ePersonService.find(context, UUID.fromString(matcher.group(1))); + } + + return Optional.ofNullable(ePerson); + } + + /** + * Method to remove a subgroup from a group. + * Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by + * using the 'checkAuthorization' method. + * + * @param parentUUID the uuid of the parent group + * @param childUUID the uuid of the subgroup which has to be removed + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = DELETE, path = "/{parentUUID}/subgroups/{childUUID}") + public void removeChildGroup(@PathVariable UUID parentUUID, @PathVariable UUID childUUID, + HttpServletResponse response, HttpServletRequest request) + throws IOException, SQLException, AuthorizeException { + + Context context = obtainContext(request); + + Group parentGroup = groupService.find(context, parentUUID); + if (parentGroup == null) { + throw new ResourceNotFoundException("parent group is not found for uuid: " + parentUUID); + } + + checkAuthorization(context, parentGroup); + + Group childGroup = groupService.find(context, childUUID); + if (childGroup == null) { + response.sendError(SC_UNPROCESSABLE_ENTITY); + } + + groupService.removeMember(context, parentGroup, childGroup); + + context.complete(); + + response.setStatus(SC_NO_CONTENT); + } + + /** + * Method to remove a member from a group. + * Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by + * using the 'checkAuthorization' method. + * + * @param parentUUID the uuid of the parent group + * @param memberUUID the uuid of the member which has to be removed + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = DELETE, path = "/{parentUUID}/epersons/{memberUUID}") + public void removeMember(@PathVariable UUID parentUUID, @PathVariable UUID memberUUID, + HttpServletResponse response, HttpServletRequest request) + throws IOException, SQLException, AuthorizeException { + + Context context = obtainContext(request); + + Group parentGroup = groupService.find(context, parentUUID); + if (parentGroup == null) { + throw new ResourceNotFoundException("parent group is not found for uuid: " + parentUUID); + } + + checkAuthorization(context, parentGroup); + + EPerson childGroup = ePersonService.find(context, memberUUID); + if (childGroup == null) { + response.sendError(SC_UNPROCESSABLE_ENTITY); + } + + groupService.removeMember(context, parentGroup, childGroup); + + context.complete(); + + response.setStatus(SC_NO_CONTENT); + } + + /** + * This method checks whether the current user has sufficient rights to modify the group. + * Depending on the kind of group and due to delegated administration, separate checks need to be done to verify + * whether the user is allowed to modify the group. + * + * @param context the context of which the user will be checked + * @param group the group to be checked + * @throws SQLException + * @throws AuthorizeException + */ + private void checkAuthorization(Context context, Group group) throws SQLException, AuthorizeException { + + if (authorizeService.isAdmin(context)) { + return; + } + + Collection collection = groupUtil.getCollection(context, group); + if (collection != null) { + + if (group.equals(collection.getSubmitters())) { + authorizeManageSubmittersGroup(context, collection); + return; + } + + + List collectionRoles = collectionRoleService.findByCollection(context, collection); + for (CollectionRole role : collectionRoles) { + if (group.equals(role.getGroup())) { + authorizeManageWorkflowsGroup(context, collection); + return; + } + } + + if (group.equals(collection.getAdministrators())) { + authorizeManageAdminGroup(context, collection); + return; + } + } + + Community community = groupUtil.getCommunity(context, group); + if (community != null) { + authorizeManageAdminGroup(context, community); + return; + } + + throw new AuthorizeException("not authorized to manage this group"); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java index 0532ff2d29..12e8e057f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java @@ -22,7 +22,6 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.hateoas.BundleResource; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.ItemRestRepository; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; @@ -108,7 +107,7 @@ public class ItemAddBundleController { } Bundle bundle = itemRestRepository.addBundleToItem(context, item, bundleRest); - BundleResource bundleResource = converter.toResource(converter.toRest(bundle, Projection.DEFAULT)); + BundleResource bundleResource = converter.toResource(converter.toRest(bundle, utils.obtainProjection())); return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), bundleResource); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java index 67aca95c10..b06360ee1d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java @@ -21,7 +21,6 @@ import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CollectionRest; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; @@ -97,7 +96,7 @@ public class ItemOwningCollectionUpdateRestController { if (targetCollection == null) { return null; } - return converter.toRest(targetCollection, Projection.DEFAULT); + return converter.toRest(targetCollection, utils.obtainProjection()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index ec36721fc0..bca634cb7d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -580,11 +580,10 @@ public class RestResourceController implements InitializingBean { MultipartFile uploadfile) { checkModelPluralForm(apiCategory, model); DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); - RestAddressableModel modelObject = null; try { modelObject = repository.upload(request, apiCategory, model, id, uploadfile); - } catch (Exception e) { + } catch (SQLException e) { log.error(e.getMessage(), e); return ControllerUtils.toEmptyResponse(HttpStatus.INTERNAL_SERVER_ERROR); } @@ -910,7 +909,6 @@ public class RestResourceController implements InitializingBean { Pageable page, PagedResourcesAssembler assembler, HttpServletResponse response) { - DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); Link link = linkTo(methodOn(this.getClass(), apiCategory, model).findAll(apiCategory, model, page, assembler, response)) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java new file mode 100644 index 0000000000..9496e32738 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java @@ -0,0 +1,82 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.WorkflowDefinitionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; +import org.dspace.app.rest.repository.LinkRestRepository; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Collection; +import org.dspace.core.Context; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +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 "collections" subresource of an individual workflow definition. + * + * @author Maria Verdonck (Atmire) on 11/12/2019 + */ +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME + "." + + WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO) +public class WorkflowDefinitionCollectionsLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + @Autowired + protected ConverterService converter; + + @Autowired + protected Utils utils; + + /** + * GET endpoint that returns the list of collections that make an explicit use of the workflow-definition. + * If a collection doesn't specify the workflow-definition to be used, the default mapping applies, + * but this collection is not included in the list returned by this method. + * + * @param request The request object + * @param workflowName Name of workflow we want the collections of that are mapped to is + * @return List of collections mapped to the requested workflow + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page getCollections(@Nullable HttpServletRequest request, + String workflowName, + @Nullable Pageable optionalPageable, + Projection projection) { + if (xmlWorkflowFactory.workflowByThisNameExists(workflowName)) { + Context context = obtainContext(); + List collectionsMappedToWorkflow = new ArrayList<>(); + if (xmlWorkflowFactory.isDefaultWorkflow(workflowName)) { + collectionsMappedToWorkflow.addAll(xmlWorkflowFactory.getAllNonMappedCollectionsHandles(context)); + } + collectionsMappedToWorkflow.addAll(xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(context, + workflowName)); + Pageable pageable = optionalPageable != null ? optionalPageable : new PageRequest(0, 20); + return converter.toRestPage(utils.getPage(collectionsMappedToWorkflow, pageable), + projection); + } else { + throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java new file mode 100644 index 0000000000..fe05a4c1d0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java @@ -0,0 +1,63 @@ +/** + * 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 java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.WorkflowDefinitionRest; +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; +import org.dspace.app.rest.repository.LinkRestRepository; +import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Step; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +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 "steps" subresource of an individual workflow definition. + * + * @author Maria Verdonck (Atmire) on 24/02/2020 + */ +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME + "." + + WorkflowDefinitionRest.STEPS) +public class WorkflowDefinitionStepsLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + /** + * GET endpoint that returns the list of steps of a workflow-definition. + * + * @param request The request object + * @param workflowName Name of workflow we want the steps from + * @return List of steps of the requested workflow + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page getSteps(@Nullable HttpServletRequest request, + String workflowName, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + List steps = xmlWorkflowFactory.getWorkflowByName(workflowName).getSteps(); + Pageable pageable = optionalPageable != null ? optionalPageable : new PageRequest(0, 20); + return converter.toRestPage(utils.getPage(steps, pageable), projection); + } catch (WorkflowConfigurationException e) { + throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java new file mode 100644 index 0000000000..b11dd929d5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.WorkflowActionRest; +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; +import org.dspace.app.rest.repository.LinkRestRepository; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "actions" subresource of an individual workflow step. + * + * @author Maria Verdonck (Atmire) on 24/02/2020 + */ +@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.NAME + "." + + WorkflowStepRest.ACTIONS) +public class WorkflowStepActionsLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + /** + * GET endpoint that returns the list of actions of a workflow step. + * + * @param request The request object + * @param workflowStepName Name of workflow step we want the actions from + * @return List of actions of the requested workflow step + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page getActions(@Nullable HttpServletRequest request, + String workflowStepName, + @Nullable Pageable optionalPageable, + Projection projection) { + List actions = xmlWorkflowFactory.getStepByName(workflowStepName).getActions(); + Pageable pageable = optionalPageable != null ? optionalPageable : new PageRequest(0, 20); + return converter.toRestPage(utils.getPage(actions, pageable), projection); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java index 42ae152461..cc7459955f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.ClaimedTaskRest; import org.dspace.app.rest.projection.Projection; import org.dspace.discovery.IndexableObject; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +29,9 @@ public class ClaimedTaskConverter @Autowired private ConverterService converter; + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + @Override public ClaimedTaskRest convert(ClaimedTask obj, Projection projection) { ClaimedTaskRest taskRest = new ClaimedTaskRest(); @@ -35,8 +39,7 @@ public class ClaimedTaskConverter XmlWorkflowItem witem = obj.getWorkflowItem(); taskRest.setId(obj.getID()); taskRest.setWorkflowitem(converter.toRest(witem, projection)); - taskRest.setAction(obj.getActionID()); - taskRest.setStep(obj.getStepID()); + taskRest.setAction(converter.toRest(xmlWorkflowFactory.getActionByName(obj.getActionID()), projection)); taskRest.setOwner(converter.toRest(obj.getOwner(), projection)); return taskRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index 9e4f6c00f1..295634599b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -32,6 +32,7 @@ import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @@ -155,9 +156,30 @@ public class ConverterService { * @throws ClassCastException if the resource type is not compatible with the inferred return type. */ public T toResource(RestModel restObject) { + return toResource(restObject, new Link[] {}); + } + + /** + * Converts the given rest object to a {@link HALResource} object. + *

    + * If the rest object is a {@link RestAddressableModel}, the projection returned by + * {@link RestAddressableModel#getProjection()} will be used to determine which optional + * embeds and links will be added, and {@link Projection#transformResource(HALResource)} + * will be automatically called before returning the final, fully converted resource. + *

    + * In all cases, the {@link HalLinkService} will be used immediately after the resource is constructed, + * to ensure all {@link HalLinkFactory}s have had a chance to add links as needed. + *

    + * + * @param restObject the input rest object. + * @param oldLinks The old links fo the Resource Object + * @param the return type, a subclass of {@link HALResource}. + * @return the fully converted resource, with all automatic links and embeds applied. + */ + public T toResource(RestModel restObject, Link... oldLinks) { T halResource = getResource(restObject); if (restObject instanceof RestAddressableModel) { - utils.embedOrLinkClassLevelRels(halResource); + utils.embedOrLinkClassLevelRels(halResource, oldLinks); halLinkService.addLinks(halResource); Projection projection = ((RestAddressableModel) restObject).getProjection(); return projection.transformResource(halResource); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java index 7a58fda864..48299dd362 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java @@ -43,7 +43,6 @@ public class PoolTaskConverter taskRest.setGroup(converter.toRest(obj.getGroup(), projection)); } taskRest.setAction(obj.getActionID()); - taskRest.setStep(obj.getStepID()); return taskRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java index 1e7834d7ed..ab8694874c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java @@ -26,12 +26,6 @@ public class ResourcePolicyConverter implements DSpaceConverter { + + @Override + public WorkflowActionRest convert(WorkflowActionConfig modelObject, Projection projection) { + WorkflowActionRest restModel = new WorkflowActionRest(); + restModel.setProjection(projection); + restModel.setId(modelObject.getId()); + restModel.setOptions(modelObject.getOptions()); + return restModel; + } + + @Override + public Class getModelClass() { + return WorkflowActionConfig.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java new file mode 100644 index 0000000000..04af851e8b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java @@ -0,0 +1,50 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import java.util.stream.Collectors; + +import org.dspace.app.rest.model.WorkflowDefinitionRest; +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Workflow; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Converter to translate Workflow to a Workflow Definition + * + * @author Maria Verdonck (Atmire) on 11/12/2019 + */ +@Component +public class WorkflowDefinitionConverter implements DSpaceConverter { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + @Autowired + ConverterService converter; + + @Override + public WorkflowDefinitionRest convert(Workflow modelObject, Projection projection) { + WorkflowDefinitionRest restModel = new WorkflowDefinitionRest(); + restModel.setName(modelObject.getID()); + restModel.setIsDefault(xmlWorkflowFactory.isDefaultWorkflow(modelObject.getID())); + restModel.setProjection(projection); + restModel.setSteps(modelObject.getSteps().stream() + .map(x -> (WorkflowStepRest) converter.toRest(x, projection)) + .collect(Collectors.toList())); + return restModel; + } + + @Override + public Class getModelClass() { + return Workflow.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowStepConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowStepConverter.java new file mode 100644 index 0000000000..e1809f2cc4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowStepConverter.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import java.util.stream.Collectors; + +import org.dspace.app.rest.model.WorkflowActionRest; +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.Step; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Converter to translate {@link Step} to a {@link WorkflowStepRest} object + * + * @author Maria Verdonck (Atmire) on 10/01/2020 + */ +@Component +public class WorkflowStepConverter implements DSpaceConverter { + + @Autowired + ConverterService converter; + + @Override + public WorkflowStepRest convert(Step modelObject, Projection projection) { + WorkflowStepRest restModel = new WorkflowStepRest(); + restModel.setProjection(projection); + restModel.setId(modelObject.getId()); + restModel.setWorkflowactions(modelObject.getActions().stream() + .map(x -> (WorkflowActionRest) converter.toRest(x, projection)) + .collect(Collectors.toList())); + return restModel; + } + + @Override + public Class getModelClass() { + return Step.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 a0b033d8c6..e0b3a86d18 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 @@ -92,34 +92,37 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH HttpStatus.UNPROCESSABLE_ENTITY.value()); } - @ExceptionHandler( {MissingParameterException.class, QueryMethodParameterConversionException.class}) + @ExceptionHandler(QueryMethodParameterConversionException.class) protected void ParameterConversionException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - - //422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity". - //Using the value from HttpStatus. - //Since this is a handled exception case, the stack trace will not be returned. + // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 sendErrorResponse(request, response, null, ex.getMessage(), - HttpStatus.UNPROCESSABLE_ENTITY.value()); + HttpStatus.BAD_REQUEST.value()); + } + + @ExceptionHandler(MissingParameterException.class) + protected void MissingParameterException(HttpServletRequest request, HttpServletResponse response, Exception ex) + throws IOException { + // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + sendErrorResponse(request, response, null, + ex.getMessage(), + HttpStatus.BAD_REQUEST.value()); } @Override protected ResponseEntity handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { - // we want the 422 status for missing parameter as it seems to be the common behavior for REST application, see - // https://stackoverflow.com/questions/3050518/what-http-status-response-code-should-i-use-if-the-request-is-missing-a-required - return super.handleMissingServletRequestParameter(ex, headers, HttpStatus.UNPROCESSABLE_ENTITY, request); + // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + return super.handleMissingServletRequestParameter(ex, headers, HttpStatus.BAD_REQUEST, request); } @Override protected ResponseEntity handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { - // we want the 422 status for type mismatch on parameters as it seems to be the common behavior for REST - // application, see - // https://stackoverflow.com/questions/3050518/what-http-status-response-code-should-i-use-if-the-request-is-missing-a-required - return super.handleTypeMismatch(ex, headers, HttpStatus.UNPROCESSABLE_ENTITY, request); + // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + return super.handleTypeMismatch(ex, headers, HttpStatus.BAD_REQUEST, request); } @ExceptionHandler(Exception.class) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java index 8b13aee2d6..f280f65964 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java @@ -9,20 +9,26 @@ package org.dspace.app.rest.model; import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.RestResourceController; -import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; /** * The ClaimedTask REST Resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = ClaimedTaskRest.STEP, + method = "getStep" + ) +}) public class ClaimedTaskRest extends BaseObjectRest { public static final String NAME = "claimedtask"; public static final String CATEGORY = RestAddressableModel.WORKFLOW; - private String step; + public static final String STEP = "step"; - private String action; + @JsonIgnore + private WorkflowActionRest action; @JsonIgnore private EPersonRest owner; @@ -45,27 +51,15 @@ public class ClaimedTaskRest extends BaseObjectRest { return RestResourceController.class; } - /** - * @see ClaimedTask#getStepID() - * @return the step - */ - public String getStep() { - return step; - } - - public void setStep(String step) { - this.step = step; - } - /** * @see ClaimedTaskRest#getAction() * @return the action */ - public String getAction() { + public WorkflowActionRest getAction() { return action; } - public void setAction(String action) { + public void setAction(WorkflowActionRest action) { this.action = action; } @@ -82,7 +76,7 @@ public class ClaimedTaskRest extends BaseObjectRest { } /** - * + * * @return the WorkflowItemRest that belong to this claimed task */ public WorkflowItemRest getWorkflowitem() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java index b577de7327..ac248f435b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -26,6 +26,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; @LinkRest( name = CollectionRest.MAPPED_ITEMS, method = "getMappedItems" + ), + @LinkRest( + name = CollectionRest.PARENT_COMMUNITY, + method = "getParentCommunity" ) }) public class CollectionRest extends DSpaceObjectRest { @@ -37,6 +41,7 @@ public class CollectionRest extends DSpaceObjectRest { public static final String LICENSE = "license"; public static final String LOGO = "logo"; public static final String MAPPED_ITEMS = "mappedItems"; + public static final String PARENT_COMMUNITY = "parentCommunity"; @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java index c5097662c7..ee80c9633b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java @@ -26,6 +26,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; @LinkRest( name = CommunityRest.SUBCOMMUNITIES, method = "getSubcommunities" + ), + @LinkRest( + name = CommunityRest.PARENT_COMMUNITY, + method = "getParentCommunity" ) }) public class CommunityRest extends DSpaceObjectRest { @@ -36,6 +40,8 @@ public class CommunityRest extends DSpaceObjectRest { public static final String COLLECTIONS = "collections"; public static final String LOGO = "logo"; public static final String SUBCOMMUNITIES = "subcommunities"; + public static final String PARENT_COMMUNITY = "parentCommunity"; + @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java index 693c73a0ca..56ea6fa5f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java @@ -19,8 +19,12 @@ import org.dspace.app.rest.RestResourceController; @JsonIgnoreProperties(ignoreUnknown = true) @LinksRest(links = { @LinkRest( - name = GroupRest.GROUPS, + name = GroupRest.SUBGROUPS, method = "getGroups" + ), + @LinkRest( + name = GroupRest.EPERSONS, + method = "getMembers" ) }) public class GroupRest extends DSpaceObjectRest { @@ -28,6 +32,8 @@ public class GroupRest extends DSpaceObjectRest { public static final String CATEGORY = RestAddressableModel.EPERSON; public static final String GROUPS = "groups"; + public static final String SUBGROUPS = "subgroups"; + public static final String EPERSONS = "epersons"; private String name; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java index 6ab445659e..b3dedd23b9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java @@ -16,11 +16,17 @@ import org.dspace.xmlworkflow.storedcomponents.PoolTask; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = PoolTaskRest.STEP, + method = "getStep" + ) +}) public class PoolTaskRest extends BaseObjectRest { public static final String NAME = "pooltask"; public static final String CATEGORY = RestAddressableModel.WORKFLOW; - private String step; + public static final String STEP = "step"; private String action; @@ -48,18 +54,6 @@ public class PoolTaskRest extends BaseObjectRest { return RestResourceController.class; } - /** - * @see PoolTask#getStepID() - * @return - */ - public String getStep() { - return step; - } - - public void setStep(String step) { - this.step = step; - } - /** * @see PoolTask#getActionID() * @return diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java index 606bd8c273..656d9049fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.model; import java.util.Date; -import java.util.UUID; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -34,12 +33,6 @@ public class ResourcePolicyRest extends BaseObjectRest { private String description; - @JsonInclude(Include.NON_NULL) - private UUID groupUUID; - - @JsonInclude(Include.NON_NULL) - private UUID epersonUUID; - @JsonIgnore private EPersonRest eperson; @@ -55,14 +48,6 @@ public class ResourcePolicyRest extends BaseObjectRest { private Date endDate; - public UUID getGroupUUID() { - return groupUUID; - } - - public void setGroupUUID(UUID groupUuid) { - this.groupUUID = groupUuid; - } - public Date getEndDate() { return endDate; } @@ -111,14 +96,6 @@ public class ResourcePolicyRest extends BaseObjectRest { this.description = description; } - public UUID getEpersonUUID() { - return epersonUUID; - } - - public void setEpersonUUID(UUID epersonUUID) { - this.epersonUUID = epersonUUID; - } - public EPersonRest getEperson() { return eperson; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UploadBitstreamAccessConditionDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UploadBitstreamAccessConditionDTO.java new file mode 100644 index 0000000000..120cb6a9d2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UploadBitstreamAccessConditionDTO.java @@ -0,0 +1,101 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.Date; +import java.util.UUID; + +import org.dspace.app.rest.model.step.UploadBitstreamRest; + +/** + * The UploadAccessConditionDTO is a partial representation of the DSpace + * {@link ResourcePolicyRest} as used in the patch payload for the upload + * submission section (see {@link UploadBitstreamRest}. The main reason for this + * class is to have a DTO to use serialize/deserialize the REST model, that + * include reference to the GroupRest and EPersonRest object, in the upload + * section data in a simpler way where such reference are just UUID. Indeed, due + * to the fact that the RestModel class are serialized according to the HAL + * format and the reference are only exposed in the _links section of the + * RestResource it was not possible to use the {@link ResourcePolicyRest} class + * directly in the upload section + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class UploadBitstreamAccessConditionDTO { + + private Integer id; + + private UUID groupUUID; + + private UUID epersonUUID; + + private String name; + + private String description; + + private Date startDate; + + private Date endDate; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public UUID getGroupUUID() { + return groupUUID; + } + + public void setGroupUUID(UUID groupUUID) { + this.groupUUID = groupUUID; + } + + public UUID getEpersonUUID() { + return epersonUUID; + } + + public void setEpersonUUID(UUID epersonUUID) { + this.epersonUUID = epersonUUID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java new file mode 100644 index 0000000000..e998df6bc2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.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.app.rest.model; + +import java.util.List; + +import org.dspace.app.rest.RestResourceController; + +/** + * The rest resource used for workflow actions + * + * @author Maria Verdonck (Atmire) on 06/01/2020 + */ +public class WorkflowActionRest extends BaseObjectRest { + + public static final String CATEGORY = "config"; + public static final String NAME = "workflowaction"; + public static final String NAME_PLURAL = "workflowactions"; + + private List options; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + /** + * Generic getter for the options + * + * @return the options value of this WorkflowActionRest + */ + public List getOptions() { + return options; + } + + /** + * Generic setter for the options + * + * @param options The options to be set on this WorkflowActionRest + */ + public void setOptions(List options) { + this.options = options; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java new file mode 100644 index 0000000000..7c2de7071b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * The rest resource used for workflow definitions + * + * @author Maria Verdonck (Atmire) on 11/12/2019 + */ +@LinksRest(links = { + @LinkRest( + name = WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO, + method = "getCollections" + ), + @LinkRest( + name = WorkflowDefinitionRest.STEPS, + method = "getSteps" + ) +}) +public class WorkflowDefinitionRest extends BaseObjectRest { + + public static final String CATEGORY = "config"; + public static final String NAME = "workflowdefinition"; + public static final String NAME_PLURAL = "workflowdefinitions"; + + public static final String COLLECTIONS_MAPPED_TO = "collections"; + public static final String STEPS = "steps"; + + private String name; + private boolean isDefault; + private List steps; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + @Override + @JsonIgnore + public String getId() { + return name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean getIsDefault() { + return isDefault; + } + + public void setIsDefault(boolean isDefault) { + this.isDefault = isDefault; + } + + @JsonIgnore + public List getSteps() { + return steps; + } + + public void setSteps(List steps) { + this.steps = steps; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index bf00ba0e4f..8f580f4414 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -14,10 +14,18 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = WorkflowItemRest.STEP, + method = "getStep" + ) +}) public class WorkflowItemRest extends AInprogressSubmissionRest { public static final String NAME = "workflowitem"; public static final String CATEGORY = RestAddressableModel.WORKFLOW; + public static final String STEP = "step"; + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java new file mode 100644 index 0000000000..648cffbca8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.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.app.rest.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * The rest resource used for workflow steps + * + * @author Maria Verdonck (Atmire) on 10/01/2020 + */ +@LinksRest(links = { + @LinkRest( + name = WorkflowStepRest.ACTIONS, + method = "getActions" + ), +}) +public class WorkflowStepRest extends BaseObjectRest { + + public static final String CATEGORY = "config"; + public static final String NAME = "workflowstep"; + public static final String NAME_PLURAL = "workflowsteps"; + + public static final String ACTIONS = "workflowactions"; + + private List workflowactions; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + @JsonIgnore + public List getWorkflowactions() { + return workflowactions; + } + + public void setWorkflowactions(List actions) { + this.workflowactions = actions; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowActionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowActionResource.java new file mode 100644 index 0000000000..373c6a35ac --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowActionResource.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.WorkflowActionRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * {@link WorkflowActionRest} HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Maria Verdonck (Atmire) on 06/01/2020 + */ +@RelNameDSpaceResource(WorkflowActionRest.NAME) +public class WorkflowActionResource extends DSpaceResource { + public WorkflowActionResource(WorkflowActionRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowDefinitionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowDefinitionResource.java new file mode 100644 index 0000000000..9247749e9d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowDefinitionResource.java @@ -0,0 +1,24 @@ +/** + * 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.WorkflowDefinitionRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * WorkflowDefinition Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * @author Maria Verdonck (Atmire) on 11/12/2019 + */ +@RelNameDSpaceResource(WorkflowDefinitionRest.NAME) +public class WorkflowDefinitionResource extends DSpaceResource { + public WorkflowDefinitionResource(WorkflowDefinitionRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowStepResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowStepResource.java new file mode 100644 index 0000000000..6128c984a0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowStepResource.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.WorkflowStepRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * {@link WorkflowStepRest} HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Maria Verdonck (Atmire) on 10/01/2020 + */ +@RelNameDSpaceResource(WorkflowStepRest.NAME) +public class WorkflowStepResource extends DSpaceResource { + public WorkflowStepResource(WorkflowStepRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java index 80e36fdb59..7024e5cdb1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.model.step; import java.util.ArrayList; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,13 +17,19 @@ import java.util.UUID; import org.dspace.app.rest.model.BitstreamFormatRest; import org.dspace.app.rest.model.CheckSumRest; import org.dspace.app.rest.model.MetadataValueRest; -import org.dspace.app.rest.model.ResourcePolicyRest; +import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; +/** + * This Java Bean is used to represent a single bitstream with all its metadata + * and access conditions ({@link UploadBitstreamAccessConditionDTO}) inside an + * upload submission section ({@link DataUpload} + * + */ public class UploadBitstreamRest extends UploadStatusResponse { private UUID uuid; private Map> metadata = new HashMap<>(); - private List accessConditions; + private List accessConditions; private BitstreamFormatRest format; private Long sizeBytes; private CheckSumRest checkSum; @@ -68,14 +75,14 @@ public class UploadBitstreamRest extends UploadStatusResponse { this.metadata = metadata; } - public List getAccessConditions() { + public List getAccessConditions() { if (accessConditions == null) { accessConditions = new ArrayList<>(); } return accessConditions; } - public void setAccessConditions(List accessConditions) { + public void setAccessConditions(List accessConditions) { this.accessConditions = accessConditions; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java index 12a492191d..bbf38fd97f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java @@ -8,8 +8,10 @@ package org.dspace.app.rest.projection; import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; /** * Abstract base class for projections. @@ -34,7 +36,8 @@ public abstract class AbstractProjection implements Projection { } @Override - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks) { return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java new file mode 100644 index 0000000000..d9a28cec04 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java @@ -0,0 +1,84 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.projection; + +import java.util.List; + +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; + +/** + * A projection that combines the behavior of multiple projections. + * + * Model, rest, and resource transformations will be performed in the order of the projections given in + * the constructor. Embedding will be allowed if any of the given projections allow them. Linking will + * be allowed if all of the given projections allow them. + */ +public class CompositeProjection implements Projection { + + public final static String NAME = "composite"; + + private final List projections; + + public CompositeProjection(List projections) { + this.projections = projections; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public T transformModel(T modelObject) { + for (Projection projection : projections) { + modelObject = projection.transformModel(modelObject); + } + return modelObject; + } + + @Override + public T transformRest(T restObject) { + for (Projection projection : projections) { + restObject = projection.transformRest(restObject); + } + return restObject; + } + + @Override + public T transformResource(T halResource) { + for (Projection projection : projections) { + halResource = projection.transformResource(halResource); + } + return halResource; + } + + @Override + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks) { + for (Projection projection : projections) { + if (projection.allowEmbedding(halResource, linkRest, oldLinks)) { + return true; + } + } + return false; + } + + @Override + public boolean allowLinking(HALResource halResource, LinkRest linkRest) { + for (Projection projection : projections) { + if (!projection.allowLinking(halResource, linkRest)) { + return false; + } + } + return true; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java new file mode 100644 index 0000000000..1db0c6a74d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java @@ -0,0 +1,65 @@ +/** + * 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.projection; + +import java.util.Set; + +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; + +/** + * Projection that allows a given set of rels to be embedded. + * A Rel refers to a Link Relation, this is an Embedded Object of the HalResource and the HalResource contains + * a link to this + */ +public class EmbedRelsProjection extends AbstractProjection { + + public final static String NAME = "embedrels"; + + private final Set embedRels; + + public EmbedRelsProjection(Set embedRels) { + this.embedRels = embedRels; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks) { + // If level 0, and the name is present, the link can be embedded (e.g. the logo on a collection page) + if (halResource.getContent().getEmbedLevel() == 0 && embedRels.contains(linkRest.name())) { + return true; + } + + StringBuilder fullName = new StringBuilder(); + for (Link oldLink : oldLinks) { + fullName.append(oldLink.getRel()).append("/"); + } + fullName.append(linkRest.name()); + // If the full name matches, the link can be embedded (e.g. mappedItems/owningCollection on a collection page) + if (embedRels.contains(fullName.toString())) { + return true; + } + + fullName.append("/"); + // If the full name starts with the allowed embed, but the embed goes deeper, the link can be embedded + // (e.g. making sure mappedItems/owningCollection also embeds mappedItems on a collection page) + for (String embedRel : embedRels) { + if (embedRel.startsWith(fullName.toString())) { + return true; + } + } + return false; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java index 9c5a4a9f9e..99719c8ac3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java @@ -8,7 +8,10 @@ package org.dspace.app.rest.projection; import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; /** @@ -18,14 +21,17 @@ import org.springframework.stereotype.Component; public class FullProjection extends AbstractProjection { public final static String NAME = "full"; + private final int maxEmbed = DSpaceServicesFactory.getInstance().getConfigurationService() + .getIntProperty("rest.projections.full.max", 2); public String getName() { return NAME; } @Override - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { - return true; + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks) { + return halResource.getContent().getEmbedLevel() < maxEmbed; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java index d9e0b10261..cb16093464 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java @@ -10,9 +10,12 @@ package org.dspace.app.rest.projection; import javax.persistence.Entity; import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.repository.DSpaceRestRepository; +import org.dspace.app.rest.utils.Utils; +import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RestController; @@ -44,7 +47,7 @@ import org.springframework.web.bind.annotation.RestController; *
  • After it is converted to a {@link RestModel}, the projection may modify it * via {@link #transformRest(RestModel)}.
  • *
  • During conversion to a {@link HALResource}, the projection may opt in to certain annotation-discovered - * HAL embeds and links via {@link #allowEmbedding(HALResource, LinkRest)} + * HAL embeds and links via {@link #allowEmbedding(HALResource, LinkRest, Link...)} * and {@link #allowLinking(HALResource, LinkRest)}
  • *
  • After conversion to a {@link HALResource}, the projection may modify it * via {@link #transformResource(HALResource)}.
  • @@ -52,8 +55,7 @@ import org.springframework.web.bind.annotation.RestController; * *

    How a projection is chosen

    * - * When a REST request is made, the projection argument, if present, is used to look up the projection to use, - * by name. If no argument is present, {@link DefaultProjection} will be used. + * See {@link Utils#obtainProjection()}. */ public interface Projection { @@ -115,16 +117,18 @@ public interface Projection { * * @param halResource the resource from which the embed may or may not be made. * @param linkRest the LinkRest annotation through which the related resource was discovered on the rest object. + * @param oldLinks The previously traversed links * @return true if allowed, false otherwise. */ - boolean allowEmbedding(HALResource halResource, LinkRest linkRest); + boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks); /** * Tells whether this projection permits the linking of a particular linkable subresource. * * This gives the projection an opportunity to opt in to to certain links, by returning {@code true}. * - * Note: If {@link #allowEmbedding(HALResource, LinkRest)} returns {@code true} for a given subresource, + * Note: If {@link #allowEmbedding(HALResource, LinkRest, Link...)} returns {@code true} for a given subresource, * it will be automatically linked regardless of what this method returns. * * @param halResource the resource from which the link may or may not be made. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/SpecificLevelProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/SpecificLevelProjection.java new file mode 100644 index 0000000000..7ef603b23c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/SpecificLevelProjection.java @@ -0,0 +1,69 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.projection; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.MissingParameterException; +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.services.RequestService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; + +/** + * This Projection will allow us to specify how many levels deep we're going to embed resources onto the requested + * HalResource. + * The projection is used by using the name combined with the embedLevelDepth parameter to specify how deep the embeds + * have to go. There is an upperlimit in place for this, which is specified on the bean through the maxEmbed property + */ +public class SpecificLevelProjection extends AbstractProjection { + + @Autowired + private RequestService requestService; + + public final static String NAME = "level"; + + private int maxEmbed = DSpaceServicesFactory.getInstance().getConfigurationService() + .getIntProperty("rest.projections.full.max", 2); + + public int getMaxEmbed() { + return maxEmbed; + } + + public void setMaxEmbed(int maxEmbed) { + this.maxEmbed = maxEmbed; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks) { + String embedLevelDepthString = requestService.getCurrentRequest().getHttpServletRequest() + .getParameter("embedLevelDepth"); + if (StringUtils.isBlank(embedLevelDepthString)) { + throw new MissingParameterException("The embedLevelDepth parameter needs to be specified" + + " for this Projection"); + } + Integer embedLevelDepth = Integer.parseInt(embedLevelDepthString); + if (embedLevelDepth > maxEmbed) { + throw new IllegalArgumentException("The embedLevelDepth may not exceed the configured max: " + maxEmbed); + } + return halResource.getContent().getEmbedLevel() < embedLevelDepth; + } + + @Override + public boolean allowLinking(HALResource halResource, LinkRest linkRest) { + return true; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java index 4c68a02bed..7cea8ee7fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamFormatRest; -import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.content.BitstreamFormat; import org.dspace.content.service.BitstreamFormatService; @@ -89,7 +88,7 @@ public class BitstreamFormatRestRepository extends DSpaceRestRepository groups = utils.getPage(groupService.allMemberGroups(context, eperson), optionalPageable); + Page groups = utils.getPage(eperson.getGroups(), optionalPageable); return converter.toRestPage(groups, projection); } catch (SQLException e) { throw new RuntimeException(e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index e11d2f5c53..073d1b25bd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -20,7 +20,6 @@ import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; @@ -84,7 +83,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository findByName(@Parameter(value = "q", required = true) String q, - Pageable pageable) { - try { - Context context = obtainContext(); - long total = es.searchResultCount(context, q); - List epersons = es.search(context, q, Math.toIntExact(pageable.getOffset()), - Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); - return converter.toRestPage(epersons, pageable, total, utils.obtainProjection()); - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - /** * Find the eperson with the provided email address if any. The search is delegated to the * {@link EPersonService#findByEmail(Context, String)} method @@ -162,6 +137,32 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository findByMetadata(@Parameter(value = "query", required = true) String query, + Pageable pageable) { + + try { + Context context = obtainContext(); + long total = es.searchResultCount(context, query); + List epersons = es.search(context, query, Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); + return converter.toRestPage(epersons, pageable, total, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override @PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 24757883f8..9b63e6c420 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -13,7 +13,6 @@ import java.util.Optional; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.ExternalSourceEntryRest; import org.dspace.app.rest.model.ExternalSourceRest; -import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.ExternalDataProvider; @@ -52,7 +51,7 @@ public class ExternalSourceRestRepository extends DSpaceRestRepository new ResourceNotFoundException( "Couldn't find an ExternalSource for source: " + externalSourceName + " and ID: " + entryId)); - return converter.toRest(dataObject, Projection.DEFAULT); + return converter.toRest(dataObject, utils.obtainProjection()); } /** @@ -84,7 +83,7 @@ public class ExternalSourceRestRepository extends DSpaceRestRepository getMembers(@Nullable HttpServletRequest request, + UUID groupId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Group group = groupService.find(context, groupId); + if (group == null) { + throw new ResourceNotFoundException("No such group: " + groupId); + } + Page ePersons = utils.getPage(group.getMembers(), optionalPageable); + return converter.toRestPage(ePersons, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java index d415d3502f..952fc62bf5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "groups" subresource of an individual group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.GROUPS) +@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.SUBGROUPS) public class GroupGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java index 9909a3f011..c5d9164c4d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.repository; +import static org.apache.commons.lang3.StringUtils.isBlank; + import java.io.IOException; import java.sql.SQLException; import java.util.List; @@ -14,12 +16,13 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Group; @@ -54,9 +57,10 @@ public class GroupRestRepository extends DSpaceObjectRestRepository groups = gs.findAll(context, null, pageable.getPageSize(), - Math.toIntExact(pageable.getOffset())); + Math.toIntExact(pageable.getOffset())); return converter.toRestPage(groups, pageable, total, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); @@ -112,6 +120,31 @@ public class GroupRestRepository extends DSpaceObjectRestRepository findByMetadata(@Parameter(value = "query", required = true) String query, + Pageable pageable) { + + try { + Context context = obtainContext(); + long total = gs.searchResultCount(context, query); + List groups = gs.search(context, query, Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); + return converter.toRestPage(groups, pageable, total, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override public Class getDomainClass() { return GroupRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java index ad153a5ddc..ccacc24418 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java @@ -21,7 +21,6 @@ import org.dspace.app.rest.converter.HarvestedCollectionConverter; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.HarvestTypeEnum; import org.dspace.app.rest.model.HarvestedCollectionRest; -import org.dspace.app.rest.projection.Projection; import org.dspace.content.Collection; import org.dspace.core.Context; import org.dspace.harvest.HarvestedCollection; @@ -91,7 +90,7 @@ public class HarvestedCollectionRestRepository extends AbstractDSpaceRestReposit List> configs = OAIHarvester.getAvailableMetadataFormats(); return harvestedCollectionConverter.fromModel(harvestedCollection, collection, configs, - Projection.DEFAULT); + utils.obtainProjection()); } else { throw new UnprocessableEntityException( "Incorrect harvest settings in request. The following errors were found: " + errors.toString() diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 3125b4be3e..c6643496ae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -28,7 +28,6 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.handler.service.UriListHandlerService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bundle; @@ -293,7 +292,7 @@ public class ItemRestRepository extends DSpaceObjectRestRepository { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public WorkflowActionRest findOne(Context context, String workflowActionName) { + WorkflowActionConfig actionConfig = this.xmlWorkflowFactory.getActionByName(workflowActionName); + if (actionConfig != null) { + return converter.toRest(actionConfig, utils.obtainProjection()); + } else { + throw new ResourceNotFoundException("No workflow action with name " + workflowActionName + + " is configured"); + } + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(WorkflowActionRest.NAME, "findAll"); + } + + @Override + public Class getDomainClass() { + return WorkflowActionRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java new file mode 100644 index 0000000000..6837168521 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java @@ -0,0 +1,95 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.model.WorkflowDefinitionRest; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Context; +import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Workflow; +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 rest repository responsible for managing WorkflowDefinition Rest objects + * + * @author Maria Verdonck (Atmire) on 11/12/2019 + */ +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME) +public class WorkflowDefinitionRestRepository extends DSpaceRestRepository { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + @Autowired + private CollectionService collectionService; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public WorkflowDefinitionRest findOne(Context context, String workflowName) { + if (xmlWorkflowFactory.workflowByThisNameExists(workflowName)) { + try { + return converter.toRest(xmlWorkflowFactory.getWorkflowByName(workflowName), utils.obtainProjection()); + } catch (WorkflowConfigurationException e) { + // Should never occur, since xmlWorkflowFactory.getWorkflowByName only throws a + // WorkflowConfigurationException if no workflow by that name is configured (tested earlier) + throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); + } + } else { + throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); + } + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(Context context, Pageable pageable) { + List workflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); + return converter.toRestPage(utils.getPage(workflows, pageable), utils.obtainProjection()); + } + + /** + * GET endpoint that returns the workflow definition that applies to a specific collection eventually fallback + * to the default configuration. + * + * @param collectionId Uuid of the collection + * @return the workflow definition for this collection + */ + @SearchRestMethod(name = "findByCollection") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public WorkflowDefinitionRest findByCollection(@Parameter(value = "uuid") UUID collectionId) throws SQLException { + Context context = obtainContext(); + Collection collectionFromUuid = collectionService.find(context, collectionId); + if (collectionFromUuid != null) { + try { + return converter.toRest(xmlWorkflowFactory.getWorkflow(collectionFromUuid), utils.obtainProjection()); + } catch (WorkflowConfigurationException e) { + throw new ResourceNotFoundException("No workflow for this collection fault and " + + "no defaultWorkflow found"); + } + } else { + throw new ResourceNotFoundException("Collection with id " + collectionId + " not found"); + } + } + + @Override + public Class getDomainClass() { + return WorkflowDefinitionRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java index 99f6c5d38a..7d1605e109 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.repository; +import static org.dspace.xmlworkflow.state.actions.processingaction.ProcessingAction.SUBMIT_EDIT_METADATA; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -24,7 +26,6 @@ import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.WorkflowItemRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.submit.AbstractRestProcessingStep; import org.dspace.app.rest.submit.SubmissionService; import org.dspace.app.rest.submit.UploadableStep; @@ -42,7 +43,14 @@ import org.dspace.eperson.EPersonServiceImpl; import org.dspace.services.ConfigurationService; import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowService; +import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -89,6 +97,15 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository wfs; + @Autowired + ClaimedTaskService claimedTaskService; + + @Autowired + protected XmlWorkflowItemService xmlWorkflowItemService; + + @Autowired + protected XmlWorkflowFactory workflowFactory; + private final SubmissionConfigReader submissionConfigReader; public WorkflowItemRestRepository() throws SubmissionConfigReaderException { @@ -118,7 +135,8 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository witems = wis.findAll(context, pageable.getPageNumber(), pageable.getPageSize()); return converter.toRestPage(witems, pageable, total, utils.obtainProjection()); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new RuntimeException("SQLException in " + this.getClass() + "#findAll trying to retrieve all " + + "workflowitems from db.", e); } } @@ -133,7 +151,8 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository errors = new ArrayList(); SubmissionConfig submissionConfig = submissionConfigReader.getSubmissionConfigByName(wsi.getSubmissionDefinition().getName()); @@ -204,7 +226,7 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository operations = patch.getOperations(); WorkflowItemRest wsi = findOne(context, id); XmlWorkflowItem source = wis.find(context, id); + + this.checkIfEditMetadataAllowedInCurrentStep(context, source); + for (Operation op : operations) { //the value in the position 0 is a null value String[] path = op.getPath().substring(1).split("/", 3); @@ -290,8 +315,42 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository poolTasks = poolTaskService.find(context, xmlWorkflowItem); + List claimedTasks = claimedTaskService.find(context, xmlWorkflowItem); + for (PoolTask poolTask : poolTasks) { + return converter.toRest(xmlWorkflowFactory.getStepByName(poolTask.getStepID()), projection); + } + for (ClaimedTask claimedTask : claimedTasks) { + return converter.toRest(xmlWorkflowFactory.getStepByName(claimedTask.getStepID()), projection); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + throw new ResourceNotFoundException("No workflowStep for this workflowItem with id: " + workflowItemId + + " was found"); + + + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java new file mode 100644 index 0000000000..798c352b22 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java @@ -0,0 +1,55 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.core.Context; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Step; +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 rest repository responsible for managing {@link WorkflowStepRest} objects + * + * @author Maria Verdonck (Atmire) on 10/01/2020 + */ +@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.NAME) +public class WorkflowStepRestRepository extends DSpaceRestRepository { + + @Autowired + protected XmlWorkflowFactory xmlWorkflowFactory; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public WorkflowStepRest findOne(Context context, String workflowStepName) { + Step step = this.xmlWorkflowFactory.getStepByName(workflowStepName); + if (step != null) { + return converter.toRest(step, utils.obtainProjection()); + } else { + throw new ResourceNotFoundException("No workflow step with name " + workflowStepName + + " is configured"); + } + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(WorkflowStepRest.NAME, "findAll"); + } + + @Override + public Class getDomainClass() { + return WorkflowStepRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index b8a1fcb59d..397dce03c9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -31,7 +31,6 @@ import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.handler.service.UriListHandlerService; import org.dspace.app.rest.submit.AbstractRestProcessingStep; import org.dspace.app.rest.submit.SubmissionService; @@ -167,7 +166,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository { +public class BitstreamResourcePolicyAddPatchOperation extends AddPatchOperation { @Autowired BitstreamService bitstreamService; @@ -48,6 +49,7 @@ public class ResourcePolicyAddPatchOperation extends AddPatchOperation newAccessConditions = new ArrayList(); + List newAccessConditions = + new ArrayList(); if (split.length == 3) { authorizeService.removePoliciesActionFilter(context, b, Constants.READ); newAccessConditions = evaluateArrayObject((LateObjectEvaluator) value); @@ -74,7 +77,7 @@ public class ResourcePolicyAddPatchOperation extends AddPatchOperation getArrayClassForEvaluation() { - return ResourcePolicyRest[].class; + protected Class getArrayClassForEvaluation() { + return UploadBitstreamAccessConditionDTO[].class; } @Override - protected Class getClassForEvaluation() { - return ResourcePolicyRest.class; + protected Class getClassForEvaluation() { + return UploadBitstreamAccessConditionDTO.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ResourcePolicyRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java similarity index 86% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ResourcePolicyRemovePatchOperation.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java index 18b15dfba0..fcf96578a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ResourcePolicyRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import org.dspace.app.rest.model.ResourcePolicyRest; +import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; @@ -28,7 +28,8 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -public class ResourcePolicyRemovePatchOperation extends RemovePatchOperation { +public class BitstreamResourcePolicyRemovePatchOperation + extends RemovePatchOperation { @Autowired ItemService itemService; @@ -83,12 +84,12 @@ public class ResourcePolicyRemovePatchOperation extends RemovePatchOperation getArrayClassForEvaluation() { - return ResourcePolicyRest[].class; + protected Class getArrayClassForEvaluation() { + return UploadBitstreamAccessConditionDTO[].class; } @Override - protected Class getClassForEvaluation() { - return ResourcePolicyRest.class; + protected Class getClassForEvaluation() { + return UploadBitstreamAccessConditionDTO.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ResourcePolicyReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java similarity index 88% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ResourcePolicyReplacePatchOperation.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java index c03ea98271..7f0a23dbb9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ResourcePolicyReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.Date; import java.util.List; +import java.util.UUID; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.patch.LateObjectEvaluator; @@ -35,7 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -public class ResourcePolicyReplacePatchOperation extends ReplacePatchOperation { +public class BitstreamResourcePolicyReplacePatchOperation extends ReplacePatchOperation { @Autowired BitstreamService bitstreamService; @@ -89,12 +90,14 @@ public class ResourcePolicyReplacePatchOperation extends ReplacePatchOperation + * Any number of individual {@code Projections} that are spring-registered {@link Component}s may be specified + * by name via the {@code projection} parameter. If multiple projections are specified, they will be wrapped in a + * {@link CompositeProjection} and applied in order as described there. + *

    + * In addition, any number of embeds may be specified by rel name via the {@code embed} parameter. + * When provided, these act as a whitelist of embeds that may be included in the response, as described + * and implemented by {@link EmbedRelsProjection}. + *

    * * @return the requested or default projection, never {@code null}. * @throws IllegalArgumentException if the request specifies an unknown projection name. */ public Projection obtainProjection() { - String projectionName = requestService.getCurrentRequest().getServletRequest().getParameter("projection"); - return converter.getProjection(projectionName); + ServletRequest servletRequest = requestService.getCurrentRequest().getServletRequest(); + List projectionNames = getValues(servletRequest, "projection"); + Set embedRels = new HashSet<>(getValues(servletRequest, "embed")); + + List projections = new ArrayList<>(); + + for (String projectionName : projectionNames) { + projections.add(converter.getProjection(projectionName)); + } + + if (!embedRels.isEmpty()) { + projections.add(new EmbedRelsProjection(embedRels)); + } + + if (projections.isEmpty()) { + return Projection.DEFAULT; + } else if (projections.size() == 1) { + return projections.get(0); + } else { + return new CompositeProjection(projections); + } + } + + /** + * Gets zero or more values for the given servlet request parameter. + *

    + * This convenience method reads multiple values that have been specified as request parameter in multiple ways: + * via * {@code ?paramName=value1¶mName=value2}, via {@code ?paramName=value1,value2}, + * or a combination. + *

    + * It provides the values in the order they were given in the request, and automatically de-dupes them. + *

    + * + * @param servletRequest the servlet request. + * @param parameterName the parameter name. + * @return the ordered, de-duped values, possibly empty, never {@code null}. + */ + private List getValues(ServletRequest servletRequest, String parameterName) { + String[] rawValues = servletRequest.getParameterValues(parameterName); + List values = new ArrayList<>(); + if (rawValues != null) { + for (String rawValue : rawValues) { + for (String value : rawValue.split(",")) { + String trimmedValue = value.trim(); + if (trimmedValue.length() > 0 && !values.contains(trimmedValue)) { + values.add(trimmedValue); + } + } + } + } + return values; } /** * Adds embeds or links for all class-level LinkRel annotations for which embeds or links are allowed. * * @param halResource the resource. + * @param oldLinks previously traversed links */ - public void embedOrLinkClassLevelRels(HALResource halResource) { + public void embedOrLinkClassLevelRels(HALResource halResource, Link... oldLinks) { Projection projection = halResource.getContent().getProjection(); getLinkRests(halResource.getContent().getClass()).stream().forEach((linkRest) -> { Link link = linkToSubResource(halResource.getContent(), linkRest.name()); - if (projection.allowEmbedding(halResource, linkRest)) { - embedRelFromRepository(halResource, linkRest.name(), link, linkRest); + if (projection.allowEmbedding(halResource, linkRest, oldLinks)) { + embedRelFromRepository(halResource, linkRest.name(), link, linkRest, oldLinks); halResource.add(link); // unconditionally link if embedding was allowed } else if (projection.allowLinking(halResource, linkRest)) { halResource.add(link); @@ -499,6 +562,32 @@ public class Utils { */ void embedRelFromRepository(HALResource resource, String rel, Link link, LinkRest linkRest) { + embedRelFromRepository(resource, rel, link, linkRest, new Link[] {}); + } + + /** + * Embeds a rel whose value comes from a {@link LinkRestRepository}, if the maximum embed level has not + * been exceeded yet. + *

    + * The embed will be skipped if 1) the link repository reports that it is not embeddable or 2) the returned + * value is null and the LinkRest annotation has embedOptional = true. + *

    + * Implementation note: The caller is responsible for ensuring that the projection allows the embed + * before calling this method. + *

    + * + * @param resource the resource from which the embed will be made. + * @param rel the name of the rel. + * @param link the link. + * @param linkRest the LinkRest annotation (must have method defined). + * @param oldLinks The previously traversed links + * @throws RepositoryNotFoundException if the link repository could not be found. + * @throws IllegalArgumentException if the method specified by the LinkRest could not be found in the + * link repository. + * @throws RuntimeException if any other problem occurs when trying to invoke the method. + */ + void embedRelFromRepository(HALResource resource, + String rel, Link link, LinkRest linkRest, Link... oldLinks) { if (resource.getContent().getEmbedLevel() == EMBED_MAX_LEVELS) { return; } @@ -510,7 +599,7 @@ public class Utils { Object contentId = getContentIdForLinkMethod(resource.getContent(), method); try { Object linkedObject = method.invoke(linkRepository, null, contentId, null, projection); - resource.embedResource(rel, wrapForEmbedding(resource, linkedObject, link)); + resource.embedResource(rel, wrapForEmbedding(resource, linkedObject, link, oldLinks)); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof RuntimeException) { throw (RuntimeException) e.getTargetException(); @@ -615,17 +704,37 @@ public class Utils { */ private Object wrapForEmbedding(HALResource resource, Object linkedObject, Link link) { + return wrapForEmbedding(resource, linkedObject, link, new Link[] {}); + } + + /** + * Wraps the given linked object (retrieved from a link repository or link method on the rest item) + * in an object that is appropriate for embedding, if needed. Does not perform the actual embed; the + * caller is responsible for that. + * + * @param resource the resource from which the embed will be made. + * @param linkedObject the linked object. + * @param link the link, which is used if the linked object is a list or page, to determine the self link + * and embed property name to use for the subresource. + * @param oldLinks The previously traversed links + * @return the wrapped object, which will have an "embed level" one greater than the given parent resource. + */ + private Object wrapForEmbedding(HALResource resource, + Object linkedObject, Link link, Link... oldLinks) { int childEmbedLevel = resource.getContent().getEmbedLevel() + 1; + //Add the latest link to the list + Link[] newList = Arrays.copyOf(oldLinks, oldLinks.length + 1); + newList[oldLinks.length] = link; if (linkedObject instanceof RestAddressableModel) { RestAddressableModel restObject = (RestAddressableModel) linkedObject; restObject.setEmbedLevel(childEmbedLevel); - return converter.toResource(restObject); + return converter.toResource(restObject, newList); } else if (linkedObject instanceof Page) { // The first page has already been constructed by a link repository and we only need to wrap it Page page = (Page) linkedObject; return new EmbeddedPage(link.getHref(), page.map((restObject) -> { restObject.setEmbedLevel(childEmbedLevel); - return converter.toResource(restObject); + return converter.toResource(restObject, newList); }), null, link.getRel()); } else if (linkedObject instanceof List) { // The full list has been retrieved and we need to provide the first page for embedding @@ -637,7 +746,7 @@ public class Utils { return new EmbeddedPage(link.getHref(), page.map((restObject) -> { restObject.setEmbedLevel(childEmbedLevel); - return converter.toResource(restObject); + return converter.toResource(restObject, newList); }), list, link.getRel()); } else { diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index ed3e185839..61459f11d6 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -50,7 +50,7 @@ + class="org.dspace.app.rest.submit.factory.impl.BitstreamResourcePolicyAddPatchOperation"/> @@ -75,7 +75,7 @@ + class="org.dspace.app.rest.submit.factory.impl.BitstreamResourcePolicyRemovePatchOperation"/> @@ -96,7 +96,7 @@ + class="org.dspace.app.rest.submit.factory.impl.BitstreamResourcePolicyReplacePatchOperation"/> idRef = new AtomicReference(); + AtomicReference idRefNoEmbeds = new AtomicReference(); try { getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) - .contentType(contentType)) + .contentType(contentType) + .param("projection", "full")) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", CommunityMatcher.matchFullEmbeds())) .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.id", not(empty())), hasJsonPath("$.uuid", not(empty())), @@ -135,9 +142,19 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest // capture "id" returned in JSON response .andDo(result -> idRef .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + getClient(authToken).perform(post("/api/core/communities") + .content(mapper.writeValueAsBytes(commNoembeds)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andDo(result -> idRefNoEmbeds + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); } finally { // Delete the created community (cleanup after ourselves!) CommunityBuilder.deleteCommunity(idRef.get()); + CommunityBuilder.deleteCommunity(idRefNoEmbeds.get()); } } @@ -307,10 +324,11 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()), CommunityMatcher - .matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + .matchCommunityEntryFullProjection(child1.getName(), child1.getID(), child1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -334,13 +352,15 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); - getClient().perform(get("/api/core/communities").param("size", "2").param("projection", "full")) + getClient().perform(get("/api/core/communities").param("size", "2").param("projection", + "full")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( CommunityMatcher.matchCommunityEntryMultipleTitles(titles, parentCommunity.getID(), parentCommunity.getHandle()), - CommunityMatcher.matchCommunityEntry(child1.getID(), child1.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) .andExpect(jsonPath("$.page.totalElements", is(2))) @@ -357,50 +377,60 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .withTitle(titles.get(2)) .withTitle(titles.get(3)) .build(); - Community childCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity).build(); + Community childCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity).withName("test") + .build(); Community secondParentCommunity = CommunityBuilder.createCommunity(context).withName("testing").build(); Community thirdParentCommunity = CommunityBuilder.createCommunity(context).withName("testingTitleTwo").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/core/communities").param("size", "2").param("projection", "full")) + getClient().perform(get("/api/core/communities").param("size", "2").param("projection", + "full")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( CommunityMatcher.matchCommunityEntryMultipleTitles(titles, parentCommunity.getID(), parentCommunity.getHandle()), - CommunityMatcher.matchCommunityEntry(childCommunity.getID(), childCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(childCommunity.getName(), + childCommunity.getID(), + childCommunity.getHandle()) ))) - .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) - .andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(0, 2, 2, 4))); + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/core/communities"))) + .andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(0, 2, + 2, 4))); getClient().perform(get("/api/core/communities").param("size", "2").param("page", "1") .param("projection", "full")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( - CommunityMatcher.matchCommunityEntry(secondParentCommunity.getID(), - secondParentCommunity.getHandle()), - CommunityMatcher.matchCommunityEntry(thirdParentCommunity.getID(), - thirdParentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(secondParentCommunity.getName(), + secondParentCommunity.getID(), + secondParentCommunity.getHandle()), + CommunityMatcher.matchCommunityEntryFullProjection(thirdParentCommunity.getName(), + thirdParentCommunity.getID(), + thirdParentCommunity.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) - .andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(1, 2, 2, 4))); + .andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(1, 2, + 2, 4))); } @Test public void findAllNoNameCommunityIsReturned() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).build(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("test").build(); getClient().perform(get("/api/core/communities") .param("projection", "full")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.contains( - CommunityMatcher.matchCommunityEntry(parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) .andExpect(jsonPath("$.page.totalElements", is(1))); @@ -462,12 +492,14 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.contains( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$._embedded.communities", Matchers.not( Matchers.contains( - CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()) ) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) @@ -480,12 +512,14 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.contains( - CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()) ))) .andExpect(jsonPath("$._embedded.communities", Matchers.not( Matchers.contains( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) @@ -553,12 +587,14 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$", Matchers.not( Matchers.is( - CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()) ) ))) .andExpect(jsonPath("$._links.self.href", Matchers @@ -633,14 +669,18 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunity2.getName(), parentCommunity2.getID(), - parentCommunity2.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity2.getName(), + parentCommunity2.getID(), + parentCommunity2.getHandle()) ))) .andExpect(jsonPath("$._embedded.communities", Matchers.not(Matchers.containsInAnyOrder( - CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()), - CommunityMatcher.matchCommunityEntry(child12.getName(), child12.getID(), child12.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()), + CommunityMatcher.matchCommunityEntryFullProjection(child12.getName(), child12.getID(), + child12.getHandle()) )))) .andExpect( jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities/search/top"))) @@ -697,25 +737,25 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(content().contentType(contentType)) //Checking that these communities are present .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( - CommunityMatcher.matchCommunityEntry(parentCommunityChild1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunityChild1.getName(), parentCommunityChild1.getID(), parentCommunityChild1.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunityChild2.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunityChild2.getName(), parentCommunityChild2.getID(), parentCommunityChild2.getHandle()) ))) //Checking that these communities are not present .andExpect(jsonPath("$._embedded.communities", Matchers.not(Matchers.anyOf( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), parentCommunity.getID(), parentCommunity.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunity2.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity2.getName(), parentCommunity2.getID(), parentCommunity2.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunity2Child1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity2Child1.getName(), parentCommunity2Child1.getID(), parentCommunity2Child1.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunityChild2Child1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunityChild2Child1.getName(), parentCommunityChild2Child1.getID(), parentCommunityChild2Child1.getHandle()) )))) @@ -732,25 +772,25 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(content().contentType(contentType)) //Checking that these communities are present .andExpect(jsonPath("$._embedded.communities", Matchers.contains( - CommunityMatcher.matchCommunityEntry(parentCommunityChild2Child1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunityChild2Child1.getName(), parentCommunityChild2Child1.getID(), parentCommunityChild2Child1.getHandle()) ))) //Checking that these communities are not present .andExpect(jsonPath("$._embedded.communities", Matchers.not(Matchers.anyOf( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), parentCommunity.getID(), parentCommunity.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunity2.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity2.getName(), parentCommunity2.getID(), parentCommunity2.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunity2Child1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity2Child1.getName(), parentCommunity2Child1.getID(), parentCommunity2Child1.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunityChild2Child1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunityChild2Child1.getName(), parentCommunityChild2Child1.getID(), parentCommunityChild2Child1.getHandle()), - CommunityMatcher.matchCommunityEntry(parentCommunityChild1.getName(), + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunityChild1.getName(), parentCommunityChild1.getID(), parentCommunityChild1.getHandle()) )))) @@ -774,7 +814,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void findAllSubCommunitiesWithoutUUID() throws Exception { getClient().perform(get("/api/core/communities/search/subCommunities")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } @Test @@ -824,12 +864,14 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$", Matchers.not( Matchers.is( - CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()) ) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) @@ -857,7 +899,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry("Electronic theses and dissertations", + CommunityMatcher.matchCommunityEntryFullProjection("Electronic theses and dissertations", parentCommunity.getID(), parentCommunity.getHandle()) ))) @@ -912,11 +954,12 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", - Matchers.containsString("/api/core/communities"))) ; + Matchers.containsString("/api/core/communities"))); getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID().toString())) .andExpect(status().isNoContent()) ; @@ -974,11 +1017,12 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", - Matchers.containsString("/api/core/communities"))) ; + Matchers.containsString("/api/core/communities"))); getClient().perform(delete("/api/core/communities/" + parentCommunity.getID().toString())) .andExpect(status().isUnauthorized()) ; @@ -1007,11 +1051,12 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", - Matchers.containsString("/api/core/communities"))) ; + Matchers.containsString("/api/core/communities"))); getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID().toString())) .andExpect(status().isNoContent()) ; @@ -1041,12 +1086,14 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), - parentCommunity.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(parentCommunity.getName(), + parentCommunity.getID(), + parentCommunity.getHandle()) ))) .andExpect(jsonPath("$", Matchers.not( Matchers.is( - CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + CommunityMatcher.matchCommunityEntryFullProjection(child1.getName(), child1.getID(), + child1.getHandle()) ) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) @@ -1077,7 +1124,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry("Electronic theses and dissertations", + CommunityMatcher.matchCommunityEntryFullProjection("Electronic theses and dissertations", parentCommunity.getID(), parentCommunity.getHandle()) ))) 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 8c10c8a6a5..18ad6d3a48 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 @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -3717,4 +3719,407 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest } -} \ No newline at end of file + @Test + public void discoverSearchObjectsTestForAdministrativeViewAnonymous() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + + //1. 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(); + Collection col2 = CollectionBuilder + .createCollection(context, child1) + .withName("Collection 2") + .build(); + + //2. One public item, one private, one withdrawn. + + ItemBuilder.createItem(context, col1) + .withTitle("Public Test Item") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn Test Item") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("ExtraEntry") + .withdrawn() + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Private Test Item") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .withSubject("ExtraEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + + // An anonymous user browses this endpoint to find the withdrawn or private objects in the system + // With a query stating 'Test' + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test")) + + //** THEN ** + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( + SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item") + ))) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } + + @Test + public void discoverSearchObjectsTestForAdministrativeViewEPerson() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + + //1. 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(); + Collection col2 = CollectionBuilder + .createCollection(context, child1) + .withName("Collection 2") + .build(); + + //2. One public item, one private, one withdrawn. + + ItemBuilder.createItem(context, col1) + .withTitle("Public Test Item") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn Test Item") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("ExtraEntry") + .withdrawn() + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Private Test Item") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .withSubject("ExtraEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + + // A non-admin user browses this endpoint to find the withdrawn or private objects in the system + // With a query stating 'Test' + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test")) + + //** THEN ** + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( + SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item") + ))) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } + + @Test + public void discoverSearchObjectsTestForAdministrativeViewAdmin() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + + //1. 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(); + Collection col2 = CollectionBuilder + .createCollection(context, child1) + .withName("Collection 2") + .build(); + + //2. One public item, one private, one withdrawn. + + ItemBuilder.createItem(context, col1) + .withTitle("Public Test Item") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn Test Item") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("ExtraEntry") + .withdrawn() + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Private Test Item") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .withSubject("ExtraEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + + // A system admin user browses this endpoint to find the withdrawn or private objects in the system + // With a query stating 'Test' + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test")) + + //** THEN ** + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item"), + SearchResultMatcher.matchOnItemName("item", "items", "Withdrawn Test Item"), + SearchResultMatcher.matchOnItemName("item", "items", "Private Test Item") + ) + )) + .andExpect(jsonPath("$._embedded.facets", Matchers.hasItems( + allOf( + hasJsonPath("$.name", is("discoverable")), + hasJsonPath("$._embedded.values", Matchers.hasItems( + allOf( + hasJsonPath("$.label", is("true")), + hasJsonPath("$.count", is(2)) + ), + allOf( + hasJsonPath("$.label", is("false")), + hasJsonPath("$.count", is(1)) + ) + )) + ), + allOf( + hasJsonPath("$.name", is("withdrawn")), + hasJsonPath("$._embedded.values", Matchers.hasItems( + allOf( + hasJsonPath("$.label", is("true")), + hasJsonPath("$.count", is(1)) + ), + allOf( + hasJsonPath("$.label", is("false")), + hasJsonPath("$.count", is(2)) + ) + )) + ) + ))) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } + + @Test + public void discoverSearchObjectsTestForAdministrativeViewWithFilters() throws Exception { + + context.turnOffAuthorisationSystem(); + + 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(); + Collection col2 = CollectionBuilder + .createCollection(context, child1) + .withName("Collection 2") + .build(); + + ItemBuilder.createItem(context, col1) + .withTitle("Public Test Item") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn Test Item") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("ExtraEntry") + .withdrawn() + .build(); + + ItemBuilder.createItem(context, col2) + .withTitle("Private Test Item") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .withSubject("ExtraEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken) + .perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test") + .param("f.withdrawn", "true") + ) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.contains( + SearchResultMatcher.matchOnItemName("item", "items", "Withdrawn Test Item") + ) + )) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + getClient(adminToken) + .perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test") + .param("f.withdrawn", "false") + ) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item"), + SearchResultMatcher.matchOnItemName("item", "items", "Private Test Item") + ) + )) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + getClient(adminToken) + .perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test") + .param("f.discoverable", "true") + ) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item"), + SearchResultMatcher.matchOnItemName("item", "items", "Withdrawn Test Item") + ) + )) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + getClient(adminToken) + .perform(get("/api/discover/search/objects") + .param("configuration", "administrativeView") + .param("query", "Test") + .param("f.discoverable", "false") + ) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1) + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.contains( + SearchResultMatcher.matchOnItemName("item", "items", "Private Test Item") + ) + )) + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index fc782d7c39..9cbb163800 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -9,6 +9,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -30,8 +31,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.EPersonBuilder; +import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.EPersonMatcher; +import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.MetadataRest; @@ -43,6 +46,7 @@ import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.hamcrest.Matchers; import org.junit.Test; @@ -56,6 +60,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { // we should check how to get it from Spring ObjectMapper mapper = new ObjectMapper(); EPersonRest data = new EPersonRest(); + EPersonRest dataFull = new EPersonRest(); MetadataRest metadataRest = new MetadataRest(); data.setEmail("createtest@fake-email.com"); data.setCanLogIn(true); @@ -66,13 +71,18 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { firstname.setValue("John"); metadataRest.put("eperson.firstname", firstname); data.setMetadata(metadataRest); + dataFull.setEmail("createtestFull@fake-email.com"); + dataFull.setCanLogIn(true); + dataFull.setMetadata(metadataRest); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/eperson/epersons") .content(mapper.writeValueAsBytes(data)) - .contentType(contentType)) + .contentType(contentType) + .param("projection", "full")) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", EPersonMatcher.matchFullEmbeds())) .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.uuid", not(empty())), // is it what you expect? EPerson.getName() returns the email... @@ -86,6 +96,13 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { matchMetadata("eperson.firstname", "John"), matchMetadata("eperson.lastname", "Doe") ))))); + + getClient(authToken).perform(post("/api/eperson/epersons") + .content(mapper.writeValueAsBytes(dataFull)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())); // TODO cleanup the context!!! } @@ -297,7 +314,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._links.byEmail", Matchers.notNullValue())) - .andExpect(jsonPath("$._links.byName", Matchers.notNullValue())); + .andExpect(jsonPath("$._links.byMetadata", Matchers.notNullValue())); } @Test @@ -318,40 +335,40 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail") - .param("email", ePerson.getEmail())) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", is( - EPersonMatcher.matchEPersonEntry(ePerson) - ))); + .param("email", ePerson.getEmail())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", is( + EPersonMatcher.matchEPersonEntry(ePerson) + ))); // it must be case-insensitive getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail") - .param("email", ePerson.getEmail().toUpperCase())) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", is( - EPersonMatcher.matchEPersonEntry(ePerson) - ))); + .param("email", ePerson.getEmail().toUpperCase())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", is( + EPersonMatcher.matchEPersonEntry(ePerson) + ))); } @Test public void findByEmailUndefined() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail") - .param("email", "undefined@undefined.com")) - .andExpect(status().isNoContent()); + .param("email", "undefined@undefined.com")) + .andExpect(status().isNoContent()); } @Test - public void findByEmailUnprocessable() throws Exception { + public void findByEmailMissingParameter() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } @Test - public void findByName() throws Exception { + public void findByMetadataUsingLastName() throws Exception { context.turnOffAuthorisationSystem(); EPerson ePerson = EPersonBuilder.createEPerson(context) .withNameInMetadata("John", "Doe") @@ -379,8 +396,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/eperson/epersons/search/byName") - .param("q", ePerson.getLastName())) + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", ePerson.getLastName())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( @@ -392,8 +409,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.page.totalElements", is(4))); // it must be case insensitive - getClient(authToken).perform(get("/api/eperson/epersons/search/byName") - .param("q", ePerson.getLastName().toLowerCase())) + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", ePerson.getLastName().toLowerCase())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( @@ -406,20 +423,189 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { } @Test - public void findByNameUndefined() throws Exception { + public void findByMetadataUsingFirstName() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .build(); + + EPerson ePerson2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jane", "Smith") + .withEmail("janesmith@fake-email.com") + .build(); + + EPerson ePerson3 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Smith") + .withEmail("tomdoe@fake-email.com") + .build(); + + EPerson ePerson4 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John-Postfix", "Smath") + .withEmail("dirkdoepostfix@fake-email.com") + .build(); + + EPerson ePerson5 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Prefix-John", "Smoth") + .withEmail("harrydoeprefix@fake-email.com") + .build(); + String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/eperson/epersons/search/byName") - .param("q", "Doe, John")) + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", ePerson.getFirstName())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(ePerson), + EPersonMatcher.matchEPersonEntry(ePerson3), + EPersonMatcher.matchEPersonEntry(ePerson4), + EPersonMatcher.matchEPersonEntry(ePerson5) + ))) + .andExpect(jsonPath("$.page.totalElements", is(4))); + + // it must be case insensitive + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", ePerson.getFirstName().toLowerCase())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(ePerson), + EPersonMatcher.matchEPersonEntry(ePerson3), + EPersonMatcher.matchEPersonEntry(ePerson4), + EPersonMatcher.matchEPersonEntry(ePerson5) + ))) + .andExpect(jsonPath("$.page.totalElements", is(4))); + } + + @Test + public void findByMetadataUsingEmail() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .build(); + + EPerson ePerson2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jane", "Smith") + .withEmail("janesmith@fake-email.com") + .build(); + + EPerson ePerson3 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Tom", "Doe") + .withEmail("tomdoe@fake-email.com") + .build(); + + EPerson ePerson4 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Dirk", "Doe-Postfix") + .withEmail("dirkdoepostfix@fake-email.com") + .build(); + + EPerson ePerson5 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Harry", "Prefix-Doe") + .withEmail("harrydoeprefix@fake-email.com") + .build(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", ePerson.getEmail())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.contains( + EPersonMatcher.matchEPersonEntry(ePerson) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // it must be case insensitive + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", ePerson.getEmail().toLowerCase())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.contains( + EPersonMatcher.matchEPersonEntry(ePerson) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findByMetadataUsingUuid() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .build(); + + EPerson ePerson2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jane", "Smith") + .withEmail("janesmith@fake-email.com") + .build(); + + EPerson ePerson3 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Tom", "Doe") + .withEmail("tomdoe@fake-email.com") + .build(); + + EPerson ePerson4 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Dirk", "Doe-Postfix") + .withEmail("dirkdoepostfix@fake-email.com") + .build(); + + EPerson ePerson5 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Harry", "Prefix-Doe") + .withEmail("harrydoeprefix@fake-email.com") + .build(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", String.valueOf(ePerson.getID()))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.contains( + EPersonMatcher.matchEPersonEntry(ePerson) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // it must be case insensitive + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", String.valueOf(ePerson.getID()).toLowerCase())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.contains( + EPersonMatcher.matchEPersonEntry(ePerson) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + + @Test + public void findByMetadataUnauthorized() throws Exception { + getClient().perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Doe, John")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByMetadataForbidden() throws Exception { + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Doe, John")) + .andExpect(status().isForbidden()); + } + + @Test + public void findByMetadataUndefined() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Doe, John")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.page.totalElements", is(0))); } @Test - public void findByNameUnprocessable() throws Exception { + public void findByMetadataMissingParameter() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/eperson/epersons/search/byName")) - .andExpect(status().isUnprocessableEntity()); + getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")) + .andExpect(status().isBadRequest()); } @Test @@ -1308,4 +1494,54 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus); } + /** + * Test that epersons/<:uuid>/groups endpoint returns the direct groups of the epersons + * @throws Exception + */ + @Test + public void getDirectEpersonGroups() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + Group parentGroup1 = GroupBuilder.createGroup(context) + .withName("Test Parent Group 1") + .build(); + + Group childGroup1 = GroupBuilder.createGroup(context) + .withName("Test Child Group 1") + .withParent(parentGroup1) + .addMember(ePerson) + .build(); + + Group parentGroup2 = GroupBuilder.createGroup(context) + .withName("Test Parent Group 2") + .build(); + + Group childGroup2 = GroupBuilder.createGroup(context) + .withName("Test Child Group 2") + .withParent(parentGroup2) + .addMember(ePerson) + .build(); + + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/" + ePerson.getID() + "/groups")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", containsInAnyOrder( + GroupMatcher.matchGroupWithName(childGroup1.getName()), + GroupMatcher.matchGroupWithName(childGroup2.getName())))) + .andExpect(jsonPath("$._embedded.groups", Matchers.not( + containsInAnyOrder( + GroupMatcher.matchGroupWithName(parentGroup1.getName()), + GroupMatcher.matchGroupWithName(parentGroup2.getName())))) + ); + + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java index 7998864d8d..799f6b7892 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java @@ -114,6 +114,6 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati @Test public void findOneExternalSourceEntriesNoQuery() throws Exception { getClient().perform(get("/api/integration/externalsources/mock/entries")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index efccd50582..0b8b12f7fc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -10,6 +10,12 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; +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.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -20,18 +26,27 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.content.Community; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CommunityService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; import org.hamcrest.Matchers; import org.junit.Test; - /** * @author Jonas Van Goolen - (jonas@atmire.com) */ @@ -43,20 +58,32 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { throws Exception { // hold the id of the created workflow item - AtomicReference idRef = new AtomicReference(); + AtomicReference idRef = new AtomicReference<>(); + AtomicReference idRefNoEmbeds = new AtomicReference(); try { ObjectMapper mapper = new ObjectMapper(); GroupRest groupRest = new GroupRest(); + GroupRest groupRestNoEmbeds = new GroupRest(); String groupName = "testGroup1"; + String groupDescription = "test description"; + String groupNameNoEmbeds = "testGroup2"; groupRest.setName(groupName); + MetadataRest metadata = new MetadataRest(); + metadata.put("dc.description", new MetadataValueRest(groupDescription)); + groupRest.setMetadata(metadata); + + groupRestNoEmbeds.setName(groupNameNoEmbeds); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/eperson/groups") - .content(mapper.writeValueAsBytes(groupRest)).contentType(contentType)) + .content(mapper.writeValueAsBytes(groupRest)).contentType(contentType) + .param("projection", "full")) .andExpect(status().isCreated()) + .andExpect(jsonPath("$", GroupMatcher.matchFullEmbeds())) .andDo(result -> idRef - .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); getClient(authToken).perform(get("/api/eperson/groups")) //The status has to be 200 OK @@ -66,9 +93,26 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { GroupMatcher.matchGroupWithName(groupName), GroupMatcher.matchGroupWithName("Administrator"), GroupMatcher.matchGroupWithName("Anonymous")))); + + getClient(authToken).perform(post("/api/eperson/groups") + .content(mapper.writeValueAsBytes(groupRestNoEmbeds)).contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andDo(result -> idRefNoEmbeds + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group group = groupService.find(context, idRef.get()); + + assertEquals( + groupService.getMetadata(group, "dc.description"), + groupDescription + ); + } finally { // remove the created group if any GroupBuilder.deleteGroup(idRef.get()); + GroupBuilder.deleteGroup(idRefNoEmbeds.get()); } } @@ -81,17 +125,15 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { groupRest.setName(groupName); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient().perform(post("/api/eperson/groups") .content(mapper.writeValueAsBytes(groupRest)).contentType(contentType)) .andExpect(status().isUnauthorized()); - } @Test public void createForbiddenTest() throws Exception { + ObjectMapper mapper = new ObjectMapper(); GroupRest groupRest = new GroupRest(); String groupName = "testGroupForbidden1"; @@ -99,13 +141,26 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { groupRest.setName(groupName); String authToken = getAuthToken(eperson.getEmail(), password); - - authToken = getAuthToken(eperson.getEmail(), password); getClient(authToken).perform(post("/api/eperson/groups") .content(mapper.writeValueAsBytes(groupRest)).contentType(contentType)) .andExpect(status().isForbidden()); } + @Test + public void createUnprocessableTest() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(post("/api/eperson/groups") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void findAllTest() throws Exception { @@ -257,6 +312,96 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { ; } + @Test + public void searchMethodsExist() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons")) + .andExpect(jsonPath("$._links.search.href", Matchers.notNullValue())); + + getClient(authToken).perform(get("/api/eperson/epersons/search")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.byMetadata", Matchers.notNullValue())); + } + + @Test + public void findByMetadata() throws Exception { + context.turnOffAuthorisationSystem(); + + Group group1 = GroupBuilder.createGroup(context) + .withName("Test group") + .build(); + + Group group2 = GroupBuilder.createGroup(context) + .withName("Test group 2") + .build(); + + Group group3 = GroupBuilder.createGroup(context) + .withName("Test group 3") + .build(); + + Group group4 = GroupBuilder.createGroup(context) + .withName("Test other group") + .build(); + + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.containsInAnyOrder( + GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()), + GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()), + GroupMatcher.matchGroupEntry(group3.getID(), group3.getName()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + // it must be case insensitive + getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", String.valueOf(group1.getID()))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.contains( + GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findByMetadataUnauthorized() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient().perform(get("/api/eperson/groups/search/byMetadata") + .param("query", "Administrator")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByMetadataForbidden() throws Exception { + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", "Administrator")) + .andExpect(status().isForbidden()); + } + + @Test + public void findByMetadataUndefined() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", "Non-existing Group")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void findByMetadataMissingParameter() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata")) + .andExpect(status().isBadRequest()); + } + + @Test public void patchGroupMetadataAuthorized() throws Exception { runPatchMetadataTests(admin, 200); @@ -275,4 +420,1093 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/groups/" + group.getID(), expectedStatus); } + + @Test + public void addChildGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup1 = null; + Group childGroup2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup1 = groupService.create(context); + childGroup2 = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + assertTrue( + groupService.isMember(parentGroup, childGroup1) + ); + + assertTrue( + groupService.isMember(parentGroup, childGroup2) + ); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup1 != null) { + GroupBuilder.deleteGroup(childGroup1.getID()); + } + if (childGroup2 != null) { + GroupBuilder.deleteGroup(childGroup2.getID()); + } + } + } + + @Test + public void addChildGroupCommunityAdminTest() throws Exception { + + CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Community community = null; + + Group parentGroup = null; + Group childGroup1 = null; + Group childGroup2 = null; + + try { + context.turnOffAuthorisationSystem(); + + community = communityService.create(null, context); + parentGroup = communityService.createAdministrators(context, community); + childGroup1 = groupService.create(context); + childGroup2 = groupService.create(context); + + groupService.addMember(context, parentGroup, eperson); + groupService.update(context, parentGroup); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + assertTrue( + groupService.isMember(parentGroup, childGroup1) + ); + + assertTrue( + groupService.isMember(parentGroup, childGroup2) + ); + + } finally { + if (community != null) { + CommunityBuilder.deleteCommunity(community.getID()); + } + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup1 != null) { + GroupBuilder.deleteGroup(childGroup1.getID()); + } + if (childGroup2 != null) { + GroupBuilder.deleteGroup(childGroup2.getID()); + } + } + } + + @Test + public void addChildGroupForbiddenTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup1 = null; + Group childGroup2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup1 = groupService.create(context); + childGroup2 = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isForbidden()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup1 != null) { + GroupBuilder.deleteGroup(childGroup1.getID()); + } + if (childGroup2 != null) { + GroupBuilder.deleteGroup(childGroup2.getID()); + } + } + } + + @Test + public void addChildGroupUnauthorizedTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup1 = null; + Group childGroup2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup1 = groupService.create(context); + childGroup2 = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + getClient().perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isUnauthorized()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup1 != null) { + GroupBuilder.deleteGroup(childGroup1.getID()); + } + if (childGroup2 != null) { + GroupBuilder.deleteGroup(childGroup2.getID()); + } + } + } + + @Test + public void addChildGroupNotFoundTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup1 = null; + Group childGroup2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup1 = groupService.create(context); + childGroup2 = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + UUID.randomUUID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNotFound()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup1 != null) { + GroupBuilder.deleteGroup(childGroup1.getID()); + } + if (childGroup2 != null) { + GroupBuilder.deleteGroup(childGroup2.getID()); + } + } + } + + @Test + public void addChildGroupUnprocessableTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup1 = null; + Group childGroup2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup1 = groupService.create(context); + childGroup2 = groupService.create(context); + + groupService.addMember(context, childGroup1, parentGroup); + groupService.update(context, childGroup1); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/123456789\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isUnprocessableEntity()); + + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isUnprocessableEntity()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup1 != null) { + GroupBuilder.deleteGroup(childGroup1.getID()); + } + if (childGroup2 != null) { + GroupBuilder.deleteGroup(childGroup2.getID()); + } + } + } + + @Test + public void addMemberTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member1 = null; + EPerson member2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member1 = ePersonService.create(context); + member2 = ePersonService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + assertTrue( + groupService.isMember(context, member1, parentGroup) + ); + + assertTrue( + groupService.isMember(context, member2, parentGroup) + ); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member1 != null) { + EPersonBuilder.deleteEPerson(member1.getID()); + } + if (member2 != null) { + EPersonBuilder.deleteEPerson(member2.getID()); + } + } + } + + @Test + public void addMemberCommunityAdminTest() throws Exception { + + CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Community community = null; + Group parentGroup = null; + EPerson member1 = null; + EPerson member2 = null; + + try { + context.turnOffAuthorisationSystem(); + + community = communityService.create(null, context); + parentGroup = communityService.createAdministrators(context, community); + member1 = ePersonService.create(context); + member2 = ePersonService.create(context); + + groupService.addMember(context, parentGroup, eperson); + groupService.update(context, parentGroup); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + assertTrue( + groupService.isMember(context, member1, parentGroup) + ); + + assertTrue( + groupService.isMember(context, member2, parentGroup) + ); + + } finally { + if (community != null) { + CommunityBuilder.deleteCommunity(community.getID()); + } + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member1 != null) { + EPersonBuilder.deleteEPerson(member1.getID()); + } + if (member2 != null) { + EPersonBuilder.deleteEPerson(member2.getID()); + } + } + } + + @Test + public void addMemberForbiddenTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member1 = null; + EPerson member2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member1 = ePersonService.create(context); + member2 = ePersonService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isForbidden()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member1 != null) { + EPersonBuilder.deleteEPerson(member1.getID()); + } + if (member2 != null) { + EPersonBuilder.deleteEPerson(member2.getID()); + } + } + } + + @Test + public void addMemberUnauthorizedTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member1 = null; + EPerson member2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member1 = ePersonService.create(context); + member2 = ePersonService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + getClient().perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isUnauthorized()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member1 != null) { + EPersonBuilder.deleteEPerson(member1.getID()); + } + if (member2 != null) { + EPersonBuilder.deleteEPerson(member2.getID()); + } + } + } + + @Test + public void addMemberNotFoundTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member1 = null; + EPerson member2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member1 = ePersonService.create(context); + member2 = ePersonService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + UUID.randomUUID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isNotFound()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member1 != null) { + EPersonBuilder.deleteEPerson(member1.getID()); + } + if (member2 != null) { + EPersonBuilder.deleteEPerson(member2.getID()); + } + } + } + + @Test + public void addMemberUnprocessableTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member1 = null; + EPerson member2 = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member1 = ePersonService.create(context); + member2 = ePersonService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/123456789\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isUnprocessableEntity()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member1 != null) { + EPersonBuilder.deleteEPerson(member1.getID()); + } + if (member2 != null) { + EPersonBuilder.deleteEPerson(member2.getID()); + } + } + } + + @Test + public void removeChildGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup = groupService.create(context); + groupService.addMember(context, parentGroup, childGroup); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + assertFalse( + groupService.isMember(parentGroup, childGroup) + ); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup != null) { + GroupBuilder.deleteGroup(childGroup.getID()); + } + } + } + + @Test + public void removeChildGroupCommunityAdminTest() throws Exception { + + CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Community community = null; + Group parentGroup = null; + Group childGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + community = communityService.create(null, context); + parentGroup = communityService.createAdministrators(context, community); + childGroup = groupService.create(context); + + groupService.addMember(context, parentGroup, childGroup); + groupService.addMember(context, parentGroup, eperson); + groupService.update(context, parentGroup); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + assertFalse( + groupService.isMember(parentGroup, childGroup) + ); + + } finally { + if (community != null) { + CommunityBuilder.deleteCommunity(community.getID()); + } + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup != null) { + GroupBuilder.deleteGroup(childGroup.getID()); + } + } + } + + @Test + public void removeChildGroupForbiddenTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isForbidden()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup != null) { + GroupBuilder.deleteGroup(childGroup.getID()); + } + } + } + + @Test + public void removeChildGroupUnauthorizedTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + getClient().perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isUnauthorized()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup != null) { + GroupBuilder.deleteGroup(childGroup.getID()); + } + } + } + + @Test + public void removeChildGroupNotFoundTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup = groupService.create(context); + + groupService.addMember(context, childGroup, parentGroup); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + UUID.randomUUID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isNotFound()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup != null) { + GroupBuilder.deleteGroup(childGroup.getID()); + } + } + } + + @Test + public void removeChildGroupUnprocessableTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + Group parentGroup = null; + Group childGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + childGroup = groupService.create(context); + + groupService.addMember(context, childGroup, parentGroup); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + childGroup = context.reloadEntity(childGroup); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + UUID.randomUUID()) + ).andExpect(status().isUnprocessableEntity()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (childGroup != null) { + GroupBuilder.deleteGroup(childGroup.getID()); + } + } + } + + @Test + public void removeMemberTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member = ePersonService.create(context); + groupService.addMember(context, parentGroup, member); + assertTrue(groupService.isMember(context, member, parentGroup)); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + assertFalse( + groupService.isMember(context, member, parentGroup) + ); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member != null) { + EPersonBuilder.deleteEPerson(member.getID()); + } + } + } + + @Test + public void removeMemberCommunityAdminTest() throws Exception { + + CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Community community = null; + Group parentGroup = null; + EPerson member = null; + + try { + context.turnOffAuthorisationSystem(); + + community = communityService.create(null, context); + parentGroup = communityService.createAdministrators(context, community); + member = ePersonService.create(context); + + groupService.addMember(context, parentGroup, member); + groupService.addMember(context, parentGroup, eperson); + groupService.update(context, parentGroup); + + assertTrue(groupService.isMember(context, member, parentGroup)); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + assertFalse( + groupService.isMember(context, member, parentGroup) + ); + + } finally { + if (community != null) { + CommunityBuilder.deleteCommunity(community.getID()); + } + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member != null) { + EPersonBuilder.deleteEPerson(member.getID()); + } + } + } + + @Test + public void removeMemberForbiddenTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member = ePersonService.create(context); + groupService.addMember(context, parentGroup, member); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) + ).andExpect(status().isForbidden()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member != null) { + EPersonBuilder.deleteEPerson(member.getID()); + } + } + } + + @Test + public void removeMemberUnauthorizedTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member = ePersonService.create(context); + groupService.addMember(context, parentGroup, member); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + getClient().perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) + ).andExpect(status().isUnauthorized()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member != null) { + EPersonBuilder.deleteEPerson(member.getID()); + } + } + } + + @Test + public void removeMemberNotFoundTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member = ePersonService.create(context); + groupService.addMember(context, parentGroup, member); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + UUID.randomUUID() + "/epersons/" + member.getID()) + ).andExpect(status().isNotFound()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member != null) { + EPersonBuilder.deleteEPerson(member.getID()); + } + } + } + + @Test + public void removeMemberUnprocessableTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + EPerson member = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + member = ePersonService.create(context); + groupService.addMember(context, parentGroup, member); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + member = context.reloadEntity(member); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + UUID.randomUUID()) + ).andExpect(status().isUnprocessableEntity()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + if (member != null) { + EPersonBuilder.deleteEPerson(member.getID()); + } + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java index 9a1cd5d5ab..9fac87a9e6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java @@ -98,8 +98,7 @@ public class ItemOwningCollectionUpdateRestControllerIT extends AbstractControll //We expect a 401 Unauthorized status when performed by anonymous .andExpect(status().isOk()); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/owningCollection") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/owningCollection")) .andExpect(jsonPath("$", is(CollectionMatcher .matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) @@ -152,8 +151,7 @@ public class ItemOwningCollectionUpdateRestControllerIT extends AbstractControll //We expect a 401 Unauthorized status when performed by anonymous .andExpect(status().isOk()); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/owningCollection") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/owningCollection")) .andExpect(jsonPath("$", is(CollectionMatcher .matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) @@ -279,8 +277,9 @@ public class ItemOwningCollectionUpdateRestControllerIT extends AbstractControll "https://localhost:8080/spring-rest/api/core/collections/" + col2.getID() )) - //We expect a 401 Unauthorized status when performed by anonymous - .andExpect(status().isForbidden()); + // we expect a 200 here as the user has ADMIN permission on the source collection and + // ADD permission on the target one. This is the normal behavior also in DSpace 6 + .andExpect(status().isOk()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index f578cb6f08..45c398ba85 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; + 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; @@ -35,6 +36,8 @@ import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; +import org.dspace.app.rest.matcher.BitstreamMatcher; +import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; @@ -50,15 +53,20 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.CollectionService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MvcResult; public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private CollectionService collectionService; + @Test public void findAllTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -251,6 +259,16 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andExpect(jsonPath("$", publicItem1Matcher)); + + // When exact embeds are requested, response should include expected properties, links, and exact embeds. + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .param("embed", "bundles,owningCollection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", HalMatcher.matchEmbeds( + "bundles[]", + "owningCollection" + ))) + .andExpect(jsonPath("$", publicItem1Matcher)); } @Test @@ -1294,6 +1312,122 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { } + @Test + public void embargoAccessGrantAdminsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson adminParentCommunity = EPersonBuilder.createEPerson(context) + .withEmail("adminCommunity@mail.com") + .withPassword("qwerty01") + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(adminParentCommunity) + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + EPerson adminChild2 = EPersonBuilder.createEPerson(context) + .withEmail("adminChild2@mail.com") + .withPassword("qwerty05") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community 2") + .withAdminGroup(adminChild2) + .build(); + + EPerson adminCollection1 = EPersonBuilder.createEPerson(context) + .withEmail("adminCollection1@mail.com") + .withPassword("qwerty02") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCollection1) + .build(); + + EPerson adminCollection2 = EPersonBuilder.createEPerson(context) + .withEmail("adminCollection2@mail.com") + .withPassword("qwerty03") + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withAdminGroup(adminCollection2) + .build(); + + Item embargoedItem = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2015-10-21") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .withEmbargoPeriod("1 week") + .build(); + + context.restoreAuthSystemState(); + // parent community's admin user is allowed access to embargoed item + String tokenAdminParentCommunity = getAuthToken(adminParentCommunity.getEmail(), "qwerty01"); + getClient(tokenAdminParentCommunity).perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(ItemMatcher.matchItemProperties(embargoedItem)))); + + // collection1's admin user is allowed access to embargoed item + String tokenAdminCollection1 = getAuthToken(adminCollection1.getEmail(), "qwerty02"); + getClient(tokenAdminCollection1).perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(ItemMatcher.matchItemProperties(embargoedItem)))); + + // collection2's admin user is NOT allowed access to embargoed item of collection1 + String tokenAdminCollection2 = getAuthToken(adminCollection2.getEmail(), "qwerty03"); + getClient(tokenAdminCollection2).perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isForbidden()); + + // full admin user is allowed access to embargoed item + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(ItemMatcher.matchItemProperties(embargoedItem)))); + + // child2's admin user is NOT allowed access to embargoed item of collection1 + String tokenAdminChild2 = getAuthToken(adminChild2.getEmail(), "qwerty05"); + getClient(tokenAdminChild2).perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void expiredEmbargoTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item embargoedItem = ItemBuilder.createItem(context, col1) + .withTitle("embargoed item 1") + .withIssueDate("2017-11-18") + .withAuthor("Smith, Donald") + .withEmbargoPeriod("-2 week") + .build(); + + context.restoreAuthSystemState(); + + // all are allowed access to item with embargoed expired + + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(ItemMatcher.matchItemProperties(embargoedItem)))); + + getClient().perform(get("/api/core/items/" + embargoedItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(ItemMatcher.matchItemProperties(embargoedItem)))); + } + @Test public void undiscoverableAccessTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1422,6 +1556,134 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.containsString("/api/core/items"))); } + @Test + public void restrictedGroupAccessForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson memberRestrictGroup = EPersonBuilder.createEPerson(context) + .withEmail("eperson1@mail.com") + .withPassword("qwerty01") + .build(); + + Group restrictGroup = GroupBuilder.createGroup(context) + .addMember(memberRestrictGroup) + .build(); + + 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(); + + Item itemRestrictedByGroup = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2011-11-13") + .withAuthor("Smith, Donald") + .withReaderGroup(restrictGroup) + .build(); + + context.restoreAuthSystemState(); + + //A member of the restricted group is also allowed access to restricted item + String tokenMemberRestrictedGroup = getAuthToken(memberRestrictGroup.getEmail(), "qwerty01"); + getClient(tokenMemberRestrictedGroup).perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(ItemMatcher.matchItemProperties(itemRestrictedByGroup)))); + + //members who are not part of the restricted group, have no access to the item + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void restrictedGroupAccessGrantAdminsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson adminParentCommunity = EPersonBuilder.createEPerson(context) + .withEmail("adminCommunity@mail.com") + .withPassword("qwerty01") + .build(); + + Group restrictedGroup = GroupBuilder.createGroup(context) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(adminParentCommunity) + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + EPerson adminChild2 = EPersonBuilder.createEPerson(context) + .withEmail("adminChild2@mail.com") + .withPassword("qwerty05") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community 2") + .build(); + + EPerson adminCollection1 = EPersonBuilder.createEPerson(context) + .withEmail("adminCollection1@mail.com") + .withPassword("qwerty02") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCollection1) + .build(); + + EPerson adminCollection2 = EPersonBuilder.createEPerson(context) + .withEmail("adminCollection2@mail.com") + .withPassword("qwerty03") + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withAdminGroup(adminCollection2) + .build(); + + Item itemRestrictedByGroup = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2015-10-21") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .withReaderGroup(restrictedGroup) + .build(); + + context.restoreAuthSystemState(); + // parent community's admin user is allowed access to restricted item + String tokenAdminParentCommunity = getAuthToken(adminParentCommunity.getEmail(), "qwerty01"); + getClient(tokenAdminParentCommunity).perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(ItemMatcher.matchItemProperties(itemRestrictedByGroup)))); + + // collection1's admin user is allowed access to restricted item + String tokenAdminCollection1 = getAuthToken(adminCollection1.getEmail(), "qwerty02"); + getClient(tokenAdminCollection1).perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(ItemMatcher.matchItemProperties(itemRestrictedByGroup)))); + + // collection2's admin user is NOT allowed access to restricted item of collection1 + String tokenAdminCollection2 = getAuthToken(adminCollection2.getEmail(), "qwerty03"); + getClient(tokenAdminCollection2).perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isForbidden()); + + // child2's admin user is NOT allowed access to restricted item of collection1 + String tokenAdminChild2 = getAuthToken(adminChild2.getEmail(), "qwerty05"); + getClient(tokenAdminCollection2).perform(get("/api/core/items/" + itemRestrictedByGroup.getID())) + .andExpect(status().isForbidden()); + } + @Test public void testCreateItem() throws Exception { @@ -1441,6 +1703,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ObjectMapper mapper = new ObjectMapper(); ItemRest itemRest = new ItemRest(); + ItemRest itemRestFull = new ItemRest(); itemRest.setName("Practices of research data curation in institutional repositories:" + " A qualitative view from repository staff"); itemRest.setInArchive(true); @@ -1454,11 +1717,25 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) .put("dc.title", new MetadataValueRest("Title Text"))); + itemRestFull.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRestFull.setInArchive(true); + itemRestFull.setDiscoverable(true); + itemRestFull.setWithdrawn(false); + + itemRestFull.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

    Some cool HTML code here

    ")) + .put("dc.description.abstract", new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

    HTML News

    ")) + .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("Title Text"))); + String token = getAuthToken(admin.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + col1.getID().toString()) .content(mapper.writeValueAsBytes(itemRest)).contentType(contentType)) .andExpect(status().isCreated()) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andReturn(); String content = mvcResult.getResponse().getContentAsString(); @@ -1487,6 +1764,13 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { MetadataMatcher.matchMetadata("dc.title", "Title Text") ))))); + + MvcResult mvcResultFull = getClient(token).perform(post("/api/core/items?owningCollection=" + + col1.getID().toString()).param("projection", "full") + .content(mapper.writeValueAsBytes(itemRestFull)).contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", ItemMatcher.matchFullEmbeds())) + .andReturn(); } @Test @@ -2088,4 +2372,178 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .content("https://localhost:8080/server/api/integration/externalsources/" + "mock/entryValues/one")).andExpect(status().isUnauthorized()); } + + @Test + public void specificEmbedTestMultipleLevelOfLinks() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. 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(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 3") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + + //Add a bitstream to an item + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + getClient().perform(get("/api/core/items/" + publicItem1.getID() + + "?embed=owningCollection/mappedItems/bundles/" + + "bitstreams&embed=owningCollection/logo")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(publicItem1))) + .andExpect(jsonPath("$._embedded.owningCollection", + CollectionMatcher.matchCollectionEntry(col1.getName(), + col1.getID(), + col1.getHandle()))) + // .doesNotExist() makes sure that this section is not embedded, it's not there at all + .andExpect(jsonPath("$._embedded.bundles").doesNotExist()) + // .doesNotExist() makes sure that this section is not embedded, it's not there at all + .andExpect(jsonPath("$._embedded.relationships").doesNotExist()) + // .doesNotExist() makes sure that this section is not embedded, it's not there at all + .andExpect(jsonPath("$._embedded.owningCollection._embedded.defaultAccessConditions") + .doesNotExist()) + // .nullValue() makes sure that it could be embedded, it's just null in this case + .andExpect(jsonPath("$._embedded.owningCollection._embedded.logo", Matchers.nullValue())) + // .empty() makes sure that the embed is there, but that there's no actual data + .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems._embedded.mappedItems", + Matchers.empty())) + ; + } + + @Test + public void specificEmbedTestMultipleLevelOfLinksWithData() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. 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") + .withLogo("TestingContentForLogo").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 3") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + + collectionService.addItem(context, col1, publicItem2); + + //Add a bitstream to an item + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem2, is) + .withName("Bitstream2") + .withMimeType("text/plain") + .build(); + } + + + context.restoreAuthSystemState(); + getClient().perform(get("/api/core/items/" + publicItem1.getID() + + "?embed=owningCollection/mappedItems/bundles/" + + "bitstreams&embed=owningCollection/logo")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(publicItem1))) + .andExpect(jsonPath("$._embedded.owningCollection", + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), + col1.getHandle()))) + // .doesNotExist() makes sure that this section is not embedded, it's not there at all + .andExpect(jsonPath("$._embedded.bundles").doesNotExist()) + .andExpect(jsonPath("$._embedded.relationships").doesNotExist()) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.defaultAccessConditions") + .doesNotExist()) + // .notNullValue() makes sure that it's there and that it does actually contain a value, but not null + .andExpect(jsonPath("$._embedded.owningCollection._embedded.logo", Matchers.notNullValue())) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.logo._embedded").doesNotExist()) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems._embedded.mappedItems", + Matchers.contains(ItemMatcher.matchItemProperties(publicItem2)))) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems._embedded" + + ".mappedItems[0]._embedded.bundles._embedded.bundles[0]._embedded" + + ".bitstreams._embedded.bitstreams", Matchers.contains( + BitstreamMatcher.matchBitstreamEntryWithoutEmbed(bitstream2.getID(), bitstream2.getSizeBytes()) + ))) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems." + + "_embedded.mappedItems[0]_embedded.relationships").doesNotExist()) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems" + + "._embedded.mappedItems[0]._embedded.bundles._embedded.bundles[0]." + + "_embedded.primaryBitstream").doesNotExist()) + .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems." + + "_embedded.mappedItems[0]._embedded.bundles._embedded.bundles[0]." + + "_embedded.bitstreams._embedded.bitstreams[0]._embedded.format") + .doesNotExist()) + ; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java index e3d64fce0d..4164f38c36 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java @@ -62,12 +62,11 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .build(); context.restoreAuthSystemState(); -// collectionService.addItem(context, col2, publicItem1); -// collectionService.update(context, col2); +// collectionService.addItem(context, colAA2, publicItem1); +// collectionService.update(context, colAA2); // itemService.update(context, publicItem1); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.not(Matchers.contains( CollectionMatcher.matchCollectionEntry("Collection 1", col1.getID(), col1.getHandle()), @@ -76,14 +75,12 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))) ; - getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))); - getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) @@ -124,8 +121,7 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat ) ); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.containsInAnyOrder( CollectionMatcher.matchCollectionEntry("Collection 2", col2.getID(), col2.getHandle()) @@ -133,15 +129,13 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))) ; - getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0))); - getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) @@ -184,8 +178,7 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat ) ); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.containsInAnyOrder( CollectionMatcher.matchCollectionEntry("Collection 2", col2.getID(), col2.getHandle()), @@ -251,8 +244,7 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat ) ); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.containsInAnyOrder( CollectionMatcher.matchCollectionEntry("Collection 2", col2.getID(), col2.getHandle()), @@ -307,8 +299,7 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat ) ); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.not(Matchers.contains( CollectionMatcher.matchCollectionEntry("Collection 1", col1.getID(), col1.getHandle()) @@ -359,16 +350,14 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat ) ); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.not(Matchers.contains( CollectionMatcher.matchCollectionEntry("Collection 1", col1.getID(), col1.getHandle())) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))) ; - getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) @@ -378,30 +367,26 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat getClient(adminToken) .perform(delete("/api/core/items/" + publicItem1.getID() + "/mappedCollections/" + col2.getID())); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.not(Matchers.containsInAnyOrder( CollectionMatcher.matchCollectionEntry("Collection 2", col2.getID(), col2.getHandle()), CollectionMatcher.matchCollectionEntry("Collection 1", col1.getID(), col1.getHandle()) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))); - getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0))); - getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0))); - getClient().perform(get("/api/core/collections/" + col3.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col3.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) @@ -411,8 +396,7 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat getClient(adminToken) .perform(delete("/api/core/items/" + publicItem1.getID() + "/mappedCollections/" + col1.getID())); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.not(Matchers.containsInAnyOrder( CollectionMatcher.matchCollectionEntry("Collection 2", col2.getID(), col2.getHandle()), @@ -420,22 +404,19 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))) ; - getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col1.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0))); - getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col2.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0)));; - getClient().perform(get("/api/core/collections/" + col3.getID() + "/mappedItems") - .param("projection", "full")) + getClient().perform(get("/api/core/collections/" + col3.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) @@ -626,14 +607,13 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .build(); context.restoreAuthSystemState(); -// collectionService.addItem(context, col2, publicItem1); -// collectionService.update(context, col2); +// collectionService.addItem(context, colAA2, publicItem1); +// collectionService.update(context, colAA2); // itemService.update(context, publicItem1); context.restoreAuthSystemState(); context.setCurrentUser(null); - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections") - .param("projection", "full")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/mappedCollections")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedCollections", Matchers.not(Matchers.contains( CollectionMatcher.matchCollectionEntry("Collection 1", col1.getID(), col1.getHandle()), @@ -667,8 +647,8 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .build(); context.restoreAuthSystemState(); -// collectionService.addItem(context, col2, publicItem1); -// collectionService.update(context, col2); +// collectionService.addItem(context, colAA2, publicItem1); +// collectionService.update(context, colAA2); // itemService.update(context, publicItem1); getClient().perform( @@ -704,8 +684,8 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .build(); context.restoreAuthSystemState(); -// collectionService.addItem(context, col2, publicItem1); -// collectionService.update(context, col2); +// collectionService.addItem(context, colAA2, publicItem1); +// collectionService.update(context, colAA2); // itemService.update(context, publicItem1); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java index 9aa2820eae..c116cb9d28 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.MetadataSchemaBuilder; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.matcher.MetadataschemaMatcher; import org.dspace.app.rest.model.MetadataSchemaRest; import org.dspace.app.rest.projection.Projection; @@ -102,6 +103,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) .contentType(contentType)) .andExpect(status().isCreated()) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); getClient().perform(get("/api/core/metadataschemas/" + idRef.get())) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index 49a5995be7..1bf2a37f96 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -165,7 +165,7 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration public void findByNullSchema() throws Exception { getClient().perform(get("/api/core/metadatafields/search/bySchema")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } @Test @@ -184,6 +184,7 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration getClient(authToken) .perform(post("/api/core/metadatafields") .param("schemaId", metadataSchema.getID() + "") + .param("projection", "full") .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) .contentType(contentType)) .andExpect(status().isCreated()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index 67e086e13e..69a3ea86b0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -514,6 +514,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .param("relationshipType", isAuthorOfPublicationRelationshipType.getID() .toString()) + .param("projection", "full") .contentType(MediaType.parseMediaType (org.springframework.data.rest.webmvc.RestMediaTypes .TEXT_URI_LIST_VALUE)) @@ -523,6 +524,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest "https://localhost:8080/server/api/core/items/" + author1 .getID())) .andExpect(status().isCreated()) + .andExpect(jsonPath("$", RelationshipMatcher.matchFullEmbeds())) .andReturn(); ObjectMapper mapper = new ObjectMapper(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index ed298fcef5..c08cef9289 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -38,7 +38,7 @@ import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.ResourcePolicyBuilder; -import org.dspace.app.rest.matcher.ResoucePolicyMatcher; +import org.dspace.app.rest.matcher.ResourcePolicyMatcher; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; @@ -56,7 +56,6 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.hamcrest.Matchers; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -123,7 +122,7 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio getClient(authToken).perform(get("/api/authz/resourcepolicies/" + resourcePolicy.getID())) .andExpect(status().isOk()).andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( - ResoucePolicyMatcher.matchResourcePolicy(resourcePolicy) + ResourcePolicyMatcher.matchResourcePolicy(resourcePolicy) ))) .andExpect(jsonPath("$._links.self.href", Matchers .containsString("/api/authz/resourcepolicies/" + resourcePolicy.getID()))); @@ -237,8 +236,7 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(get("/api/authz/resourcepolicies/" + resourcePolicy.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is( - ResoucePolicyMatcher.matchResourcePolicy(resourcePolicy)))); + .andExpect(jsonPath("$", is(ResourcePolicyMatcher.matchResourcePolicy(resourcePolicy)))); } @Test @@ -273,7 +271,7 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio String authToken = getAuthToken(eperson1.getEmail(), "qwerty01"); getClient(authToken).perform(get("/api/authz/resourcepolicies/" + resourcePolicy.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is(ResoucePolicyMatcher.matchResourcePolicy(resourcePolicy)))); + .andExpect(jsonPath("$", is(ResourcePolicyMatcher.matchResourcePolicy(resourcePolicy)))); String authTokenEperson2 = getAuthToken(eperson2.getEmail(), "qwerty02"); getClient(authTokenEperson2).perform(get("/api/authz/resourcepolicies/" + resourcePolicy.getID())) @@ -316,9 +314,9 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.contains( - ResoucePolicyMatcher.matchResourcePolicy(resourcePolicyOfEPerson1)))) + ResourcePolicyMatcher.matchResourcePolicy(resourcePolicyOfEPerson1)))) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.not(is(ResoucePolicyMatcher.matchResourcePolicy(resourcePolicyOfEPerson2))))) + Matchers.not(is(ResourcePolicyMatcher.matchResourcePolicy(resourcePolicyOfEPerson2))))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString( "api/authz/resourcepolicies/search/eperson"))) .andExpect(jsonPath("$.page.totalElements", is(1))); @@ -361,18 +359,17 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .param("resource", community.getID().toString())) .andExpect(status().isOk()).andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.containsInAnyOrder( - ResoucePolicyMatcher.matchResourcePolicy(resourcePolicyOfCommunity), - ResoucePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfCommunity) + ResourcePolicyMatcher.matchResourcePolicy(resourcePolicyOfCommunity), + ResourcePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfCommunity) ))) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.not(is(ResoucePolicyMatcher.matchResourcePolicy(resourcePolicyOfCollection))))) + Matchers.not(is(ResourcePolicyMatcher.matchResourcePolicy(resourcePolicyOfCollection))))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/authz/resourcepolicies/search/eperson"))) .andExpect(jsonPath("$.page.totalElements", is(2))); } @Test - @Ignore("Currently fail due to https://jira.lyrasis.org/browse/DS-4428") public void findResoucesPoliciesEPersonWithoutParametersBadRequestTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -499,11 +496,11 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.containsInAnyOrder( - ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfEPerson1), - ResoucePolicyMatcher.matchResourcePolicy(resourcePolicyAnonymous) + ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfEPerson1), + ResourcePolicyMatcher.matchResourcePolicy(resourcePolicyAnonymous) ))) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.not(is(ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfEPerson2))))) + Matchers.not(is(ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfEPerson2))))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/authz/resourcepolicies/search/resource"))) .andExpect(jsonPath("$.page.totalElements", is(2))); @@ -551,10 +548,10 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.contains( - ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfEPerson2) + ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfEPerson2) ))) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.not(is(ResoucePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfEPerson2))))) + Matchers.not(is(ResourcePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfEPerson2))))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/authz/resourcepolicies/search/resource"))) .andExpect(jsonPath("$.page.totalElements", is(1))); @@ -620,10 +617,9 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.page.size", is(2))); } - @Test - @Ignore("Currently fail due to https://jira.lyrasis.org/browse/DS-4428") - public void findResoucesPoliciesOfResourceWithoutParametersBadRequestTest() throws Exception { - context.turnOffAuthorisationSystem(); + @Test + public void findResoucesPoliciesOfResourceWithoutParametersBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); EPerson eperson1 = EPersonBuilder.createEPerson(context) .withEmail("eperson1@mail.com") @@ -767,11 +763,11 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()).andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.containsInAnyOrder( - ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup1), - ResoucePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfGroup1), - ResoucePolicyMatcher.matchResourcePolicy(collectionResourcePolicyOfGroup1)))) + ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup1), + ResourcePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfGroup1), + ResourcePolicyMatcher.matchResourcePolicy(collectionResourcePolicyOfGroup1)))) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.not(is(ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup2))))) + Matchers.not(is(ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup2))))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/authz/resourcepolicies/search/group"))) .andExpect(jsonPath("$.page.totalElements", is(3))); @@ -783,7 +779,7 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()).andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.contains( - ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup2)))) + ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup2)))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/authz/resourcepolicies/search/group"))) .andExpect(jsonPath("$.page.totalElements", is(1))); @@ -822,16 +818,15 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .param("resource", community.getID().toString())) .andExpect(status().isOk()).andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.contains(ResoucePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup1)))) + Matchers.contains(ResourcePolicyMatcher.matchResourcePolicy(firstResourcePolicyOfGroup1)))) .andExpect(jsonPath("$._embedded.resourcepolicies", - Matchers.not(is(ResoucePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfGroup1))))) + Matchers.not(is(ResourcePolicyMatcher.matchResourcePolicy(secondResourcePolicyOfGroup1))))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/authz/resourcepolicies/search/group"))) .andExpect(jsonPath("$.page.totalElements", is(1))); } @Test - @Ignore("Currently fail due to https://jira.lyrasis.org/browse/DS-4428") public void findResoucesPoliciesByGroupWithoutParametersBadRequestTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -975,9 +970,11 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .content(mapper.writeValueAsBytes(resourcePolicyRest)) .param("resource", community.getID().toString()) .param("eperson", eperson1.getID().toString()) + .param("projections", "full") .contentType(contentType)) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.name", is(resourcePolicyRest.getName())), hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), @@ -1160,7 +1157,7 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/authz/resourcepolicies/" + resourcePolicy.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is(ResoucePolicyMatcher.matchResourcePolicy(resourcePolicy)))) + .andExpect(jsonPath("$", is(ResourcePolicyMatcher.matchResourcePolicy(resourcePolicy)))) .andExpect(jsonPath("$._links.self.href", Matchers .containsString("/api/authz/resourcepolicies/" + resourcePolicy.getID()))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java index 1bc7ef7ffc..8a8c567293 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java @@ -11,11 +11,18 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; 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.fileUpload; 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.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.dspace.app.rest.builder.ClaimedTaskBuilder; @@ -27,32 +34,45 @@ import org.dspace.app.rest.builder.WorkflowItemBuilder; import org.dspace.app.rest.matcher.ClaimedTaskMatcher; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.PoolTaskMatcher; +import org.dspace.app.rest.matcher.WorkflowActionMatcher; import org.dspace.app.rest.matcher.WorkflowItemMatcher; +import org.dspace.app.rest.matcher.WorkflowStepMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.eperson.EPerson; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; /** * Test suite for the pooltasks and claimedtasks endpoints - * - * @author Andrea Bollini (andrea.bollini at 4science.it) * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * @author Maria Verdonck (Atmire) on 10/02/2020 */ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { + @Autowired + private XmlWorkflowFactory xmlWorkflowFactory; + + @Test /** * Retrieve a specific pooltask - * + * * @throws Exception */ public void findOnePoolTest() throws Exception { @@ -117,7 +137,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { @Test /** * Verify that only authenticated user can retrieve a specific pooltask - * + * * @throws Exception */ public void findOnePoolUnauthorizedTest() throws Exception { @@ -159,7 +179,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { @Test /** * Verify that an other reviewer cannot access a pooltask of a colleague - * + * * @throws Exception */ public void findOnePoolForbiddenTest() throws Exception { @@ -227,7 +247,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { /** * Create three workflowitem in three different collections with different, partially overlapping reviewers, and * verify that each reviewer get the proper list of tasks - * + * * @throws Exception */ public void findByUserTest() throws Exception { @@ -462,7 +482,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { /** * A delete request over a pooltask should result in a 405 Method not supported exception and no changes applied * workspace - * + * * @throws Exception */ public void deletePoolTaskTest() throws Exception { @@ -1439,7 +1459,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .withName("Sub Community") .build(); - // the reviewer2 is a reviewer in a different step for the col1 and with the same role than reviewer1 for + // the reviewer2 is a reviewer in a different step for the colAA1 and with the same role than reviewer1 for // another collection Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") .withWorkflowGroup(1, reviewer1) @@ -1613,7 +1633,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .withName("Sub Community") .build(); - // the reviewer2 is a reviewer in a different step for the col1 and with the same role than reviewer1 for + // the reviewer2 is a reviewer in a different step for the colAA1 and with the same role than reviewer1 for // another collection Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") .withWorkflowGroup(1, reviewer1) @@ -1799,13 +1819,15 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { AtomicReference idRef = new AtomicReference(); + Step step = xmlWorkflowFactory.getStepByName("reviewstep"); // step 1 getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/search/findByUser") - .param("uuid", reviewer1.getID().toString())) + .param("uuid", reviewer1.getID().toString()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( Matchers.allOf( Matchers.is(PoolTaskMatcher.matchPoolTask(null, "reviewstep")), + hasJsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step)), hasJsonPath("$._embedded.workflowitem", Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) @@ -1821,17 +1843,22 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .contentType(MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isNoContent()); + WorkflowActionConfig workflowAction = xmlWorkflowFactory.getActionByName("reviewaction"); + // get the id of the claimed task getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer1.getID().toString())) + .param("uuid", reviewer1.getID().toString()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step)), hasJsonPath("$._embedded.workflowitem", Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))), + hasJsonPath("$._embedded.action", + WorkflowActionMatcher.matchWorkflowActionEntry(workflowAction)) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -1850,13 +1877,16 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.inArchive", is(false))); + step = xmlWorkflowFactory.getStepByName("editstep"); + // step 2 getClient(reviewer2Token).perform(get("/api/workflow/pooltasks/search/findByUser") - .param("uuid", reviewer2.getID().toString())) + .param("uuid", reviewer2.getID().toString()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( Matchers.allOf( Matchers.is(PoolTaskMatcher.matchPoolTask(null, "editstep")), + hasJsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step)), hasJsonPath("$._embedded.workflowitem", Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) @@ -1872,17 +1902,22 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .contentType(MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isNoContent()); + workflowAction = xmlWorkflowFactory.getActionByName("editaction"); + // get the id of the claimed task getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer2.getID().toString())) + .param("uuid", reviewer2.getID().toString()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step)), hasJsonPath("$._embedded.workflowitem", Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))), + hasJsonPath("$._embedded.action", + WorkflowActionMatcher.matchWorkflowActionEntry(workflowAction)) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -1901,17 +1936,20 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.inArchive", is(false))); + step = xmlWorkflowFactory.getStepByName("finaleditstep"); + // step 3 getClient(reviewer3Token).perform(get("/api/workflow/pooltasks/search/findByUser") - .param("uuid", reviewer3.getID().toString())) + .param("uuid", reviewer3.getID().toString()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( Matchers.allOf( Matchers.is(PoolTaskMatcher.matchPoolTask(null, "finaleditstep")), + hasJsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step)), hasJsonPath("$._embedded.workflowitem", Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) - )))) + )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(1))) @@ -1923,17 +1961,21 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .contentType(MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isNoContent()); + workflowAction = xmlWorkflowFactory.getActionByName("finaleditaction"); // get the id of the claimed task getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer3.getID().toString())) + .param("uuid", reviewer3.getID().toString()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step)), hasJsonPath("$._embedded.workflowitem", Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))), + hasJsonPath("$._embedded.action", + WorkflowActionMatcher.matchWorkflowActionEntry(workflowAction)) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -1953,4 +1995,1103 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.inArchive", is(true))); } + @Test + /** + * Test the run of the default workflow where the wfi gets rejected in the first step (review step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilReviewStep_Reject() throws Exception { + + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A reviewer + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(1, reviewer1) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + Item item = witem.getItem(); + + context.restoreAuthSystemState(); + + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 1 + getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "reviewstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer1Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // reject the claimedTask with reason, default wf step 1 - review step + getClient(reviewer1Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_reject", "true") + .param("reason", "I need to test the reject in review step") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // verify that the task has been processed and is not anymore available + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/" + idRef.get())) + .andExpect(status().isNotFound()); + + // verify that the task is send back to the user and not to the pool + getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that the underline item is still unpublished + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))); + } + + @Test + /** + * Test the run of the default workflow where the reviewer attempts a non-valid option in the first step + * (review step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilReviewStep_NonValidOption_EditMetadata() throws Exception { + + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A reviewer + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(1, reviewer1) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 1 + getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "reviewstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer1Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // try non valid option (submit_edit_metadata), in default wf step 1 (review step) + getClient(reviewer1Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_edit_metadata", "true") + .param("reason", "I need to test the submit_edit_metadata") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isUnprocessableEntity()); + + // verify that the task has not been processed and is still available + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/" + idRef.get())) + .andExpect(status().isOk()); + } + + @Test + /** + * Test the run of the default workflow where the wfi gets rejected in the 2nd step (edit step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilEditStep_Reject() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. reviewer of 2nd step + EPerson reviewer2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(2, reviewer2) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + Item item = witem.getItem(); + + context.restoreAuthSystemState(); + + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 2 + getClient(reviewer2Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "editstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer2Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // reject the claimedTask, default wf step 2 (edit step) + getClient(reviewer2Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_reject", "true") + .param("reason", "I need to test the submit_reject in edit step") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // verify that the task has been processed and is not anymore available + getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/" + idRef.get())) + .andExpect(status().isNotFound()); + + // verify that the task is send back to the user and not to the pool + getClient(reviewer2Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that the underline item is still unpublished + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))); + } + + @Test + /** + * Test the run of the default workflow where the reviewer attempts a non-valid option in the 2d step (edit step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilEditStep_NonValidOption() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. reviewer of 2nd step + EPerson reviewer2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(2, reviewer2) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + Item item = witem.getItem(); + + context.restoreAuthSystemState(); + + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 2 + getClient(reviewer2Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "editstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer2Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // try non valid option (submit_edit_metadata), default wf step 2 (edit step) + getClient(reviewer2Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_non_valid_option", "true") + .param("reason", "I need to test an unvalid option") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isUnprocessableEntity()); + + // verify that the task has not been processed and is still available + getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/" + idRef.get())) + .andExpect(status().isOk()); + + // verify that the underline item is still unpublished + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))); + } + + @Test + /** + * Test the run of the default workflow where the reviewer attempts a reject option in the 3rd step + * (final edit step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilFinalEditStep_Reject() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. reviewer of 3rd step + EPerson reviewer3 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer3@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(3, reviewer3) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + Item item = witem.getItem(); + + context.restoreAuthSystemState(); + + String reviewer3Token = getAuthToken(reviewer3.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 3 + getClient(reviewer3Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "finaleditstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer3Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // reject the claimedTask, default wf step 3 (final edit step) + getClient(reviewer3Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_reject", "true") + .param("reason", "I need to test reject in fina edit step") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isUnprocessableEntity()); + + // verify that the task has not been processed and is still available + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/" + idRef.get())) + .andExpect(status().isOk()); + + // verify that the underline item is still unpublished + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))); + } + + @Test + /** + * Test the run of the default workflow where the reviewer attempts an edit metadata in the 3rd step + * (final edit step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilFinalEditStep_EditMetadata() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. reviewer of 3rd step + EPerson reviewer3 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer3@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(3, reviewer3) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + Item item = witem.getItem(); + + context.restoreAuthSystemState(); + + String reviewer3Token = getAuthToken(reviewer3.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 3 + getClient(reviewer3Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "finaleditstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer3Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // edit metadata of the claimedTask, default wf step 3 (final edit step) + getClient(reviewer3Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_edit_metadata", "true") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + } + + @Test + /** + * Test the run of the default workflow where the reviewer attempts a non-valid option in the 3rd step + * (final edit step) + * + * @throws Exception + */ + public void defaultWorkflowTest_UntilFinalEditStep_NonValidOption() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. reviewer of 3rd step + EPerson reviewer3 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer3@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(3, reviewer3) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + Item item = witem.getItem(); + + context.restoreAuthSystemState(); + + String reviewer3Token = getAuthToken(reviewer3.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 3 + getClient(reviewer3Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "finaleditstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer3Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // get the id of the claimed task + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.claimedtasks[0].id")))); + + // non valid option in the default wf step 3 (final edit step) + getClient(reviewer3Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_non_valid_option", "true") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isUnprocessableEntity()); + + // verify that the task has not been processed and is still available + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/" + idRef.get())) + .andExpect(status().isOk()); + + // verify that the underline item is still unpublished + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))); + } + + @Test + /** + * Test to try an upload on unclaimed task in step where edit_metadata is allowed + * + * @throws Exception + */ + public void unclaimedTaskTest_Upload_EditMetadataOptionAllowed() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. a reviewer + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(1, reviewer1) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //4. a pool task + PoolTask poolTask = PoolTaskBuilder.createPoolTask(context, col1, reviewer1) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + XmlWorkflowItem witem = poolTask.getWorkflowItem(); + poolTask.setStepID("editstep"); + poolTask.setActionID("editaction"); + + context.restoreAuthSystemState(); + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + + // try to upload file on workspace item while it is unclaimed + InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", + bibtex); + getClient(reviewer1Token).perform(fileUpload("/api/workflow/workflowitems/" + witem.getID()) + .file(bibtexFile)) + .andExpect(status().isUnprocessableEntity()); + + bibtex.close(); + } + + @Test + /** + * Test to try a patch on unclaimed task in step where edit_metadata is allowed + * + * @throws Exception + */ + public void unclaimedTaskTest_Patch_EditMetadataOptionAllowed() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. reviewer for second step + EPerson reviewer2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(2, reviewer2) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 2 + getClient(reviewer2Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "editstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // try to patch a workspace item while it is unclaimed + String authToken = getAuthToken(eperson.getEmail(), password); + + // 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(authToken).perform(patch("/api/workflow/workflowitems/" + witem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + /** + * Test to try an upload on a claimed task in step where edit_metadata is Not allowed (review step) + * + * @throws Exception + */ + public void uploadTest_ClaimedTask_EditMetadataOptionNotAllowed() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. two reviewers + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(1, reviewer1) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //4. a claimed task with workflow item in review step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, reviewer1) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("reviewstep"); + claimedTask.setActionID("reviewaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); + + context.restoreAuthSystemState(); + + // try to upload to a workspace item while it is in a step that does not have the edit_metadata option + // (review step) + String authToken = getAuthToken(reviewer1.getEmail(), password); + InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", + bibtex); + getClient(authToken).perform(fileUpload("/api/workflow/workflowitems/" + witem.getID()) + .file(bibtexFile)) + .andExpect(status().isUnprocessableEntity()); + + bibtex.close(); + } + + @Test + /** + * Test to try a patch on a claimed task that is in a step that does not allow edit_metadata (review step) + * + * @throws Exception + */ + public void patchTest_ClaimedTask_EditMetadataOptionNotAllowed() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. two reviewers + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + EPerson reviewer2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(1, reviewer1) + .withWorkflowGroup(2, reviewer2) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //3. create a workflowitem (so a pool task in step1) + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Test item full workflow") + .withIssueDate("2019-03-06") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + + // step 1 + getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.pooltasks", Matchers.contains( + Matchers.allOf( + Matchers.is(PoolTaskMatcher.matchPoolTask(null, "reviewstep")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "Test item full workflow", "2019-03-06", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andDo((result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.pooltasks[0].id")))); + + // claim the task + getClient(reviewer1Token).perform(post("/api/workflow/pooltasks/" + idRef.get()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // try to patch a workspace item while it is in a step that does not have the edit_metadata option (review step) + String authToken = getAuthToken(eperson.getEmail(), password); + + // 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(authToken).perform(patch("/api/workflow/workflowitems/" + witem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + /** + * Test to upload on claimed task in step where edit_metadata is allowed + * + * @throws Exception + */ + public void claimedTaskTest_Upload_EditMetadataOptionAllowed() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. a reviewer + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + 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") + .withWorkflowGroup(1, reviewer1) + .build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //4. a claimed task with workflow item in review step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, reviewer1) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); + + context.restoreAuthSystemState(); + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + + // upload a file on workspace item that is claimed and in the edit step + InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", + bibtex); + getClient(reviewer1Token).perform(fileUpload("/api/workflow/workflowitems/" + witem.getID()) + .file(bibtexFile)) + .andExpect(status().isCreated()); + + bibtex.close(); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java index 8f99811fa6..41e68f08c8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java @@ -281,13 +281,13 @@ public class UUIDLookupRestControllerIT extends AbstractControllerIntegrationTes @Test /** - * Test that a request with an uuid parameter that is not an actual UUID return a 422 Unprocessable Entity status + * Test that a request with an uuid parameter that is not an actual UUID return a 400 Bad Request status * * @throws Exception */ public void testInvalidUUID() throws Exception { getClient().perform(get("/api/dso/find?uuid={uuid}","invalidUUID")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java new file mode 100644 index 0000000000..884fc6cfa5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -0,0 +1,128 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.WorkflowActionMatcher; +import org.dspace.app.rest.model.WorkflowActionRest; +import org.dspace.app.rest.repository.WorkflowActionRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Integration tests for the {@link WorkflowActionRestRepository} controlled endpoints + * + * @author Maria Verdonck (Atmire) on 06/01/2020 + */ +public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegrationTest { + + private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + + private static final String WORKFLOW_ACTIONS_ENDPOINT + = "/api/" + WorkflowActionRest.CATEGORY + "/" + WorkflowActionRest.NAME_PLURAL; + + @Test + public void getAllWorkflowActions_NonImplementedEndpoint() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT)) + //We expect a 405 Method not allowed status + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void getAllWorkflowActions_NonImplementedEndpoint_NonValidToken() throws Exception { + String token = "nonValidToken"; + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT)) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getAllWorkflowActions_NonImplementedEndpoint_NoToken() throws Exception { + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_ACTIONS_ENDPOINT)) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } + + @Test + public void getWorkflowActionByName_NonExistentWorkflowAction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameNonExistentWorkflowActionName = "TestNameNonExistentWorkflowAction9999"; + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameNonExistentWorkflowActionName)) + //We expect a 404 Not Found status + .andExpect(status().isNotFound()); + } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_editaction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithOptions = "editaction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has options + .andExpect(jsonPath("$.options", not(empty()))) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) + ))); + } + + @Test + public void getWorkflowActionByName_ExistentWithoutOptions_claimaction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithoutOptions = "claimaction"; + WorkflowActionConfig existentWorkflowNoOptions = xmlWorkflowFactory.getActionByName(nameActionWithoutOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithoutOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has no options + .andExpect(jsonPath("$.options", empty())) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflowNoOptions) + ))); + } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_NonValidToken() throws Exception { + String token = "nonValidToken"; + String nameActionWithOptions = "editaction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_NoToken() throws Exception { + String nameActionWithOptions = "editaction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java new file mode 100644 index 0000000000..4a877825b9 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java @@ -0,0 +1,458 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalToIgnoringCase; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.matcher.WorkflowDefinitionMatcher; +import org.dspace.app.rest.model.WorkflowDefinitionRest; +import org.dspace.app.rest.repository.WorkflowDefinitionRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.state.Workflow; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Integration tests for the {@link WorkflowDefinitionRestRepository} + * and {@link WorkflowDefinitionCollectionsLinkRepository} controlled endpoints + * + * @author Maria Verdonck (Atmire) on 17/12/2019 + */ +public class WorkflowDefinitionRestRepositoryIT extends AbstractControllerIntegrationTest { + + private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + + private static final String WORKFLOW_DEFINITIONS_ENDPOINT + = "/api/" + WorkflowDefinitionRest.CATEGORY + "/" + WorkflowDefinitionRest.NAME_PLURAL; + + @Test + public void getAllWorkflowDefinitionsEndpoint() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + List allConfiguredWorkflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT)) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of configured workflows + .andExpect(jsonPath("$.page.totalElements", is(allConfiguredWorkflows.size()))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT))); + } + + @Test + public void getAllWorkflowDefinitionsEndpoint_Pagination_Size1() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + List allConfiguredWorkflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT) + .param("size", "1")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of configured workflows + .andExpect(jsonPath("$.page.totalElements", is(allConfiguredWorkflows.size()))) + //Page size is 1 + .andExpect(jsonPath("$.page.size", is(1))) + //Page nr is 1 + .andExpect(jsonPath("$.page.number", is(0))) + //Contains only the first configured workflow + .andExpect(jsonPath("$._embedded.workflowdefinitions", Matchers.contains( + WorkflowDefinitionMatcher.matchWorkflowDefinitionEntry(allConfiguredWorkflows.get(0)) + ))) + //Doesn't contain the other workflows + .andExpect(jsonPath("$._embedded.workflowdefinitions", Matchers.not( + Matchers.contains( + WorkflowDefinitionMatcher.matchWorkflowDefinitionEntry(allConfiguredWorkflows.get(1)) + ) + ))); + } + + @Test + public void getAllWorkflowDefinitionsEndpoint_Pagination_Size1_Page1() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + List allConfiguredWorkflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT) + .param("size", "1") + .param("page", "1")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of configured workflows + .andExpect(jsonPath("$.page.totalElements", is(allConfiguredWorkflows.size()))) + //Page size is 1 + .andExpect(jsonPath("$.page.size", is(1))) + //Page nr is 2 + .andExpect(jsonPath("$.page.number", is(1))) + //Contains only the second configured workflow + .andExpect(jsonPath("$._embedded.workflowdefinitions", Matchers.contains( + WorkflowDefinitionMatcher.matchWorkflowDefinitionEntry(allConfiguredWorkflows.get(1)) + ))) + //Doesn't contain 1st configured workflow + .andExpect(jsonPath("$._embedded.workflowdefinitions", Matchers.not( + Matchers.contains( + WorkflowDefinitionMatcher.matchWorkflowDefinitionEntry(allConfiguredWorkflows.get(0)) + ) + ))); + } + + @Test + public void getAllWorkflowDefinitionsEndpoint_NonValidToken() throws Exception { + String token = "NonValidToken"; + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT)) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getAllWorkflowDefinitionsEndpoint_NoToken() throws Exception { + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT)) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } + + @Test + public void getWorkflowDefinitionByName_DefaultWorkflow() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + String workflowName = defaultWorkflow.getID(); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + workflowName)) + //We expect a 200 OK status + .andExpect(status().isOk()) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT))) + // its name is default + .andExpect(jsonPath("$.name", equalToIgnoringCase(workflowName))) + // is default + .andExpect(jsonPath("$.isDefault", is(true))); + } + + @Test + public void getWorkflowDefinitionByName_NonDefaultWorkflow() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + List allConfiguredWorkflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); + String firstNonDefaultWorkflowName = ""; + for (Workflow workflow : allConfiguredWorkflows) { + if (!workflow.getID().equalsIgnoreCase(defaultWorkflow.getID())) { + firstNonDefaultWorkflowName = workflow.getID(); + } + } + if (StringUtils.isNotBlank(firstNonDefaultWorkflowName)) { + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + firstNonDefaultWorkflowName)) + //We expect a 200 OK status + .andExpect(status().isOk()) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT))) + // its name is name of non-default workflow + .andExpect(jsonPath("$.name", equalToIgnoringCase(firstNonDefaultWorkflowName))) + // is not default + .andExpect(jsonPath("$.isDefault", is(false))); + } + } + + @Test + public void getWorkflowDefinitionByName_NonExistentWorkflow() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String workflowName = "TestNameNonExistentWorkflow9999"; + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + workflowName)) + //We expect a 404 Not Found status + .andExpect(status().isNotFound()); + } + + @Test + public void getWorkflowDefinitionByName_DefaultWorkflow_NonValidToken() throws Exception { + String token = "UnvalidToken"; + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + String workflowName = defaultWorkflow.getID(); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + workflowName)) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getWorkflowDefinitionByName_DefaultWorkflow_NoToken() throws Exception { + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + String workflowName = defaultWorkflow.getID(); + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + workflowName)) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } + + @Test + public void getWorkflowDefinitionByCollectionId_ExistentCollection() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + context.restoreAuthSystemState(); + + Workflow workflowForThisCollection = xmlWorkflowFactory.getWorkflow(col1); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/search/findByCollection?uuid=" + col1.getID())) + //We expect a 200 OK status + .andExpect(status().isOk()) + // its name is name of corresponding workflow + .andExpect(jsonPath("$.name", equalToIgnoringCase(workflowForThisCollection.getID()))); + } + + @Test + public void getWorkflowDefinitionByCollectionId_nonValidUUID() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nonValidUUID = "TestNonValidUUID"; + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/search/findByCollection?uuid=" + nonValidUUID)) + //We expect a 400 Illegal Argument Exception (Bad Request) cannot convert UUID + .andExpect(status().isBadRequest()) + .andExpect(status().reason(containsString("Failed to convert " + nonValidUUID))); + } + + @Test + public void getWorkflowDefinitionByCollectionId_nonExistentCollection() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + UUID nonExistentCollectionUUID = UUID.randomUUID(); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/search/findByCollection?uuid=" + + nonExistentCollectionUUID)) + //We expect a 404 Not Found status + .andExpect(status().isNotFound()); + } + + @Test + public void getCollectionsOfWorkflowByName_DefaultWorkflow_AllNonMappedCollections() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + List allNonMappedCollections = xmlWorkflowFactory.getAllNonMappedCollectionsHandles(context); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/collections")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of non-mapped collections + .andExpect(jsonPath("$.page.totalElements", is(allNonMappedCollections.size()))); + } + + @Test + public void getCollectionsOfWorkflowByName_DefaultWorkflow_AllNonMappedCollections_Paginated_Size1() + throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. 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(); + Collection col2 = CollectionBuilder.createCollection(context, child1, "123456789/non-mapped-collection") + .withName("Collection 2") + .build(); + context.restoreAuthSystemState(); + + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + List allNonMappedCollections = xmlWorkflowFactory.getAllNonMappedCollectionsHandles(context); + + if (allNonMappedCollections.size() > 0) { + Collection firstNonMappedCollection = allNonMappedCollections.get(0); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/collections") + .param("size", "1")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of configured workflows + .andExpect(jsonPath("$.page.totalElements", is(allNonMappedCollections.size()))) + //Page size is 1 + .andExpect(jsonPath("$.page.size", is(1))) + //Page nr is 1 + .andExpect(jsonPath("$.page.number", is(0))) + //Contains only the first non-mapped collection + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + WorkflowDefinitionMatcher.matchCollectionEntry(firstNonMappedCollection.getName(), + firstNonMappedCollection.getID(), firstNonMappedCollection.getHandle()) + ))); + } + } + + @Test + public void getCollectionsOfWorkflowByName_NonDefaultWorkflow() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + // Collection with handle used in workflow.xml! + Collection col1 = CollectionBuilder.createCollection(context, child1, "123456789/workflow-test-1") + .withName("Collection 1") + .build(); + context.restoreAuthSystemState(); + + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + List allConfiguredWorkflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); + String firstNonDefaultWorkflowName = ""; + for (Workflow workflow : allConfiguredWorkflows) { + if (!workflow.getID().equalsIgnoreCase(defaultWorkflow.getID())) { + firstNonDefaultWorkflowName = workflow.getID(); + } + } + + if (StringUtils.isNotBlank(firstNonDefaultWorkflowName)) { + List mappedCollections + = xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(context, firstNonDefaultWorkflowName); + //When we call this facets endpoint + if (mappedCollections.size() > 0) { + //returns array of collection jsons that are mapped to given workflow + //When we call this facets endpoint + Collection firstMappedCollection = mappedCollections.get(0); + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + firstNonDefaultWorkflowName + + "/collections") + .param("size", "1")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of configured workflows + .andExpect(jsonPath("$.page.totalElements", is(mappedCollections.size()))) + //Page size is 1 + .andExpect(jsonPath("$.page.size", is(1))) + //Page nr is 1 + .andExpect(jsonPath("$.page.number", is(0))) + //Contains only the first mapped collection + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + WorkflowDefinitionMatcher.matchCollectionEntry(firstMappedCollection.getName(), + firstMappedCollection.getID(), firstMappedCollection.getHandle()) + ))); + } else { + //no collections mapped to this workflow + getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + + firstNonDefaultWorkflowName + "/collections")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //results in empty list + .andExpect(jsonPath("$._embedded.collections", empty())); + } + } + } + + @Test + public void getCollectionsOfWorkflowByName_NonExistentWorkflow() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String workflowName = "TestNameNonExistentWorkflow9999"; + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + workflowName + "/collections")) + //We expect a 404 Not Found + .andExpect(status().isNotFound()); + } + + @Test + public void getCollectionsOfWorkflowByName_DefaultWorkflow_NoValidToken() throws Exception { + String token = "NonValidToken"; + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/collections")) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getCollectionsOfWorkflowByName_DefaultWorkflow_NoToken() throws Exception { + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + List allNonMappedCollections = xmlWorkflowFactory.getAllNonMappedCollectionsHandles(context); + + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/collections")) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } + + @Test + public void getStepsOfWorkflowByName_DefaultWorkflow() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/steps") + .param("projection", "full")) + //We expect a 200 OK status + .andExpect(status().isOk()) + //Number of total workflows is equals to number of non-mapped collections + .andExpect(jsonPath("$.page.totalElements", is(defaultWorkflow.getSteps().size()))); + } + + @Test + public void getStepsOfWorkflowByName_DefaultWorkflow_NoValidToken() throws Exception { + String token = "NonValidToken"; + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/steps")) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getStepsOfWorkflowByName_DefaultWorkflow_NoToken() throws Exception { + Workflow defaultWorkflow = xmlWorkflowFactory.getDefaultWorkflow(); + List allNonMappedCollections = xmlWorkflowFactory.getAllNonMappedCollectionsHandles(context); + + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + defaultWorkflow.getID() + + "/steps")) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } +} 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 af2d6b6c53..5cb79fba2c 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 @@ -39,6 +39,7 @@ import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.WorkflowItemMatcher; +import org.dspace.app.rest.matcher.WorkflowStepMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; @@ -52,6 +53,8 @@ import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; @@ -70,6 +73,8 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Autowired private ConfigurationService configurationService; + @Autowired + private XmlWorkflowFactory xmlWorkflowFactory; @Before @Override @@ -84,7 +89,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * All the workflowitems should be returned regardless of the collection where they were created - * + * * @throws Exception */ public void findAllTest() throws Exception { @@ -139,7 +144,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * The workflowitem endpoint must provide proper pagination - * + * * @throws Exception */ public void findAllWithPaginationTest() throws Exception { @@ -207,7 +212,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * The findAll should be available only to admins regardless to having or less a role in the workflow - * + * * @throws Exception */ public void findAllForbiddenTest() throws Exception { @@ -258,7 +263,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * The workflowitem resource endpoint must expose the proper structure - * + * * @throws Exception */ public void findOneTest() throws Exception { @@ -296,7 +301,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * The workflowitem resource endpoint should be visible only to member of the corresponding workflow step - * + * * @throws Exception */ public void findOneForbiddenTest() throws Exception { @@ -429,7 +434,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * The workflowitem resource endpoint must expose the proper structure - * + * * @throws Exception */ public void findOneRelsTest() throws Exception { @@ -458,8 +463,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID() + "/collection") - .param("projection", "full")) + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID() + "/collection")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers .is(CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle())))); @@ -478,7 +482,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT @Test /** * Check the response code for unexistent workflowitem - * + * * @throws Exception */ public void findOneWrongIDTest() throws Exception { @@ -495,7 +499,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT /** * Create three workflowitem with two different submitter and verify that the findBySubmitter return the proper * list of workflowitem for each submitter also paginating - * + * * @throws Exception */ public void findBySubmitterTest() throws Exception { @@ -602,7 +606,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT /** * A delete request over a workflowitem should result in abort the workflow sending the item back to the submitter * workspace - * + * * @throws Exception */ public void deleteOneTest() throws Exception { @@ -767,6 +771,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT // submit the workspaceitem to start the workflow getClient(authToken) .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .param("projection", "full") .content("/api/submission/workspaceitems/" + wsitem.getID()) .contentType(textUriContentType)) .andExpect(status().isCreated()) @@ -908,12 +913,16 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT context.setCurrentUser(submitter); - //3. a workflow item - XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withTitle("Workflow Item 1") - .withIssueDate("2017-10-17") - .withSubject("ExtraEntry") - .build(); + //3. a claimed task with workflow item in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); context.restoreAuthSystemState(); @@ -936,7 +945,6 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT // check the new title and untouched values Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, "New Title", "2017-10-17", "ExtraEntry")))); - ; // verify that the patch changes have been persisted getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID())) @@ -1033,12 +1041,15 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT context.setCurrentUser(submitter); - //3. a workflow item - XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withTitle("Workflow Item 1") - .withIssueDate("2017-10-17") - .withSubject("ExtraEntry") - .build(); + //3. a claimed task with workflow item in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); context.restoreAuthSystemState(); @@ -1098,30 +1109,40 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT context.setCurrentUser(submitter); - //3. some workflow items for our test - XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withTitle("Workflow Item 1") - .withIssueDate("2017-10-17") - .withSubject("ExtraEntry") - .build(); + //3. some claimed tasks with workflow items in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); - XmlWorkflowItem witemMultipleSubjects = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withTitle("Workflow Item 2") - .withIssueDate("2017-10-17") - .withSubject("Subject1") - .withSubject("Subject2") - .withSubject("Subject3") - .withSubject("Subject4") - .build(); + ClaimedTask claimedTask2 = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Workflow Item 2") + .withIssueDate("2017-10-17") + .withSubject("Subject1") + .withSubject("Subject2") + .withSubject("Subject3") + .withSubject("Subject4") + .build(); + claimedTask2.setStepID("editstep"); + claimedTask2.setActionID("editaction"); + XmlWorkflowItem witemMultipleSubjects = claimedTask2.getWorkflowItem(); - XmlWorkflowItem witemWithTitleDateAndSubjects = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withTitle("Workflow Item 3") - .withIssueDate("2017-10-17") - .withSubject("Subject1") - .withSubject("Subject2") - .withSubject("Subject3") - .withSubject("Subject4") - .build(); + ClaimedTask claimedTask3 = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Workflow Item 3") + .withIssueDate("2017-10-17") + .withSubject("Subject1") + .withSubject("Subject2") + .withSubject("Subject3") + .withSubject("Subject4") + .build(); + claimedTask3.setStepID("editstep"); + claimedTask3.setActionID("editaction"); + XmlWorkflowItem witemWithTitleDateAndSubjects = claimedTask3.getWorkflowItem(); context.restoreAuthSystemState(); @@ -1288,11 +1309,14 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT context.setCurrentUser(submitter); - //3. some workflow items for our test - XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withIssueDate("2017-10-17") - .withSubject("ExtraEntry") - .build(); + //3. a claimed task with workflow item in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); context.restoreAuthSystemState(); @@ -1353,11 +1377,16 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT context.setCurrentUser(submitter); - //3. some workflow items for our test - XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) - .withTitle("Test WorkflowItem") - .withIssueDate("2017-10-17") - .build(); + //3. a claimed task with workflow item in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); context.restoreAuthSystemState(); @@ -1534,4 +1563,84 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT .andExpect(jsonPath("$.sections.traditionalpagetwo['dc.subject'][5].value", is("Final Subject"))) ; } + + @Test + public void stepEmbedTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. A community-collection structure with one parent community with sub-community and three collections + // (different workflow steps and reviewers). + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + EPerson reviewer1 = EPersonBuilder.createEPerson(context).withEmail("reviewer1@example.com") + .withPassword(password).build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") + .withWorkflowGroup(1, reviewer1).build(); + + EPerson reviewer2 = EPersonBuilder.createEPerson(context).withEmail("reviewer2@example.com") + .withPassword(password).build(); + + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2") + .withWorkflowGroup(2, reviewer2).build(); + + EPerson reviewer3 = EPersonBuilder.createEPerson(context).withEmail("reviewer3@example.com") + .withPassword(password).build(); + + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("Collection 3") + .withWorkflowGroup(3, reviewer3).build(); + + //2. three workflow items in the three collections (this will lead to pool task) + XmlWorkflowItem witem1 = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Workflow Item 1") + .withIssueDate("2016-02-13") + .build(); + + XmlWorkflowItem witem2 = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withTitle("Workflow Item 2") + .withIssueDate("2016-02-13") + .build(); + + XmlWorkflowItem witem3 = WorkflowItemBuilder.createWorkflowItem(context, col3) + .withTitle("Workflow Item 3") + .withIssueDate("2016-02-13") + .build(); + + Step step = xmlWorkflowFactory.getStepByName("reviewstep"); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/workflow/workflowitems/" + witem1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + WorkflowItemMatcher.matchItemWithTitleAndDateIssued(witem1, + "Workflow Item 1", "2016-02-13"))) + .andExpect(jsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step))); + + step = xmlWorkflowFactory.getStepByName("editstep"); + + getClient(token).perform(get("/api/workflow/workflowitems/" + witem2.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + WorkflowItemMatcher.matchItemWithTitleAndDateIssued(witem2, + "Workflow Item 2", "2016-02-13"))) + .andExpect(jsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step))); + + step = xmlWorkflowFactory.getStepByName("finaleditstep"); + + getClient(token).perform(get("/api/workflow/workflowitems/" + witem3.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + WorkflowItemMatcher.matchItemWithTitleAndDateIssued(witem3, + "Workflow Item 3", "2016-02-13"))) + .andExpect(jsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowStepRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowStepRestRepositoryIT.java new file mode 100644 index 0000000000..a9a5b12d94 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowStepRestRepositoryIT.java @@ -0,0 +1,87 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.WorkflowStepMatcher; +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.app.rest.repository.WorkflowStepRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.state.Step; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Integration tests for the {@link WorkflowStepRestRepository} controlled endpoints + * + * @author Maria Verdonck (Atmire) on 13/01/2020 + */ +public class WorkflowStepRestRepositoryIT extends AbstractControllerIntegrationTest { + + private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + + private static final String WORKFLOW_ACTIONS_ENDPOINT + = "/api/" + WorkflowStepRest.CATEGORY + "/" + WorkflowStepRest.NAME_PLURAL; + + @Test + public void getAllWorkflowSteps_NonImplementedEndpoint() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT)) + //We expect a 405 Method not allowed status + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void getAllWorkflowSteps_NonImplementedEndpoint_NonValidToken() throws Exception { + String token = "NonValidToken"; + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT)) + //We expect a 403 Forbidden status + .andExpect(status().isForbidden()); + } + + @Test + public void getAllWorkflowSteps_NonImplementedEndpoint_NoToken() throws Exception { + //When we call this facets endpoint + getClient().perform(get(WORKFLOW_ACTIONS_ENDPOINT)) + //We expect a 401 Unauthorized + .andExpect(status().isUnauthorized()); + } + + @Test + public void getWorkflowStepByName_NonExistentWorkflowStep() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameNonExistentWorkflowActionName = "TestNameNonExistentWorkflowStep9999"; + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameNonExistentWorkflowActionName)) + //We expect a 404 Not Found status + .andExpect(status().isNotFound()); + } + + @Test + public void getWorkflowStepByName_ExistentStep_reviewstep() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameStep = "reviewstep"; + Step existentStep = xmlWorkflowFactory.getStepByName(nameStep); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameStep) + .param("projection", "full")) + //We expect a 200 is ok status + .andExpect(status().isOk()) + //Matches expected step + .andExpect(jsonPath("$", Matchers.is( + WorkflowStepMatcher.matchWorkflowStepEntry(existentStep) + ))); + } +} 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 448fd06bbd..d0b018b8b3 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 @@ -8,6 +8,9 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; import static org.springframework.http.MediaType.parseMediaType; @@ -29,12 +32,14 @@ import java.util.UUID; import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; import org.dspace.app.rest.builder.BitstreamBuilder; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.EPersonBuilder; +import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; @@ -51,6 +56,8 @@ 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.eperson.factory.EPersonServiceFactory; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; @@ -69,14 +76,34 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Autowired private ConfigurationService configurationService; + private Group embargoedGroups; + private Group embargoedGroup1; + private Group embargoedGroup2; + private Group anonymousGroup; + @Before @Override public void setUp() throws Exception { - super.setUp(); + context.turnOffAuthorisationSystem(); - //disable file upload mandatory - configurationService.setProperty("webui.submit.upload.required", false); + embargoedGroups = GroupBuilder.createGroup(context) + .withName("Embargoed Groups") + .build(); + + embargoedGroup1 = GroupBuilder.createGroup(context) + .withName("Embargoed Group 1") + .withParent(embargoedGroups) + .build(); + + embargoedGroup2 = GroupBuilder.createGroup(context) + .withName("Embargoed Group 2") + .withParent(embargoedGroups) + .build(); + + anonymousGroup = EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS); + + context.restoreAuthSystemState(); } @Test @@ -259,8 +286,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/workspaceitems/" + witem.getID() + "/collection") - .param("projection", "full")) + getClient(token).perform(get("/api/submission/workspaceitems/" + witem.getID() + "/collection")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers .is(CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle())) @@ -468,14 +494,14 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); String authToken = getAuthToken(admin.getEmail(), password); - // create a workspaceitem explicitly in the col1 + // create a workspaceitem explicitly in the colAA1 getClient(authToken).perform(post("/api/submission/workspaceitems") .param("owningCollection", col1.getID().toString()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))); - // create a workspaceitem explicitly in the col2 + // create a workspaceitem explicitly in the colAA2 getClient(authToken).perform(post("/api/submission/workspaceitems") .param("owningCollection", col2.getID().toString()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) @@ -484,10 +510,11 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem without an explicit collection, this will go in the first valid collection for the // user: the col1 - getClient(authToken).perform(post("/api/submission/workspaceitems") - .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + getClient(authToken).perform(post("/api/submission/workspaceitems").param("projection", "full") + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))); + .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))) + .andExpect(jsonPath("$", WorkspaceItemMatcher.matchFullEmbeds())); // TODO cleanup the context!!! } @@ -518,7 +545,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", bibtex); - // bulk create workspaceitems in the default collection (col1) + // bulk create workspaceitems in the default collection (colAA1) getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") .file(bibtexFile)) // bulk create should return 200, 201 (created) is better for single resource @@ -539,7 +566,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) ; - // bulk create workspaceitems explicitly in the col2 + // bulk create workspaceitems explicitly in the colAA2 getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") .file(bibtexFile) .param("collection", col2.getID().toString())) @@ -636,6 +663,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withIssueDate("2017-10-17") .build(); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + getClient(authToken).perform(get("/api/submission/workspaceitems/" + workspaceItem1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()) @@ -654,7 +684,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration ))))) ; - // create an empty workspaceitem explicitly in the col1, check validation on creation + // create an empty workspaceitem explicitly in the colAA1, check validation on creation getClient(authToken).perform(post("/api/submission/workspaceitems") .param("collection", col1.getID().toString()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) @@ -696,6 +726,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withSubject("ExtraEntry") .build(); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + // a simple patch to update an existent metadata List updateTitle = new ArrayList(); Map value = new HashMap(); @@ -725,6 +758,140 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration ; } + @Test + public void patchReplaceMetadataOnItemStillInSubmissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + 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(); + + context.setCurrentUser(eperson); + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + List updateTitle = new ArrayList(); + Map value = new HashMap(); + value.put("value", "New Title"); + updateTitle.add(new ReplaceOperation("/metadata/dc.title/0", value)); + + String patchBody = getPatchContent(updateTitle); + UUID idItem = witem.getItem().getID(); + + // Verify submitter cannot modify metadata via item PATCH. They must use submission forms. + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(patch("/api/core/items/" + idItem) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/items/" + idItem)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(ItemMatcher.matchItemWithTitleAndDateIssued + (witem.getItem(), "Workspace Item 1", "2017-10-17")))); + } + + @Test + public void patchAddMetadataOnItemStillInSubmissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + 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(); + + context.setCurrentUser(eperson); + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + List addIssueDate = new ArrayList(); + Map value = new HashMap(); + value.put("value", "2017-10-17"); + addIssueDate.add(new ReplaceOperation("/metadata/dc.date.issued/0", value)); + + String patchBody = getPatchContent(addIssueDate); + UUID idItem = witem.getItem().getID(); + + // Verify submitter cannot modify metadata via item PATCH. They must use submission forms. + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(patch("/api/core/items/" + idItem) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/items/" + idItem)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata", allOf( + matchMetadata("dc.title", "Workspace"))))) + .andExpect(jsonPath("$.metadata.['dc.date.issued']").doesNotExist()); + } + + @Test + public void patchRemoveMetadataOnItemStillInSubmissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + 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(); + + context.setCurrentUser(eperson); + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace title") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + List removeTitle = new ArrayList(); + removeTitle.add(new RemoveOperation("/metadata/dc.title/0")); + + String patchBody = getPatchContent(removeTitle); + UUID idItem = witem.getItem().getID(); + + // Verify submitter cannot modify metadata via item PATCH. They must use submission forms. + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(patch("/api/core/items/" + idItem) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/items/" + idItem)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata", allOf( + matchMetadata("dc.title", "Workspace title"), + matchMetadata("dc.date.issued", "2017-10-17"))))); + } + @Test /** * Test delete of a metadata @@ -802,6 +969,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration null, "2017-10-17", "ExtraEntry")))) ; + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + // try to remove a metadata in a specific position List removeMidSubject = new ArrayList(); removeMidSubject.add(new RemoveOperation("/sections/traditionalpagetwo/dc.subject/1")); @@ -931,6 +1101,8 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withSubject("ExtraEntry") .build(); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); // try to add the title List addTitle = new ArrayList(); @@ -988,6 +1160,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withIssueDate("2017-10-17") .build(); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + // try to add multiple subjects at once List addSubjects = new ArrayList(); // create a list of values to use in add operation @@ -1209,6 +1384,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.license.url").isEmpty()) ; + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + // try to grant the license with an add operation List addGrant = new ArrayList(); addGrant.add(new AddOperation("/sections/license/granted", true)); @@ -1358,6 +1536,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .grantLicense() .build(); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + // check that our workspaceitems come with a license (all are build in the same way, just check the first) getClient().perform(get("/api/submission/workspaceitems/" + witem.getID())) .andExpect(status().isOk()) @@ -1600,6 +1781,147 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration ; } + @Test + public void patchUploadAddAccessConditionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, collection1) + .withTitle("Test WorkspaceItem") + .withIssueDate("2019-10-01") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + + context.restoreAuthSystemState(); + + // create a list of values to use in add accessCondition + List addAccessCondition = new ArrayList(); + Map value = new HashMap(); + value.put("name", "embargoedWithGroupSelect"); + value.put("groupUUID", embargoedGroup1.getID().toString()); + value.put("endDate", "2030-10-02"); + addAccessCondition.add(new AddOperation("/sections/upload/files/0/accessConditions/-", value)); + + String patchBody = getPatchContent(addAccessCondition); + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.allOf( + hasJsonPath("$.sections.upload.files[0].accessConditions[0].name", + is("embargoedWithGroupSelect")), + hasJsonPath("$.sections.upload.files[0].accessConditions[0].groupUUID", + is(embargoedGroup1.getID().toString())) + ))); + + // verify that the patch changes have been persisted + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.allOf( + hasJsonPath("$.sections.upload.files[0].accessConditions[0].name", + is("embargoedWithGroupSelect")), + hasJsonPath("$.sections.upload.files[0].accessConditions[0].groupUUID", + is(embargoedGroup1.getID().toString())) + ))); + } + + @Test + public void patchUploadRemoveAccessConditionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, collection1) + .withTitle("Test WorkspaceItem") + .withIssueDate("2019-10-01") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + + context.restoreAuthSystemState(); + + // create a list of values to use in add operation + List addAccessCondition = new ArrayList(); + Map value = new HashMap(); + value.put("name", "embargoedWithGroupSelect"); + value.put("groupUUID", embargoedGroup1.getID().toString()); + value.put("endDate", "2020-01-01"); + addAccessCondition.add(new AddOperation("/sections/upload/files/0/accessConditions/-", value)); + + String patchBody = getPatchContent(addAccessCondition); + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.allOf( + hasJsonPath("$.sections.upload.files[0].accessConditions[0].name", + is("embargoedWithGroupSelect")), + hasJsonPath("$.sections.upload.files[0].accessConditions[0].endDate", + is("2020-01-01")), + hasJsonPath("$.sections.upload.files[0].accessConditions[0].groupUUID", + is(embargoedGroup1.getID().toString())) + ))); + + // verify that the patch changes have been persisted + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.allOf( + hasJsonPath("$.sections.upload.files[0].accessConditions[0].name", + is("embargoedWithGroupSelect")), + hasJsonPath("$.sections.upload.files[0].accessConditions[0].endDate", + is("2020-01-01")), + hasJsonPath("$.sections.upload.files[0].accessConditions[0].groupUUID", + is(embargoedGroup1.getID().toString())) + ))); + + // create a list of values to use in remove operation + List removeAccessCondition = new ArrayList(); + removeAccessCondition.add(new RemoveOperation("/sections/upload/files/0/accessConditions")); + + String patchReplaceBody = getPatchContent(removeAccessCondition); + getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchReplaceBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.allOf( + hasJsonPath("$.sections.upload.files[0].accessConditions",hasSize(0))))); + + // verify that the patch changes have been persisted + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.allOf( + hasJsonPath("$.sections.upload.files[0].accessConditions", hasSize(0)) + ))); + } + @Test /** * Test the upload of files in the upload over section @@ -1671,8 +1993,6 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withIssueDate("2017-10-17") .build(); - configurationService.setProperty("webui.submit.upload.required", true); - InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); final MockMultipartFile pdfFile = new MockMultipartFile("file", "/local/path/simple-article.pdf", "application/pdf", pdf); @@ -1714,8 +2034,6 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withIssueDate("2017-10-17") .build(); - configurationService.setProperty("webui.submit.upload.required", true); - //Verify there is an error since no file was uploaded (with upload required set to true) getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())) .andExpect(status().isOk()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java index ffc8f56931..269206a8ce 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java @@ -42,6 +42,13 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { return builder.create(parent); } + public static CollectionBuilder createCollection(final Context context, + final Community parent, + final String handle) { + CollectionBuilder builder = new CollectionBuilder(context); + return builder.create(parent, handle); + } + private CollectionBuilder create(final Community parent) { try { this.collection = collectionService.create(context, parent); @@ -51,6 +58,20 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { return this; } + private CollectionBuilder create(final Community parent, final String handle) { + try { + for (Collection collection : this.collectionService.findAll(context)) { + if (collection.getHandle().equalsIgnoreCase(handle)) { + this.collection = collection; + } + } + this.collection = this.collectionService.create(context, parent, handle); + } catch (Exception e) { + return handleException(e); + } + return this; + } + public CollectionBuilder withName(final String name) { return setMetadataSingleValue(collection, MetadataSchemaEnum.DC.getName(), "title", null, name); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java index ff9ef289a4..788aa502a6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java @@ -19,6 +19,8 @@ import org.dspace.content.Community; import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; /** * Builder to construct Community objects @@ -74,6 +76,29 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { return this; } + /** + * Create an admin group for the community with the specified members + * + * @param members epersons to add to the admin group + * @return this builder + * @throws SQLException + * @throws AuthorizeException + */ + public CommunityBuilder withAdminGroup(EPerson... members) throws SQLException, AuthorizeException { + Group g = communityService.createAdministrators(context, community); + for (EPerson e : members) { + groupService.addMember(context, g, e); + } + groupService.update(context, g); + return this; + } + + public CommunityBuilder addParentCommunity(final Context context, final Community parent) + throws SQLException, AuthorizeException { + communityService.addSubcommunity(context, parent, community); + return this; + } + @Override public Community build() { try { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java index 32672f5427..a761099f83 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java @@ -7,7 +7,9 @@ */ package org.dspace.app.rest.builder; +import java.io.IOException; import java.sql.SQLException; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.DSpaceObjectService; @@ -107,4 +109,20 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { ePersonService.setPassword(ePerson, password); return this; } + + public static void deleteEPerson(UUID uuid) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + EPerson ePerson = ePersonService.find(c, uuid); + if (ePerson != null) { + try { + ePersonService.delete(c, ePerson); + } catch (AuthorizeException e) { + // cannot occur, just wrap it to make the compiler happy + throw new RuntimeException(e); + } + } + c.complete(); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java index c03deeb7bc..995b88488c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java @@ -37,12 +37,15 @@ public class RelationshipTypeBuilder extends AbstractBuilder byRelationshipType = relationshipService.findByRelationshipType(context, relationshipType); - for (Relationship relationship : byRelationshipType) { - relationshipService.delete(context, relationship); + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + List byRelationshipType = relationshipService + .findByRelationshipType(c, relationshipType); + for (Relationship relationship : byRelationshipType) { + relationshipService.delete(c, relationship); + } + c.complete(); } - context.restoreAuthSystemState(); delete(relationshipType); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java index 57a0475b63..baad6f0904 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java @@ -41,7 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired; * Created by: Andrew Wood * Date: 26 Jul 2019 */ -public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTest { +public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest { //Common collection to utilize for test private Collection col1; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamFormatMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamFormatMatcher.java index 057c80598f..0bde0a2c54 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamFormatMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamFormatMatcher.java @@ -75,5 +75,4 @@ public class BitstreamFormatMatcher { hasJsonPath("$.type", is("bitstreamformat")) ); } - } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java index 58840bb798..d9497f182b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java @@ -66,6 +66,19 @@ public class BitstreamMatcher { ); } + public static Matcher matchBitstreamEntryWithoutEmbed(UUID uuid, long size) { + return allOf( + //Check ID and size + hasJsonPath("$.uuid", is(uuid.toString())), + hasJsonPath("$.sizeBytes", is((int) size)), + //Make sure we have a checksum + hasJsonPath("$.checkSum", matchChecksum()), + //Make sure we have a valid format + //Check links + matchLinks(uuid) + ); + } + private static Matcher matchChecksum() { return allOf( hasJsonPath("$.checkSumAlgorithm", not(empty())), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java index 317544b570..0d2ba67f16 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java @@ -36,7 +36,6 @@ public class ClaimedTaskMatcher { */ public static Matcher matchClaimedTask(ClaimedTask cTask, String step) { return allOf( - hasJsonPath("$.step", is(step)), // Check workflowitem properties matchProperties(cTask), // Check links diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java index bcede0e0c6..77c8247ae9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java @@ -31,8 +31,22 @@ public class CollectionMatcher { public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, Bitstream logo) { return allOf( matchProperties(name, uuid, handle), - matchLinks(uuid), - matchLogo(logo) + matchLinks(uuid) + ); + } + + public static Matcher matchCollectionEntryFullProjection(String name, UUID uuid, String handle) { + return matchCollectionEntryFullProjection(name, uuid, handle, null); + + } + + public static Matcher matchCollectionEntryFullProjection(String name, UUID uuid, String handle, + Bitstream logo) { + return allOf( + matchProperties(name, uuid, handle), + matchLinks(uuid), + matchLogo(logo), + matchFullEmbeds() ); } @@ -54,6 +68,7 @@ public class CollectionMatcher { return matchEmbeds( "license", "logo", + "parentCommunity", "mappedItems[]" ); } @@ -68,6 +83,7 @@ public class CollectionMatcher { "license", "logo", "mappedItems", + "parentCommunity", "self" ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java index aa5dd4659d..9a6bf242e1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java @@ -31,8 +31,6 @@ public class CommunityMatcher { hasJsonPath("$.uuid", is(uuid.toString())), hasJsonPath("$.handle", is(handle)), hasJsonPath("$.type", is("community")), - hasJsonPath("$._embedded.collections", Matchers.not(Matchers.empty())), - hasJsonPath("$._embedded.logo", Matchers.not(Matchers.empty())), matchLinks(uuid) ); } @@ -53,6 +51,13 @@ public class CommunityMatcher { } public static Matcher matchCommunityEntry(String name, UUID uuid, String handle) { + return allOf( + matchProperties(name, uuid, handle), + matchLinks(uuid) + ); + } + + public static Matcher matchCommunityEntryFullProjection(String name, UUID uuid, String handle) { return allOf( matchProperties(name, uuid, handle), hasJsonPath("$._embedded.collections", Matchers.not(Matchers.empty())), @@ -80,6 +85,7 @@ public class CommunityMatcher { return matchEmbeds( "collections[]", "logo", + "parentCommunity", "subcommunities[]" ); } @@ -92,6 +98,7 @@ public class CommunityMatcher { "collections", "logo", "self", + "parentCommunity", "subcommunities" ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java index a841c2ea8d..b53146af2b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java @@ -34,7 +34,7 @@ public class GroupMatcher { hasJsonPath("$.name", is(name)), hasJsonPath("$.type", is("group")), hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/")), - hasJsonPath("$._links.groups.href", endsWith("/groups")) + hasJsonPath("$._links.subgroups.href", endsWith("/subgroups")) ); } @@ -43,7 +43,8 @@ public class GroupMatcher { */ public static Matcher matchFullEmbeds() { return matchEmbeds( - "groups[]" + "subgroups[]", + "epersons[]" ); } @@ -52,7 +53,8 @@ public class GroupMatcher { */ public static Matcher matchLinks(UUID uuid) { return HalMatcher.matchLinks(REST_SERVER_URL + "eperson/groups/" + uuid, - "groups", + "subgroups", + "epersons", "self" ); } @@ -63,7 +65,8 @@ public class GroupMatcher { hasJsonPath("$.name", is(name)), hasJsonPath("$.type", is("group")), hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/" + uuid.toString())), - hasJsonPath("$._links.groups.href", endsWith(uuid.toString() + "/groups")) + hasJsonPath("$._links.subgroups.href", endsWith(uuid.toString() + "/subgroups")), + hasJsonPath("$._links.epersons.href", endsWith(uuid.toString() + "/epersons")) ); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java index 9ebca9d572..aeed70e527 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java @@ -36,7 +36,6 @@ public class PoolTaskMatcher { */ public static Matcher matchPoolTask(PoolTask pTask, String step) { return allOf( - hasJsonPath("$.step", is(step)), // Check workflowitem properties matchProperties(pTask), // Check links diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java index df7520cc83..068788a546 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -48,4 +49,13 @@ public class RelationshipMatcher { RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType)) ); } + + /** + * Gets a matcher for all expected embeds when the full projection is requested. + */ + public static Matcher matchFullEmbeds() { + return matchEmbeds( + "relationshipType" + ); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResoucePolicyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java similarity index 84% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResoucePolicyMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java index 2780c2b392..6c0d77c4f5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResoucePolicyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; +import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -24,9 +25,9 @@ import org.hamcrest.Matcher; * @author Mykhaylo Boychuk (4science.it) * */ -public class ResoucePolicyMatcher { +public class ResourcePolicyMatcher { - private ResoucePolicyMatcher() { + private ResourcePolicyMatcher() { } public static Matcher matchResourcePolicy(ResourcePolicy resourcePolicy) { @@ -52,4 +53,15 @@ public class ResoucePolicyMatcher { ); } + /** + * Gets a matcher for all expected embeds when the full projection is requested. + */ + public static Matcher matchFullEmbeds() { + return matchEmbeds( + "eperson", + "group", + "resource" + ); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java new file mode 100644 index 0000000000..69f9c501aa --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +import org.dspace.app.rest.model.WorkflowActionRest; +import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.hamcrest.Matcher; + +/** + * @author Maria Verdonck (Atmire) on 06/01/2020 + */ +public class WorkflowActionMatcher { + + private static final String WORKFLOW_ACTIONS_ENDPOINT + = "/api/" + WorkflowActionRest.CATEGORY + "/" + WorkflowActionRest.NAME_PLURAL + "/"; + + private WorkflowActionMatcher() { + + } + + public static Matcher matchWorkflowActionEntry(WorkflowActionConfig workflowAction) { + return allOf( + hasJsonPath("$.id", is(workflowAction.getId())), + hasJsonPath("$.options", is(workflowAction.getOptions())), + hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowAction.getId())) + ); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowDefinitionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowDefinitionMatcher.java new file mode 100644 index 0000000000..2dd10f6b8c --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowDefinitionMatcher.java @@ -0,0 +1,65 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +import java.util.UUID; + +import org.dspace.app.rest.model.WorkflowDefinitionRest; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.state.Workflow; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +/** + * @author Maria Verdonck (Atmire) on 03/01/2020 + */ +public class WorkflowDefinitionMatcher { + + private static XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + + private static final String WORKFLOW_DEFINITIONS_ENDPOINT + = "/api/" + WorkflowDefinitionRest.CATEGORY + "/" + WorkflowDefinitionRest.NAME_PLURAL + "/"; + + private WorkflowDefinitionMatcher() { + } + + public static Matcher matchWorkflowDefinitionEntry(Workflow workflow) { + return allOf( + hasJsonPath("$.name", is(workflow.getID())), + hasJsonPath("$.isDefault", is(xmlWorkflowFactory.isDefaultWorkflow(workflow.getID()))), + hasJsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT + workflow.getID())) + ); + } + + public static Matcher matchWorkflowOnWorkflowName(String workflowName) { + return allOf( + hasJsonPath("$.name", is(workflowName)), + hasJsonPath("$.isDefault", is(xmlWorkflowFactory.isDefaultWorkflow(workflowName))), + hasJsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT + workflowName)) + ); + } + + public static Matcher matchCollectionEntry(String name, UUID uuid, String handle) { + return allOf( + hasJsonPath("$.uuid", is(uuid.toString())), + hasJsonPath("$.name", is(name)), + hasJsonPath("$.handle", is(handle)), + hasJsonPath("$.type", is("collection")), + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.title", name) + )) + ); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowStepMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowStepMatcher.java new file mode 100644 index 0000000000..2dc37d3586 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowStepMatcher.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +import java.util.stream.Collectors; + +import org.dspace.app.rest.model.WorkflowStepRest; +import org.dspace.xmlworkflow.state.Step; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +/** + * @author Maria Verdonck (Atmire) on 13/01/2020 + */ +public class WorkflowStepMatcher { + + private static final String WORKFLOW_ACTIONS_ENDPOINT + = "/api/" + WorkflowStepRest.CATEGORY + "/" + WorkflowStepRest.NAME_PLURAL + "/";; + + private WorkflowStepMatcher() {} + + public static Matcher matchWorkflowStepEntry(Step workflowStep) { + return allOf( + hasJsonPath("$.id", is(workflowStep.getId())), + hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowStep.getId())), + hasJsonPath("$._embedded.workflowactions._embedded.workflowactions", Matchers.containsInAnyOrder( + workflowStep.getActions() + .stream() + .map(x -> WorkflowActionMatcher.matchWorkflowActionEntry(x)) + .collect(Collectors.toList()) + )) + ); + } + + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkspaceItemMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkspaceItemMatcher.java index 24711e157e..d2d2491171 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkspaceItemMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkspaceItemMatcher.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; +import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds; import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; @@ -127,4 +128,16 @@ public class WorkspaceItemMatcher { hasJsonPath("$._links.submissionDefinition.href", startsWith(REST_SERVER_URL))); } } + + /** + * Gets a matcher for all expected embeds when the full projection is requested. + */ + public static Matcher matchFullEmbeds() { + return matchEmbeds( + "collection", + "item", + "submitter", + "submissionDefinition" + ); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java index 0c8c976a03..b34c945065 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java @@ -12,6 +12,7 @@ import javax.annotation.Nullable; import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.MockObject; import org.dspace.app.rest.model.MockObjectRest; +import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.HALResource; import org.springframework.hateoas.Link; @@ -87,8 +88,9 @@ public class MockProjection implements Projection { return halResource; } - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { - return true; + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, + Link... oldLinks) { + return halResource.getContent().getEmbedLevel() < 2; } public boolean allowLinking(HALResource halResource, LinkRest linkRest) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/EPersonClaimProviderTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/EPersonClaimProviderTest.java index bd3ac1c02a..8ca6bbcc3b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/EPersonClaimProviderTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/EPersonClaimProviderTest.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.security.jwt; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java index 975cf6a874..0fabd2dbd4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.security.jwt; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import java.text.ParseException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProviderTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProviderTest.java index 0a1563c379..42ac74be85 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProviderTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProviderTest.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.security.jwt; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import java.util.ArrayList; import java.util.List; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/MultipartFileSenderTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/MultipartFileSenderTest.java index c2665f4145..e800b021e9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/MultipartFileSenderTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/MultipartFileSenderTest.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -533,4 +533,4 @@ public class MultipartFileSenderTest { } -} \ No newline at end of file +} diff --git a/dspace/config/crosswalks/oai/metadataFormats/mets.xsl b/dspace/config/crosswalks/oai/metadataFormats/mets.xsl index 41d683f075..ca41fcdb2b 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/mets.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/mets.xsl @@ -1,9 +1,7 @@ + xmlns:doc="http://www.lyncode.com/xoai" version="2.0"> @@ -20,7 +18,7 @@ - + diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 2d1149c4d3..2e13d73f97 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -8,6 +8,12 @@ # (Requires reboot of servlet container, e.g. Tomcat, to reload) rest.cors.allowed-origins = * +# This property determines the max embeddepth for a FullProjection. This is also used by the SpecificLevelProjection +# as a fallback incase the property is defined on the bean +rest.projections.full.max = 2 + +# This property determines the max embed depth for a SpecificLevelProjection +rest.projection.specificLevel.maxEmbed = 5 #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 24e4286950..359f36d8bf 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -55,6 +55,7 @@ + @@ -392,6 +393,150 @@
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + @@ -1313,6 +1458,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/rest/projections.xml b/dspace/config/spring/rest/projections.xml new file mode 100644 index 0000000000..6f082b7b5b --- /dev/null +++ b/dspace/config/spring/rest/projections.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 041362d3b7..cd53fa0e65 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -39,12 +39,11 @@ compiling of the corresponding source module. --> + dspace-rest - - rest/pom.xml - + false rest diff --git a/pom.xml b/pom.xml index 80eb369cdb..422781dbe4 100644 --- a/pom.xml +++ b/pom.xml @@ -790,7 +790,7 @@ dspace-oai @@ -805,7 +805,7 @@ dspace-rdf @@ -819,13 +819,12 @@ - + + dspace-rest - - dspace-rest/pom.xml - + false dspace-rest @@ -834,7 +833,7 @@ dspace-sword @@ -850,7 +849,7 @@ dspace-swordv2 @@ -864,6 +863,9 @@ + dspace-server-webapp