From 9a41e6fece0ee2d428060cc6dd42a00be44e2a17 Mon Sep 17 00:00:00 2001 From: marsa Date: Wed, 18 Oct 2017 15:48:25 +0200 Subject: [PATCH 001/749] [DS-4036] Delete EPersons even if they are referenced DSpace references EPersons in different database tables like the submitter of an item or like the EPerson that gets special rights granted in the resourcepolicy table. This PR changes DSpace so it can handle references that are set null instead of referencing an actual EPerson. This is important to be able to delete EPersons which is demanded by several data protection laws like GDPR in the European Union. --- .../RequestItemAuthorExtractor.java | 13 +- .../RequestItemHelpdeskStrategy.java | 3 +- .../RequestItemMetadataStrategy.java | 34 +- .../RequestItemSubmitterStrategy.java | 15 +- .../authorize/AuthorizeServiceImpl.java | 6 + .../authorize/ResourcePolicyServiceImpl.java | 10 + .../authorize/dao/ResourcePolicyDAO.java | 4 + .../dao/impl/ResourcePolicyDAOImpl.java | 19 + .../authorize/service/AuthorizeService.java | 10 + .../service/ResourcePolicyService.java | 4 + .../org/dspace/content/ItemServiceImpl.java | 6 + .../content/WorkspaceItemServiceImpl.java | 5 +- .../java/org/dspace/content/dao/ItemDAO.java | 3 + .../dspace/content/dao/impl/ItemDAOImpl.java | 11 + .../dspace/content/service/ItemService.java | 13 + .../org/dspace/eperson/EPersonCLITool.java | 34 +- .../eperson/EPersonDeletionException.java | 7 +- .../dspace/eperson/EPersonServiceImpl.java | 242 +++- .../org/dspace/workflow/WorkflowService.java | 14 + .../BasicWorkflowServiceImpl.java | 104 +- .../TaskListItemServiceImpl.java | 11 + .../workflowbasic/dao/TaskListItemDAO.java | 5 + .../dao/impl/TaskListItemDAOImpl.java | 18 + .../service/TaskListItemService.java | 5 + .../xmlworkflow/XmlWorkflowServiceImpl.java | 150 ++- .../AcceptEditRejectAction.java | 20 + .../processingaction/ReviewAction.java | 20 + .../SingleUserReviewAction.java | 20 + .../AssignOriginalSubmitterAction.java | 34 +- .../actions/userassignment/ClaimAction.java | 23 +- .../storedcomponents/PoolTaskServiceImpl.java | 13 + .../WorkflowItemRoleServiceImpl.java | 10 + .../service/PoolTaskService.java | 2 + .../service/WorkflowItemRoleService.java | 2 + .../src/main/resources/Messages.properties | 30 +- .../java/org/dspace/eperson/EPersonTest.java | 1098 +++++++---------- .../helpers/stubs/ItemRepositoryBuilder.java | 4 +- dspace/config/emails/request_item.to_admin | 13 + 38 files changed, 1199 insertions(+), 836 deletions(-) create mode 100644 dspace/config/emails/request_item.to_admin diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java index bba0913193..9b66030e90 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java @@ -19,6 +19,15 @@ import org.dspace.core.Context; * @author Andrea Bollini */ public interface RequestItemAuthorExtractor { - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) - throws SQLException; + + /** + * Retrieve the auhtor to contact for a request copy of the give item. + * + * @param context DSpace context object + * @param item item to request + * @return An object containing name an email address to send the request to + * or null if no valid email address was found. + * @throws SQLException if database error + */ + public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java index a5f7341039..3e31016e92 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java @@ -74,8 +74,7 @@ public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy { return new RequestItemAuthor(helpdeskEPerson); } else { String helpdeskName = I18nUtil.getMessage( - "org.dspace.app.requestitem.RequestItemHelpdeskStrategy.helpdeskname", - context); + "org.dspace.app.requestitem.helpdeskname", context); return new RequestItemAuthor(helpdeskName, helpDeskEmail); } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java index 4d2f78408a..aa449d0a58 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java @@ -16,6 +16,7 @@ import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.core.I18nUtil; +import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -38,6 +39,7 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy { @Override public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException { + RequestItemAuthor author = null; if (emailMetadata != null) { List vals = itemService.getMetadataByMetadataString(item, emailMetadata); if (vals.size() > 0) { @@ -49,19 +51,37 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy { fullname = nameVals.iterator().next().getValue(); } } - if (StringUtils.isBlank(fullname)) { fullname = I18nUtil - .getMessage( - "org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed", - context); + .getMessage( + "org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed", + context); } - RequestItemAuthor author = new RequestItemAuthor( - fullname, email); + author = new RequestItemAuthor(fullname, email); return author; } + } else { + // Uses the basic strategy to look for the original submitter + author = super.getRequestItemAuthor(context, item); + // Is the author or his email null, so get the help desk or admin name and email + if (null == author || null == author.getEmail()) { + String email = null; + String name = null; + //First get help desk name and email + email = DSpaceServicesFactory.getInstance() + .getConfigurationService().getProperty("mail.helpdesk"); + name = I18nUtil.getMessage("org.dspace.app.requestitem.helpdeskname", context); + // If help desk mail is null get the mail and name of admin + if (email == null) { + email = DSpaceServicesFactory.getInstance() + .getConfigurationService().getProperty("mail.admin"); + name = DSpaceServicesFactory.getInstance() + .getConfigurationService().getProperty("admin.name"); + } + author = new RequestItemAuthor(name, email); + } } - return super.getRequestItemAuthor(context, item); + return author; } public void setEmailMetadata(String emailMetadata) { diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index 8ed6238a8c..2708c24ba9 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -23,13 +23,22 @@ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor public RequestItemSubmitterStrategy() { } + /** + * Returns the submitter of an Item as RequestItemAuthor or null if the + * Submitter is deleted. + * + * @return The submitter of the item or null if the submitter is deleted + * @throws SQLException if database error + */ @Override public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException { EPerson submitter = item.getSubmitter(); - RequestItemAuthor author = new RequestItemAuthor( - submitter.getFullName(), submitter.getEmail()); + RequestItemAuthor author = null; + if (null != submitter) { + author = new RequestItemAuthor( + submitter.getFullName(), submitter.getEmail()); + } return author; } - } diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 936a7d6492..ebd16f70a4 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -606,6 +606,12 @@ public class AuthorizeServiceImpl implements AuthorizeService { resourcePolicyService.removeDsoEPersonPolicies(c, o, e); } + @Override + public void removeAllEPersonPolicies(Context c, EPerson e) + throws SQLException, AuthorizeException { + resourcePolicyService.removeAllEPersonPolicies(c, e); + } + @Override public List getAuthorizedGroups(Context c, DSpaceObject o, int actionID) throws java.sql.SQLException { diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index 4693599c31..efc4aa3d86 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -109,6 +109,11 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { return resourcePolicyDAO.findByEPersonGroupTypeIdAction(c, e, groups, action, type_id); } + @Override + public List find(Context context, EPerson ePerson) throws SQLException { + return resourcePolicyDAO.findByEPerson(context, ePerson); + } + @Override public List findByTypeGroupActionExceptId(Context context, DSpaceObject dso, Group group, int action, int notPolicyID) @@ -241,6 +246,11 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { } + @Override + public void removeAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException, AuthorizeException { + resourcePolicyDAO.deleteAllEPersonPolicies(context, ePerson); + } + @Override public void removeGroupPolicies(Context c, Group group) throws SQLException { resourcePolicyDAO.deleteByGroup(c, group); diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java index ecaeef18e7..8536cef60e 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java @@ -32,6 +32,8 @@ public interface ResourcePolicyDAO extends GenericDAO { public List findByDsoAndType(Context context, DSpaceObject dSpaceObject, String type) throws SQLException; + public List findByEPerson(Context context, EPerson ePerson) throws SQLException; + public List findByGroup(Context context, Group group) throws SQLException; public List findByDSoAndAction(Context context, DSpaceObject dso, int actionId) throws SQLException; @@ -65,5 +67,7 @@ public interface ResourcePolicyDAO extends GenericDAO { public void deleteByDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson) throws SQLException; + public void deleteAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException; + public void deleteByDsoAndTypeNotEqualsTo(Context c, DSpaceObject o, String type) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java index a0f69dc526..638ef01582 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java @@ -60,6 +60,16 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1); } + @Override + public List findByEPerson(Context context, EPerson ePerson) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, ResourcePolicy.class); + Root resourcePolicyRoot = criteriaQuery.from(ResourcePolicy.class); + criteriaQuery.select(resourcePolicyRoot); + criteriaQuery.where(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), ePerson)); + return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1); + } + @Override public List findByGroup(Context context, Group group) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); @@ -192,6 +202,15 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO } + @Override + public void deleteAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException { + String queryString = "delete from ResourcePolicy where eperson= :eperson"; + Query query = createQuery(context, queryString); + query.setParameter("eperson", ePerson); + query.executeUpdate(); + + } + @Override public void deleteByDsoAndTypeNotEqualsTo(Context context, DSpaceObject dso, String type) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index 092460f10f..8dfeb8f6a6 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -417,6 +417,16 @@ public interface AuthorizeService { */ public void removeEPersonPolicies(Context c, DSpaceObject o, EPerson e) throws SQLException, AuthorizeException; + /** + * Removes all policies from an eperson that belong to an EPerson. + * + * @param c current context + * @param e the eperson + * @throws SQLException if there's a database problem + * @throws AuthorizeException if authorization error + */ + public void removeAllEPersonPolicies(Context c, EPerson e) throws SQLException, AuthorizeException; + /** * Returns all groups authorized to perform an action on an object. Returns * empty array if no matches. diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java index 689898daa2..88d7832760 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java @@ -38,6 +38,8 @@ public interface ResourcePolicyService extends DSpaceCRUDService public List find(Context context, Group group) throws SQLException; + public List find(Context c, EPerson ePerson) throws SQLException; + public List find(Context c, EPerson e, List groups, int action, int type_id) throws SQLException; @@ -71,6 +73,8 @@ public interface ResourcePolicyService extends DSpaceCRUDService public void removeDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson) throws SQLException, AuthorizeException; + public void removeAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException, AuthorizeException; + public void removeGroupPolicies(Context c, Group group) throws SQLException; public void removeDsoAndTypeNotEqualsToPolicies(Context c, DSpaceObject o, String type) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index d65f8d4240..efcc4115f4 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -214,6 +214,12 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It return itemDAO.findBySubmitter(context, eperson); } + @Override + public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) + throws SQLException { + return itemDAO.findBySubmitter(context, eperson, retrieveAllItems); + } + @Override public Iterator findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index 93357d1712..a55bea2754 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -208,9 +208,8 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { */ Item item = workspaceItem.getItem(); if (!authorizeService.isAdmin(context) - && ((context.getCurrentUser() == null) || (context - .getCurrentUser().getID() != item.getSubmitter() - .getID()))) { + && (item.getSubmitter() == null || (context.getCurrentUser() == null) + || (context.getCurrentUser().getID() != item.getSubmitter().getID()))) { // Not an admit, not the submitter throw new AuthorizeException("Must be an administrator or the " + "original submitter to delete a workspace item"); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java index 02ddd1cf3e..af4dfb059e 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java @@ -46,6 +46,9 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException; + public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) + throws SQLException; + public Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java index 025dab66e4..93b4e34c27 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java @@ -104,6 +104,17 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA return iterate(query); } + @Override + public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) + throws SQLException { + Query query = createQuery(context, "FROM Item WHERE submitter= :submitter"); + if (!retrieveAllItems) { + return findBySubmitter(context, eperson); + } + query.setParameter("submitter", eperson); + return iterate(query); + } + @Override public Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index bfc01fbf84..3a36640b4c 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -110,6 +110,19 @@ public interface ItemService extends DSpaceObjectService, DSpaceObjectLega public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException; + /** + * Find all the items by a given submitter. The order is + * indeterminate. All items are included. + * + * @param context DSpace context object + * @param eperson the submitter + * @param retrieveAllItems flag to determine if only archive should be returned + * @return an iterator over the items submitted by eperson + * @throws SQLException if database error + */ + public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) + throws SQLException; + /** * Retrieve the list of items submitted by eperson, ordered by recently submitted, optionally limitable * diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index 850cb992bc..8d63a2caf2 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -7,8 +7,11 @@ */ package org.dspace.eperson; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.sql.SQLException; +import java.util.List; import java.util.Locale; import org.apache.commons.cli.CommandLine; @@ -259,16 +262,27 @@ public class EPersonCLITool { } try { - ePersonService.delete(context, eperson); - context.complete(); - System.out.printf("Deleted EPerson %s\n", eperson.getID().toString()); - } catch (SQLException ex) { - System.err.println(ex.getMessage()); - return 1; - } catch (AuthorizeException ex) { - System.err.println(ex.getMessage()); - return 1; - } catch (IOException ex) { + List tableList = ePersonService.getDeleteConstraints(context, eperson); + if (!tableList.isEmpty()) { + System.out.printf("This EPerson with ID: %s is referring to this tables:%n ", + eperson.getID().toString()); + tableList.forEach((s) -> { + System.out.println(s); + }); + } + System.out.printf("Are you sure you want to delete this EPerson with ID: %s? (y or n): ", + eperson.getID().toString()); + BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); + System.out.flush(); + String s = input.readLine(); + if (s != null && s.trim().toLowerCase().startsWith("y")) { + ePersonService.delete(context, eperson); + context.complete(); + System.out.printf("%nDeleted EPerson with ID: %s", eperson.getID().toString()); + } else { + System.out.printf("%nAbort Deletetion of EPerson with ID: %s %n", eperson.getID().toString()); + } + } catch (SQLException | AuthorizeException | IOException ex) { System.err.println(ex.getMessage()); return 1; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java index 5429f3d102..93e2eddb58 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java @@ -9,6 +9,8 @@ package org.dspace.eperson; import java.util.List; +import org.apache.commons.lang.ArrayUtils; + /** * Exception indicating that an EPerson may not be deleted due to the presence * of the EPerson's ID in certain tables @@ -33,7 +35,10 @@ public class EPersonDeletionException extends Exception { * deleted if it exists in these tables. */ public EPersonDeletionException(List tableList) { - super(); + // this may not be the most beautiful way to print the tablenames as part or the error message. + // but it has to be a one liner, as the super() call must be the first statement in the constructor. + super("Cannot delete EPerson as it is referenced by the following database tables: " + + ArrayUtils.toString(tableList.toArray())); myTableList = tableList; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index f173250cf3..0e765b187d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -7,9 +7,11 @@ */ package org.dspace.eperson; +import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -21,11 +23,16 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.DSpaceObjectServiceImpl; import org.dspace.content.Item; import org.dspace.content.MetadataField; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; @@ -34,13 +41,36 @@ import org.dspace.eperson.dao.EPersonDAO; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.dao.VersionDAO; +import org.dspace.versioning.factory.VersionServiceFactory; +import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.versioning.service.VersioningService; import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; +import org.dspace.workflowbasic.BasicWorkflowItem; +import org.dspace.workflowbasic.BasicWorkflowServiceImpl; +import org.dspace.workflowbasic.factory.BasicWorkflowServiceFactory; +import org.dspace.workflowbasic.service.BasicWorkflowItemService; +import org.dspace.workflowbasic.service.BasicWorkflowService; +import org.dspace.workflowbasic.service.TaskListItemService; +import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.service.WorkflowRequirementsService; +import org.dspace.xmlworkflow.service.XmlWorkflowService; +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.PoolTaskService; +import org.dspace.xmlworkflow.storedcomponents.service.WorkflowItemRoleService; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; + import org.springframework.beans.factory.annotation.Autowired; /** - * Service implementation for the EPerson object. - * This class is responsible for all business logic calls for the EPerson object and is autowired by spring. + * Service implementation for the EPerson object. This class is responsible for + * all business logic calls for the EPerson object and is autowired by spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -61,6 +91,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme protected ItemService itemService; @Autowired(required = true) protected SubscribeService subscribeService; + @Autowired(required = true) + protected VersionDAO versionDAO; protected EPersonServiceImpl() { super(); @@ -129,7 +161,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme query = null; } return ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField), - Arrays.asList(firstNameField, lastNameField), offset, limit); + Arrays.asList(firstNameField, lastNameField), offset, limit); } } @@ -179,45 +211,192 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme // authorized? if (!authorizeService.isAdmin(context)) { throw new AuthorizeException( - "You must be an admin to create an EPerson"); + "You must be an admin to create an EPerson"); } // Create a table row EPerson e = ePersonDAO.create(context, new EPerson()); log.info(LogManager.getHeader(context, "create_eperson", "eperson_id=" - + e.getID())); + + e.getID())); context.addEvent(new Event(Event.CREATE, Constants.EPERSON, e.getID(), - null, getIdentifiers(context, e))); + null, getIdentifiers(context, e))); return e; } @Override public void delete(Context context, EPerson ePerson) throws SQLException, AuthorizeException { + try { + delete(context, ePerson, true); + } catch (AuthorizeException ex) { + log.error("This AuthorizeException: " + ex + " occured while deleting Eperson with the ID: " + + ePerson.getID()); + throw new AuthorizeException(ex.getMessage()); + } catch (IOException ex) { + log.error("This IOException: " + ex + " occured while deleting Eperson with the ID: " + ePerson.getID()); + throw new AuthorizeException(new EPersonDeletionException()); + } + } + + /** + * Deletes an EPerson. The argument cascade defines whether all references + * on an EPerson should be deleted as well (by either deleting the + * referencing object - e.g. WorkspaceItem, ResourcePolicy - or by setting + * the foreign key null - e.g. archived Items). If cascade is set to false + * and the EPerson is referenced somewhere, this leads to an + * AuthorizeException. EPersons may be referenced by Items, ResourcePolicies + * and workflow tasks. + * + * @param context DSpace context + * @param ePerson The EPerson to delete. + * @param cascade Whether to delete references on the EPerson (cascade = + * true) or to abort the deletion (cascade = false) if the EPerson is + * referenced within DSpace. + * + * @throws SQLException + * @throws AuthorizeException + * @throws IOException + */ + public void delete(Context context, EPerson ePerson, boolean cascade) + throws SQLException, AuthorizeException, IOException { // authorized? if (!authorizeService.isAdmin(context)) { throw new AuthorizeException( - "You must be an admin to delete an EPerson"); + "You must be an admin to delete an EPerson"); } - // check for presence of eperson in tables that // have constraints on eperson_id List constraintList = getDeleteConstraints(context, ePerson); - - // if eperson exists in tables that have constraints - // on eperson, throw an exception if (constraintList.size() > 0) { - throw new AuthorizeException(new EPersonDeletionException(constraintList)); - } + // Check if the constraints we found should be deleted + if (cascade) { + boolean isBasicFramework = WorkflowServiceFactory.getInstance().getWorkflowService() + instanceof BasicWorkflowService; + boolean isXmlFramework = WorkflowServiceFactory.getInstance().getWorkflowService() + instanceof XmlWorkflowService; + Iterator constraintsIterator = constraintList.iterator(); + while (constraintsIterator.hasNext()) { + String tableName = constraintsIterator.next(); + if (StringUtils.equals(tableName, "item") || StringUtils.equals(tableName, "workspaceitem")) { + Iterator itemIterator = itemService.findBySubmitter(context, ePerson, true); + + VersionHistoryService versionHistoryService = VersionServiceFactory.getInstance() + .getVersionHistoryService(); + VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService(); + + while (itemIterator.hasNext()) { + Item item = itemIterator.next(); + + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + if (null != versionHistory) { + for (Version version : versioningService.getVersionsByHistory(context, + versionHistory)) { + version.setePerson(null); + versionDAO.save(context, version); + } + } + WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance() + .getWorkspaceItemService(); + WorkspaceItem wsi = workspaceItemService.findByItem(context, item); + + if (null != wsi) { + workspaceItemService.deleteAll(context, wsi); + } else { + // we can do that as dc.provenance still contains + // information about who submitted and who + // archived an item. + item.setSubmitter(null); + itemService.update(context, item); + } + } + } else if (StringUtils.equals(tableName, "cwf_claimtask") && isXmlFramework) { + // Unclaim all XmlWorkflow tasks + XmlWorkflowItemService xmlWorkflowItemService = XmlWorkflowServiceFactory + .getInstance().getXmlWorkflowItemService(); + ClaimedTaskService claimedTaskService = XmlWorkflowServiceFactory + .getInstance().getClaimedTaskService(); + XmlWorkflowService xmlWorkflowService = XmlWorkflowServiceFactory + .getInstance().getXmlWorkflowService(); + WorkflowRequirementsService workflowRequirementsService = XmlWorkflowServiceFactory + .getInstance().getWorkflowRequirementsService(); + + List xmlWorkflowItems = xmlWorkflowItemService + .findBySubmitter(context, ePerson); + + for (XmlWorkflowItem xmlWorkflowItem : xmlWorkflowItems) { + ClaimedTask pooledTask = claimedTaskService + .findByWorkflowIdAndEPerson(context, xmlWorkflowItem, ePerson); + xmlWorkflowService.deleteClaimedTask(context, xmlWorkflowItem, pooledTask); + + try { + workflowRequirementsService.removeClaimedUser(context, xmlWorkflowItem, + ePerson, pooledTask.getStepID()); + } catch (WorkflowConfigurationException ex) { + log.error("This WorkflowConfigurationException: " + ex + + " occured while deleting Eperson with the ID: " + ePerson.getID()); + throw new AuthorizeException(new EPersonDeletionException(Collections + .singletonList(tableName))); + } + } + } else if (StringUtils.equals(tableName, "workflowitem") && isBasicFramework) { + // Remove basicWorkflow workflowitem and unclaim them + BasicWorkflowItemService basicWorkflowItemService = BasicWorkflowServiceFactory.getInstance() + .getBasicWorkflowItemService(); + BasicWorkflowService basicWorkflowService = BasicWorkflowServiceFactory.getInstance() + .getBasicWorkflowService(); + TaskListItemService taskListItemService = BasicWorkflowServiceFactory.getInstance() + .getTaskListItemService(); + List workflowItems = basicWorkflowItemService.findByOwner(context, ePerson); + for (BasicWorkflowItem workflowItem : workflowItems) { + int state = workflowItem.getState(); + // unclaim tasks that are in the pool. + if (state == BasicWorkflowServiceImpl.WFSTATE_STEP1 + || state == BasicWorkflowServiceImpl.WFSTATE_STEP2 + || state == BasicWorkflowServiceImpl.WFSTATE_STEP3) { + log.info(LogManager.getHeader(context, "unclaim_workflow", + "workflow_id=" + workflowItem.getID() + ", claiming EPerson is deleted")); + basicWorkflowService.unclaim(context, workflowItem, context.getCurrentUser()); + // remove the EPerson from the list of persons that can (re-)claim the task + // while we are doing it below, we must do this here as well as the previously + // unclaimed tasks was put back into pool and we do not know the order the tables + // are checked. + taskListItemService.deleteByWorkflowItemAndEPerson(context, workflowItem, ePerson); + } + } + } else if (StringUtils.equals(tableName, "resourcepolicy")) { + // we delete the EPerson, it won't need any rights anymore. + authorizeService.removeAllEPersonPolicies(context, ePerson); + } else if (StringUtils.equals(tableName, "tasklistitem") && isBasicFramework) { + // remove EPerson from the list of EPersons that may claim some specific workflow tasks. + TaskListItemService taskListItemService = BasicWorkflowServiceFactory.getInstance() + .getTaskListItemService(); + taskListItemService.deleteByEPerson(context, ePerson); + } else if (StringUtils.equals(tableName, "cwf_pooltask") && isXmlFramework) { + PoolTaskService poolTaskService = XmlWorkflowServiceFactory.getInstance().getPoolTaskService(); + poolTaskService.deleteByEperson(context, ePerson); + } else if (StringUtils.equals(tableName, "cwf_workflowitemrole") && isXmlFramework) { + WorkflowItemRoleService workflowItemRoleService = XmlWorkflowServiceFactory.getInstance() + .getWorkflowItemRoleService(); + workflowItemRoleService.deleteByEPerson(context, ePerson); + } else { + log.warn("EPerson is referenced in table '" + tableName + + "'. Deletion of EPerson " + ePerson.getID() + " may fail " + + "if the database does not handle this " + + "reference."); + } + } + } else { + throw new AuthorizeException(new EPersonDeletionException(constraintList)); + } + } context.addEvent(new Event(Event.DELETE, Constants.EPERSON, ePerson.getID(), ePerson.getEmail(), - getIdentifiers(context, ePerson))); + getIdentifiers(context, ePerson))); // XXX FIXME: This sidesteps the object model code so it won't // generate REMOVE events on the affected Groups. - // Remove any group memberships first // Remove any group memberships first Iterator groups = ePerson.getGroups().iterator(); @@ -234,7 +413,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme ePersonDAO.delete(context, ePerson); log.info(LogManager.getHeader(context, "delete_eperson", - "eperson_id=" + ePerson.getID())); + "eperson_id=" + ePerson.getID())); } @Override @@ -268,8 +447,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme PasswordHash hash = null; try { hash = new PasswordHash(ePerson.getDigestAlgorithm(), - ePerson.getSalt(), - ePerson.getPassword()); + ePerson.getSalt(), + ePerson.getPassword()); } catch (DecoderException ex) { log.error("Problem decoding stored salt or hash: " + ex.getMessage()); } @@ -281,9 +460,9 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme PasswordHash myHash; try { myHash = new PasswordHash( - ePerson.getDigestAlgorithm(), - ePerson.getSalt(), - ePerson.getPassword()); + ePerson.getDigestAlgorithm(), + ePerson.getSalt(), + ePerson.getPassword()); } catch (DecoderException ex) { log.error(ex.getMessage()); return false; @@ -312,8 +491,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme // Check authorisation - if you're not the eperson // see if the authorization system says you can if (!context.ignoreAuthorization() - && ((context.getCurrentUser() == null) || (ePerson.getID() != context - .getCurrentUser().getID()))) { + && ((context.getCurrentUser() == null) || (ePerson.getID() != context + .getCurrentUser().getID()))) { authorizeService.authorizeAction(context, ePerson, Constants.WRITE); } @@ -322,11 +501,11 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme ePersonDAO.save(context, ePerson); log.info(LogManager.getHeader(context, "update_eperson", - "eperson_id=" + ePerson.getID())); + "eperson_id=" + ePerson.getID())); if (ePerson.isModified()) { context.addEvent(new Event(Event.MODIFY, Constants.EPERSON, - ePerson.getID(), null, getIdentifiers(context, ePerson))); + ePerson.getID(), null, getIdentifiers(context, ePerson))); ePerson.clearModified(); } if (ePerson.isMetadataModified()) { @@ -339,11 +518,22 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme List tableList = new ArrayList(); // check for eperson in item table - Iterator itemsBySubmitter = itemService.findBySubmitter(context, ePerson); + Iterator itemsBySubmitter = itemService.findBySubmitter(context, ePerson, true); if (itemsBySubmitter.hasNext()) { tableList.add("item"); } + WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + List workspaceBySubmitter = workspaceItemService.findByEPerson(context, ePerson); + if (workspaceBySubmitter.size() > 0) { + tableList.add("workspaceitem"); + } + + ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); + if (resourcePolicyService.find(context, ePerson).size() > 0) { + tableList.add("resourcepolicy"); + } + WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); List workflowConstraints = workflowService.getEPersonDeleteConstraints(context, ePerson); tableList.addAll(workflowConstraints); diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java index 53da1660db..7395dd9da8 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java @@ -80,6 +80,20 @@ public interface WorkflowService { */ public WorkspaceItem abort(Context c, T wi, EPerson e) throws SQLException, AuthorizeException, IOException; + /** + * Deletes workflow task item in correct order. + * + * @param c The relevant DSpace Context. + * @param wi The WorkflowItem that shall be deleted. + * @param e Admin that deletes this workflow task and item (for logging + * @throws SQLException An exception that provides information on a database access error or other errors. + * @throws AuthorizeException Exception indicating the current user of the context does not have permission + * to perform a particular action. + * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. + */ + public void deleteWorkflowByWorkflowItem(Context c, T wi, EPerson e) + throws SQLException, AuthorizeException, IOException; + public WorkspaceItem sendWorkflowItemBackSubmission(Context c, T workflowItem, EPerson e, String provenance, String rejection_message) throws SQLException, AuthorizeException, IOException; diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java index 79dfa0ae3d..f01ae510e3 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java @@ -798,33 +798,38 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { try { // Get submitter EPerson ep = item.getSubmitter(); - // Get the Locale - Locale supportedLocale = I18nUtil.getEPersonLocale(ep); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_archive")); - // Get the item handle to email to user - String handle = handleService.findHandle(context, item); + // send the notification only if the person was not deleted in the + // meantime between submission and archiving. + if (ep != null) { + // Get the Locale + Locale supportedLocale = I18nUtil.getEPersonLocale(ep); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_archive")); - // Get title - String title = item.getName(); - if (StringUtils.isBlank(title)) { - try { - title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled"); - } catch (MissingResourceException e) { - title = "Untitled"; + // Get the item handle to email to user + String handle = handleService.findHandle(context, item); + + // Get title + String title = item.getName(); + if (StringUtils.isBlank(title)) { + try { + title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled"); + } catch (MissingResourceException e) { + title = "Untitled"; + } } + + email.addRecipient(ep.getEmail()); + email.addArgument(title); + email.addArgument(coll.getName()); + email.addArgument(handleService.getCanonicalForm(handle)); + + email.send(); } - - email.addRecipient(ep.getEmail()); - email.addArgument(title); - email.addArgument(coll.getName()); - email.addArgument(handleService.getCanonicalForm(handle)); - - email.send(); } catch (MessagingException e) { log.warn(LogManager.getHeader(context, "notifyOfArchive", - "cannot email user; item_id=" + item.getID() - + ": " + e.getMessage())); + "cannot email user; item_id=" + item.getID() + + ": " + e.getMessage())); } } @@ -866,6 +871,24 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { return workspaceItem; } + @Override + public void deleteWorkflowByWorkflowItem(Context context, BasicWorkflowItem wi, EPerson e) + throws SQLException, AuthorizeException, IOException { + Item myitem = wi.getItem(); + UUID itemID = myitem.getID(); + Integer workflowID = wi.getID(); + UUID collID = wi.getCollection().getID(); + // stop workflow + taskListItemService.deleteByWorkflowItem(context, wi); + // Now remove the workflow object manually from the database + workflowItemService.deleteWrapper(context, wi); + // Now delete the item + itemService.delete(context, myitem); + log.info(LogManager.getHeader(context, "delete_workflow", "workflow_item_id=" + + workflowID + "item_id=" + itemID + + "collection_id=" + collID + "eperson_id=" + + e.getID())); + } @Override public WorkspaceItem sendWorkflowItemBackSubmission(Context context, BasicWorkflowItem workflowItem, @@ -1047,25 +1070,30 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { protected void notifyOfReject(Context context, BasicWorkflowItem workflowItem, EPerson e, String reason) { try { - // Get the item title - String title = getItemTitle(workflowItem); + // Get submitter + EPerson ep = workflowItem.getSubmitter(); + // send the notification only if the person was not deleted in the meantime + if (ep != null) { + // Get the item title + String title = getItemTitle(workflowItem); - // Get the collection - Collection coll = workflowItem.getCollection(); + // Get the collection + Collection coll = workflowItem.getCollection(); - // Get rejector's name - String rejector = getEPersonName(e); - Locale supportedLocale = I18nUtil.getEPersonLocale(e); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject")); + // Get rejector's name + String rejector = getEPersonName(e); + Locale supportedLocale = I18nUtil.getEPersonLocale(e); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject")); - email.addRecipient(workflowItem.getSubmitter().getEmail()); - email.addArgument(title); - email.addArgument(coll.getName()); - email.addArgument(rejector); - email.addArgument(reason); - email.addArgument(getMyDSpaceLink()); + email.addRecipient(ep.getEmail()); + email.addArgument(title); + email.addArgument(coll.getName()); + email.addArgument(rejector); + email.addArgument(reason); + email.addArgument(getMyDSpaceLink()); - email.send(); + email.send(); + } } catch (RuntimeException re) { // log this email error log.warn(LogManager.getHeader(context, "notify_of_reject", @@ -1101,7 +1129,9 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { @Override public String getSubmitterName(BasicWorkflowItem wi) throws SQLException { EPerson e = wi.getSubmitter(); - + if (e == null) { + return null; + } return getEPersonName(e); } diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItemServiceImpl.java index d064600191..ba55ca9460 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItemServiceImpl.java @@ -46,6 +46,17 @@ public class TaskListItemServiceImpl implements TaskListItemService { taskListItemDAO.deleteByWorkflowItem(context, workflowItem); } + @Override + public void deleteByWorkflowItemAndEPerson(Context context, BasicWorkflowItem workflowItem, EPerson ePerson) + throws SQLException { + taskListItemDAO.deleteByWorkflowItemAndEPerson(context, workflowItem, ePerson); + } + + @Override + public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException { + taskListItemDAO.deleteByEPerson(context, ePerson); + } + @Override public void update(Context context, TaskListItem taskListItem) throws SQLException { taskListItemDAO.save(context, taskListItem); diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/dao/TaskListItemDAO.java b/dspace-api/src/main/java/org/dspace/workflowbasic/dao/TaskListItemDAO.java index b09cac72e0..5cdf3e0611 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/dao/TaskListItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/dao/TaskListItemDAO.java @@ -28,5 +28,10 @@ public interface TaskListItemDAO extends GenericDAO { public void deleteByWorkflowItem(Context context, BasicWorkflowItem workflowItem) throws SQLException; + public void deleteByWorkflowItemAndEPerson(Context context, BasicWorkflowItem workflowItem, EPerson ePerson) + throws SQLException; + + public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException; + public List findByEPerson(Context context, EPerson ePerson) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/dao/impl/TaskListItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/dao/impl/TaskListItemDAOImpl.java index ec92faec03..2f6448fb4e 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/dao/impl/TaskListItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/dao/impl/TaskListItemDAOImpl.java @@ -42,6 +42,24 @@ public class TaskListItemDAOImpl extends AbstractHibernateDAO impl query.executeUpdate(); } + @Override + public void deleteByWorkflowItemAndEPerson(Context context, BasicWorkflowItem workflowItem, EPerson ePerson) + throws SQLException { + String queryString = "delete from TaskListItem where workflowItem = :workflowItem AND ePerson = :ePerson"; + Query query = createQuery(context, queryString); + query.setParameter("workflowItem", workflowItem); + query.setParameter("ePerson", ePerson); + query.executeUpdate(); + } + + @Override + public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException { + String queryString = "delete from TaskListItem where ePerson = :ePerson"; + Query query = createQuery(context, queryString); + query.setParameter("ePerson", ePerson); + query.executeUpdate(); + } + @Override public List findByEPerson(Context context, EPerson ePerson) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/service/TaskListItemService.java b/dspace-api/src/main/java/org/dspace/workflowbasic/service/TaskListItemService.java index 4ce605f87f..3a8aac65fe 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/service/TaskListItemService.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/service/TaskListItemService.java @@ -28,6 +28,11 @@ public interface TaskListItemService { public void deleteByWorkflowItem(Context context, BasicWorkflowItem workflowItem) throws SQLException; + public void deleteByWorkflowItemAndEPerson(Context context, BasicWorkflowItem workflowItem, EPerson ePerson) + throws SQLException; + + public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException; + public void update(Context context, TaskListItem taskListItem) throws SQLException; public List findByEPerson(Context context, EPerson ePerson) throws SQLException; 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 8983976cc2..2ce7076f58 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -251,19 +251,21 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } protected void grantSubmitterReadPolicies(Context context, Item item) throws SQLException, AuthorizeException { - //A list of policies the user has for this item - List userHasPolicies = new ArrayList(); - List itempols = authorizeService.getPolicies(context, item); EPerson submitter = item.getSubmitter(); - for (ResourcePolicy resourcePolicy : itempols) { - if (submitter.equals(resourcePolicy.getEPerson())) { - //The user has already got this policy so add it to the list - userHasPolicies.add(resourcePolicy.getAction()); + if (null != submitter) { + //A list of policies the user has for this item + List userHasPolicies = new ArrayList<>(); + List itempols = authorizeService.getPolicies(context, item); + for (ResourcePolicy resourcePolicy : itempols) { + if (submitter.equals(resourcePolicy.getEPerson())) { + //The user has already got this policy so add it to the list + userHasPolicies.add(resourcePolicy.getAction()); + } + } + //Make sure we don't add duplicate policies + if (!userHasPolicies.contains(Constants.READ)) { + addPolicyToItem(context, item, Constants.READ, submitter, ResourcePolicy.TYPE_SUBMISSION); } - } - //Make sure we don't add duplicate policies - if (!userHasPolicies.contains(Constants.READ)) { - addPolicyToItem(context, item, Constants.READ, submitter, ResourcePolicy.TYPE_SUBMISSION); } } @@ -564,35 +566,39 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { try { // Get submitter EPerson ep = item.getSubmitter(); - // Get the Locale - Locale supportedLocale = I18nUtil.getEPersonLocale(ep); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_archive")); + // send the notification only if the person was not deleted in the + // meantime between submission and archiving. + if (null != ep) { + // Get the Locale + Locale supportedLocale = I18nUtil.getEPersonLocale(ep); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_archive")); - // Get the item handle to email to user - String handle = handleService.findHandle(context, item); + // Get the item handle to email to user + String handle = handleService.findHandle(context, item); - // Get title - List titles = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); - String title = ""; - try { - title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled"); - } catch (MissingResourceException e) { - title = "Untitled"; + // Get title + List titles = itemService + .getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + String title = ""; + try { + title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled"); + } catch (MissingResourceException e) { + title = "Untitled"; + } + if (titles.size() > 0) { + title = titles.iterator().next().getValue(); + } + + email.addRecipient(ep.getEmail()); + email.addArgument(title); + email.addArgument(coll.getName()); + email.addArgument(handleService.getCanonicalForm(handle)); + + email.send(); } - if (titles.size() > 0) { - title = titles.iterator().next().getValue(); - } - - email.addRecipient(ep.getEmail()); - email.addArgument(title); - email.addArgument(coll.getName()); - email.addArgument(handleService.getCanonicalForm(handle)); - - email.send(); } catch (MessagingException e) { log.warn(LogManager.getHeader(context, "notifyOfArchive", - "cannot email user" + " item_id=" + item.getID())); + "cannot email user" + " item_id=" + item.getID())); } } @@ -803,7 +809,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } public void removeUserItemPolicies(Context context, Item item, EPerson e) throws SQLException, AuthorizeException { - if (e != null) { + if (e != null && item.getSubmitter() != null) { //Also remove any lingering authorizations from this user authorizeService.removeEPersonPolicies(context, item, e); //Remove the bundle rights @@ -825,7 +831,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { protected void removeGroupItemPolicies(Context context, Item item, Group e) throws SQLException, AuthorizeException { - if (e != null) { + if (e != null && item.getSubmitter() != null) { //Also remove any lingering authorizations from this user authorizeService.removeGroupPolicies(context, item, e); //Remove the bundle rights @@ -841,7 +847,32 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } @Override - public WorkspaceItem sendWorkflowItemBackSubmission(Context context, XmlWorkflowItem wi, EPerson e, + public void deleteWorkflowByWorkflowItem(Context context, XmlWorkflowItem wi, EPerson e) + throws SQLException, AuthorizeException, IOException { + Item myitem = wi.getItem(); + UUID itemID = myitem.getID(); + Integer workflowID = wi.getID(); + UUID collID = wi.getCollection().getID(); + // stop workflow + deleteAllTasks(context, wi); + context.turnOffAuthorisationSystem(); + //Also clear all info for this step + workflowRequirementsService.clearInProgressUsers(context, wi); + // Remove (if any) the workflowItemroles for this item + workflowItemRoleService.deleteForWorkflowItem(context, wi); + // Now remove the workflow object manually from the database + xmlWorkflowItemService.deleteWrapper(context, wi); + // Now delete the item + itemService.delete(context, myitem); + log.info(LogManager.getHeader(context, "delete_workflow", "workflow_item_id=" + + workflowID + "item_id=" + itemID + + "collection_id=" + collID + "eperson_id=" + + e.getID())); + context.restoreAuthSystemState(); + } + + @Override + public WorkspaceItem sendWorkflowItemBackSubmission(Context context, XmlWorkflowItem wi, EPerson e, String provenance, String rejection_message) throws SQLException, AuthorizeException, @@ -1009,31 +1040,38 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { protected void notifyOfReject(Context c, XmlWorkflowItem wi, EPerson e, String reason) { try { - // Get the item title - String title = wi.getItem().getName(); + // send the notification only if the person was not deleted in the + // meantime between submission and archiving. + EPerson eperson = wi.getSubmitter(); + if (eperson != null) { + // Get the item title + String title = wi.getItem().getName(); - // Get the collection - Collection coll = wi.getCollection(); + // Get the collection + Collection coll = wi.getCollection(); - // Get rejector's name - String rejector = getEPersonName(e); - Locale supportedLocale = I18nUtil.getEPersonLocale(e); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject")); + // Get rejector's name + String rejector = getEPersonName(e); + Locale supportedLocale = I18nUtil.getEPersonLocale(e); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject")); - email.addRecipient(wi.getSubmitter().getEmail()); - email.addArgument(title); - email.addArgument(coll.getName()); - email.addArgument(rejector); - email.addArgument(reason); - email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/mydspace"); + email.addRecipient(eperson.getEmail()); + email.addArgument(title); + email.addArgument(coll.getName()); + email.addArgument(rejector); + email.addArgument(reason); + email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/mydspace"); - email.send(); + email.send(); + } else { + // DO nothing + } } catch (Exception ex) { // log this email error log.warn(LogManager.getHeader(c, "notify_of_reject", - "cannot email user" + " eperson_id" + e.getID() - + " eperson_email" + e.getEmail() - + " workflow_item_id" + wi.getID())); + "cannot email user" + " eperson_id" + e.getID() + + " eperson_email" + e.getEmail() + + " workflow_item_id" + wi.getID())); } } 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 072e2289d7..eded62cc4e 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 @@ -34,6 +34,7 @@ public class AcceptEditRejectAction extends ProcessingAction { public static final int MAIN_PAGE = 0; public static final int REJECT_PAGE = 1; + public static final int SUBMITTER_IS_DELETED_PAGE = 2; //TODO: rename to AcceptAndEditMetadataAction @@ -52,6 +53,8 @@ public class AcceptEditRejectAction extends ProcessingAction { return processMainPage(c, wfi, step, request); case REJECT_PAGE: return processRejectPage(c, wfi, step, request); + case SUBMITTER_IS_DELETED_PAGE: + return processSubmitterIsDeletedPage(c, wfi, request); default: return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } @@ -100,6 +103,23 @@ public class AcceptEditRejectAction extends ProcessingAction { } } + public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + if (request.getParameter("submit_delete") != null) { + XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() + .deleteWorkflowByWorkflowItem(c, wfi, c.getCurrentUser()); + // Delete and send user back to myDspace page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } else if (request.getParameter("submit_keep_it") != null) { + // Do nothing, just send it back to myDspace page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } else { + //Cancel, go back to the main task page + request.setAttribute("page", MAIN_PAGE); + return new ActionResult(ActionResult.TYPE.TYPE_PAGE); + } + } + private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { //Add the provenance for the accept String now = DCDate.getCurrent().toString(); 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 1db060d39d..f4f8126384 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 @@ -33,6 +33,7 @@ public class ReviewAction extends ProcessingAction { public static final int MAIN_PAGE = 0; public static final int REJECT_PAGE = 1; + public static final int SUBMITTER_IS_DELETED_PAGE = 2; @Override @@ -50,6 +51,8 @@ public class ReviewAction extends ProcessingAction { return processMainPage(c, wfi, step, request); case REJECT_PAGE: return processRejectPage(c, wfi, step, request); + case SUBMITTER_IS_DELETED_PAGE: + return processSubmitterIsDeletedPage(c, wfi, request); default: return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } @@ -114,4 +117,21 @@ public class ReviewAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_PAGE); } } + + public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + if (request.getParameter("submit_delete") != null) { + XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() + .deleteWorkflowByWorkflowItem(c, wfi, c.getCurrentUser()); + // Delete and send user back to myDspace page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } else if (request.getParameter("submit_keep_it") != null) { + // Do nothing, just send it back to myDspace page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } else { + //Cancel, go back to the main task page + request.setAttribute("page", MAIN_PAGE); + return new ActionResult(ActionResult.TYPE.TYPE_PAGE); + } + } } 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 74eea2d448..f46d08865d 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 @@ -35,6 +35,7 @@ public class SingleUserReviewAction extends ProcessingAction { public static final int MAIN_PAGE = 0; public static final int REJECT_PAGE = 1; + public static final int SUBMITTER_IS_DELETED_PAGE = 2; public static final int OUTCOME_REJECT = 1; @@ -53,6 +54,8 @@ public class SingleUserReviewAction extends ProcessingAction { return processMainPage(c, wfi, step, request); case REJECT_PAGE: return processRejectPage(c, wfi, step, request); + case SUBMITTER_IS_DELETED_PAGE: + return processSubmitterIsDeletedPage(c, wfi, request); default: return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } @@ -120,4 +123,21 @@ public class SingleUserReviewAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_PAGE); } } + + public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + if (request.getParameter("submit_delete") != null) { + XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() + .deleteWorkflowByWorkflowItem(c, wfi, c.getCurrentUser()); + // Delete and send user back to myDspace page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } else if (request.getParameter("submit_keep_it") != null) { + // Do nothing, just send it back to myDspace page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } else { + //Cancel, go back to the main task page + request.setAttribute("page", MAIN_PAGE); + return new ActionResult(ActionResult.TYPE.TYPE_PAGE); + } + } } 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..d251767b72 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 @@ -72,20 +72,22 @@ public class AssignOriginalSubmitterAction extends UserSelectionAction { @Override public void alertUsersOnActivation(Context c, XmlWorkflowItem wfi, RoleMembers roleMembers) throws IOException, SQLException { - try { - XmlWorkflowService xmlWorkflowService = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService(); - xmlWorkflowService.alertUsersOnTaskActivation(c, wfi, "submit_task", Arrays.asList(wfi.getSubmitter()), - //The arguments - wfi.getItem().getName(), - wfi.getCollection().getName(), - wfi.getSubmitter().getFullName(), - //TODO: message - "New task available.", - xmlWorkflowService.getMyDSpaceLink() - ); - } catch (MessagingException e) { - log.info(LogManager.getHeader(c, "error emailing user(s) for claimed task", + if (wfi.getSubmitter() != null) { + try { + XmlWorkflowService xmlWorkflowService = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService(); + xmlWorkflowService.alertUsersOnTaskActivation(c, wfi, "submit_task", Arrays.asList(wfi.getSubmitter()), + //The arguments + wfi.getItem().getName(), + wfi.getCollection().getName(), + wfi.getSubmitter().getFullName(), + //TODO: message + "New task available.", + xmlWorkflowService.getMyDSpaceLink() + ); + } catch (MessagingException e) { + log.info(LogManager.getHeader(c, "error emailing user(s) for claimed task", "step: " + getParent().getStep().getId() + " workflowitem: " + wfi.getID())); + } } } @@ -105,9 +107,9 @@ public class AssignOriginalSubmitterAction extends UserSelectionAction { .getId() + " to assign a submitter to. Aborting the action."); throw new IllegalStateException(); } - - createTaskForEPerson(c, wfi, step, nextAction, submitter); - + if (submitter != null) { + createTaskForEPerson(c, wfi, step, nextAction, submitter); + } //It is important that we return to the submission page since we will continue our actions with the submitter return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } 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 bf4eecb77d..271eff65c7 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 @@ -77,22 +77,25 @@ public class ClaimAction extends UserSelectionAction { public void alertUsersOnActivation(Context c, XmlWorkflowItem wfi, RoleMembers roleMembers) throws IOException, SQLException { try { + EPerson ep = wfi.getSubmitter(); + String submitterName = null; + if (ep != null) { + submitterName = ep.getFullName(); + } XmlWorkflowService xmlWorkflowService = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService(); xmlWorkflowService.alertUsersOnTaskActivation(c, wfi, "submit_task", roleMembers.getAllUniqueMembers(c), - //The arguments - wfi.getItem().getName(), - wfi.getCollection().getName(), - wfi.getSubmitter().getFullName(), - //TODO: message - "New task available.", - xmlWorkflowService.getMyDSpaceLink() + //The arguments + wfi.getItem().getName(), + wfi.getCollection().getName(), + submitterName, + //TODO: message + "New task available.", + xmlWorkflowService.getMyDSpaceLink() ); } catch (MessagingException e) { log.info(LogManager.getHeader(c, "error emailing user(s) for claimed task", - "step: " + getParent().getStep().getId() + " workflowitem: " + wfi.getID())); + "step: " + getParent().getStep().getId() + " workflowitem: " + wfi.getID())); } - - } @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java index d6ca9fdc1c..d97edd598c 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java @@ -120,6 +120,19 @@ public class PoolTaskServiceImpl implements PoolTaskService { } } + @Override + public void deleteByEperson(Context context, EPerson ePerson) + throws SQLException, AuthorizeException, IOException { + List tasks = findByEperson(context, ePerson); + //Use an iterator to remove the tasks ! + Iterator iterator = tasks.iterator(); + while (iterator.hasNext()) { + PoolTask poolTask = iterator.next(); + iterator.remove(); + delete(context, poolTask); + } + } + @Override public List findByEPerson(Context context, EPerson ePerson) throws SQLException { return poolTaskDAO.findByEPerson(context, ePerson); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRoleServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRoleServiceImpl.java index c96bcd032d..4204c7dcc3 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRoleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRoleServiceImpl.java @@ -58,6 +58,16 @@ public class WorkflowItemRoleServiceImpl implements WorkflowItemRoleService { } } + @Override + public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException, AuthorizeException { + Iterator workflowItemRoles = findByEPerson(context, ePerson).iterator(); + while (workflowItemRoles.hasNext()) { + WorkflowItemRole workflowItemRole = workflowItemRoles.next(); + workflowItemRoles.remove(); + delete(context, workflowItemRole); + } + } + @Override public List findByEPerson(Context context, EPerson ePerson) throws SQLException { return workflowItemRoleDAO.findByEPerson(context, ePerson); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java index 5006760453..613e8e44aa 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java @@ -37,5 +37,7 @@ public interface PoolTaskService extends DSpaceCRUDService { public void deleteByWorkflowItem(Context context, XmlWorkflowItem xmlWorkflowItem) throws SQLException, AuthorizeException; + public void deleteByEperson(Context context, EPerson ePerson) throws SQLException, AuthorizeException, IOException; + public List findByEPerson(Context context, EPerson ePerson) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/WorkflowItemRoleService.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/WorkflowItemRoleService.java index 62c661f02a..9f909231f1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/WorkflowItemRoleService.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/WorkflowItemRoleService.java @@ -33,5 +33,7 @@ public interface WorkflowItemRoleService extends DSpaceCRUDService findByEPerson(Context context, EPerson ePerson) throws SQLException; } diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index bf1b475375..fe991562d8 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -6,6 +6,8 @@ # http://www.dspace.org/license/ # +admin.name = DSpace Administrator + browse.page-title = Browsing DSpace browse.et-al = et al @@ -252,6 +254,12 @@ jsp.dspace-admin.eperson-browse.phone = Telephone jsp.dspace-admin.eperson-browse.self = Self Registered jsp.dspace-admin.eperson-browse.title = E-People jsp.dspace-admin.eperson-confirm-delete.confirm = Are you sure this e-person should be deleted? +jsp.dspace-admin.eperson-confirm-delete.confirm.constraint = This EPerson +jsp.dspace-admin.eperson-confirm-delete.confirm.item = has submitted one or more items which will be kept +jsp.dspace-admin.eperson-confirm-delete.confirm.workspaceitem = has unsubmitted workspace items which will be deleted +jsp.dspace-admin.eperson-confirm-delete.confirm.workflowitem = has an active submission workflow which will be put back into the pool +jsp.dspace-admin.eperson-confirm-delete.confirm.resourcepolicy = has resource policies associated with him which will be deleted +jsp.dspace-admin.eperson-confirm-delete.confirm.tasklistitem = has a workflow task awaiting their attention jsp.dspace-admin.eperson-confirm-delete.heading = Delete e-person: {0} ({1}) jsp.dspace-admin.eperson-confirm-delete.title = Delete E-Person jsp.dspace-admin.eperson-deletion-error.errormsg = The EPerson {0} cannot be deleted because a reference to it exists in the following table(s): @@ -576,7 +584,6 @@ jsp.general.without-contributor jsp.general.without-date = No date given jsp.help = jsp.help.formats.contact1 = Please contact your -jsp.help.formats.contact2 = DSpace Administrator jsp.help.formats.contact3 = if you have questions about a particular format. jsp.help.formats.extensions = Extensions jsp.help.formats.here = (Your Site's Format Support Policy Here) @@ -741,6 +748,12 @@ jsp.mydspace.reject-reason.cancel.button = Cancel Rejecti jsp.mydspace.reject-reason.reject.button = Reject Item jsp.mydspace.reject-reason.text1 = Please enter the reason you are rejecting the submission into the box below. Please indicate in your message whether the submitter should fix a problem and resubmit. jsp.mydspace.reject-reason.title = Enter Reason for Rejection +jsp.mydspace.reject-deleted-submitter.title = The Submitter of this item has been deleted +jsp.mydspace.reject-deleted-submitter.message = Do you want to delete the document, keep it as a task and work on it later or cancel the rejection process? +jsp.mydspace.reject-deleted-submitter-keep-it.button = Keep it I will work on it later +jsp.mydspace.reject-deleted-submitter-delete.button = Delete Item +jsp.mydspace.reject-deleted-submitter-delete.title = Delete successfully +jsp.mydspace.reject-deleted-submitter-delete.info = Reviewing task is done, the submitted item has been successfully removed from the system. jsp.mydspace.remove-item.cancel.button = Cancel Removal jsp.mydspace.remove-item.confirmation = Are you sure you want to remove the following incomplete item? jsp.mydspace.remove-item.remove.button = Remove the Item @@ -1643,6 +1656,7 @@ org.dspace.workflow.WorkflowManager.step1 org.dspace.workflow.WorkflowManager.step2 = The submission must be checked before inclusion in the archive. org.dspace.workflow.WorkflowManager.step3 = The metadata needs to be checked to ensure compliance with the collection's standards, and edited if necessary. org.dspace.workflow.WorkflowManager.untitled = Untitled +org.dspace.workflow.WorkflowManager.deleted-submitter = Unknown (deleted submitter) search.order.asc = Ascending search.order.desc = Descending @@ -1824,6 +1838,11 @@ In response to your request I have the pleasure to send you in attachment a copy Best regards,\n\ {3} <{4}> +itemRequest.admin.response.body.approve = Dear {0},\n\ +In response to your request I have the pleasure to send you in attachment a copy of the file(s) concerning the document: "{2}" ({1}).\n\n\ +Best regards,\n\ +{3} + itemRequest.response.subject.reject = Request copy of document itemRequest.response.body.reject = Dear {0},\n\ In response to your request I regret to inform you that it''s not possible to send you a copy of the file(s) you have requested, concerning the document: "{2}" ({1}), of which I am author (or co-author).\n\n\ @@ -1845,6 +1864,12 @@ itemRequest.response.body.contactRequester = Dear {0},\n\n\ Thanks for your interest! Since the author owns the copyright for this work, I will contact the author and ask permission to send you a copy. I''ll let you know as soon as I hear from the author.\n\n\ Thanks!\n\ {1} <{2}> + +itemRequest.admin.response.body.reject = Dear {0},\n\ +In response to your request I regret to inform you that it''s not possible to send you a copy of the file(s) you have requested, concerning the document: "{2}" ({1}).\n\n\ +Best regards,\n\ +{3} + jsp.request.item.request-form.info2 = Request a document copy: {0} jsp.request.item.request-form.problem = You must fill all the missing fields. jsp.request.item.request-form.reqname = Requester name: @@ -1886,7 +1911,8 @@ jsp.request.item.request-free-acess.free = Change to Open Access jsp.request.item.request-free-acess.name = Name: jsp.request.item.request-free-acess.email = E-mail: org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed = Corresponding Author -org.dspace.app.requestitem.RequestItemHelpdeskStrategy.helpdeskname = Help Desk +org.dspace.app.requestitem.helpdeskname = Help Desk +org.dspace.app.requestitem.default-author-name = DSpace User org.dspace.app.webui.jsptag.ItemTag.restrict = Request a copy jsp.layout.navbar-admin.batchimport = Batch import diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index 8950bfa409..0b36eb398d 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -5,21 +5,46 @@ * * http://www.dspace.org/license/ */ - package org.dspace.eperson; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import java.io.IOException; import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; +import javax.mail.MessagingException; import org.apache.commons.codec.DecoderException; import org.apache.logging.log4j.Logger; +import org.apache.commons.lang.StringUtils; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; +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.Constants; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; +import org.dspace.workflow.WorkflowException; +import org.dspace.workflow.WorkflowItem; +import org.dspace.workflow.WorkflowItemService; +import org.dspace.workflow.WorkflowService; +import org.dspace.workflow.factory.WorkflowServiceFactory; +import org.dspace.workflowbasic.BasicWorkflowItem; +import org.dspace.workflowbasic.factory.BasicWorkflowServiceFactory; +import org.dspace.workflowbasic.service.BasicWorkflowService; import org.junit.Before; import org.junit.Test; @@ -27,9 +52,31 @@ import org.junit.Test; * @author mwood */ public class EPersonTest extends AbstractUnitTest { - protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(EPersonTest.class); + protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + protected BasicWorkflowService basicWorkflowService = BasicWorkflowServiceFactory.getInstance() + .getBasicWorkflowService(); + protected WorkflowItemService workflowItemService = WorkflowServiceFactory.getInstance().getWorkflowItemService(); + protected WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); + protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance() + .getWorkspaceItemService(); + + private Community community = null; + private Collection collection = null; + private Item item = null; + + private static final String EMAIL = "kevin@dspace.org"; + private static final String FIRSTNAME = "Kevin"; + private static final String LASTNAME = "Van de Velde"; + private static final String NETID = "1985"; + private static final String PASSWORD = "test"; + + private static final Logger log = Logger.getLogger(EPersonTest.class); public EPersonTest() { } @@ -38,8 +85,8 @@ public class EPersonTest extends AbstractUnitTest { * 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 + * Other methods can be annotated with @Before here or in subclasses but no + * execution order is guaranteed */ @Before @Override @@ -49,12 +96,14 @@ public class EPersonTest extends AbstractUnitTest { context.turnOffAuthorisationSystem(); try { EPerson eperson = ePersonService.create(context); - eperson.setEmail("kevin@dspace.org"); - eperson.setFirstName(context, "Kevin"); - eperson.setLastName(context, "Van de Velde"); - eperson.setNetid("1985"); - eperson.setPassword("test"); + eperson.setEmail(EMAIL); + eperson.setFirstName(context, FIRSTNAME); + eperson.setLastName(context, LASTNAME); + eperson.setNetid(NETID); + eperson.setPassword(PASSWORD); ePersonService.update(context, eperson); + this.community = communityService.create(null, context); + this.collection = collectionService.create(context, this.community); } catch (SQLException | AuthorizeException ex) { log.error("Error in init", ex); fail("Error in init: " + ex.getMessage()); @@ -71,649 +120,65 @@ public class EPersonTest extends AbstractUnitTest { if (testPerson != null) { ePersonService.delete(context, testPerson); } - } catch (Exception ex) { + } catch (IOException | SQLException | AuthorizeException ex) { log.error("Error in destroy", ex); fail("Error in destroy: " + ex.getMessage()); } + if (item != null) { + try { + item = itemService.find(context, item.getID()); + itemService.delete(context, item); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Error in destroy", ex); + fail("Error in destroy: " + ex.getMessage()); + } + } + if (this.collection != null) { + try { + this.collection = collectionService.find(context, this.collection.getID()); + collectionService.delete(context, this.collection); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Error in destroy", ex); + fail("Error in destroy: " + ex.getMessage()); + } + } + if (this.community != null) { + try { + this.community = communityService.find(context, this.community.getID()); + communityService.delete(context, this.community); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Error in destroy", ex); + fail("Error in destroy: " + ex.getMessage()); + } + } + context.restoreAuthSystemState(); + item = null; + this.collection = null; + this.community = null; super.destroy(); } - - /** - * Test of equals method, of class EPerson. - */ -/* - @Test - public void testEquals() - { - System.out.println("equals"); - Object obj = null; - EPerson instance = null; - boolean expResult = false; - boolean result = instance.equals(obj); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of hashCode method, of class EPerson. - */ -/* - @Test - public void testHashCode() - { - System.out.println("hashCode"); - EPerson instance = null; - int expResult = 0; - int result = instance.hashCode(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of find method, of class EPerson. - */ -/* - @Test - public void testFind() - throws Exception - { - System.out.println("find"); - Context context = null; - int id = 0; - EPerson expResult = null; - EPerson result = EPerson.find(context, id); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of findByEmail method, of class EPerson. - */ -/* - @Test - public void testFindByEmail() - throws Exception - { - System.out.println("findByEmail"); - Context context = null; - String email = ""; - EPerson expResult = null; - EPerson result = EPerson.findByEmail(context, email); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of findByNetid method, of class EPerson. - */ -/* - @Test - public void testFindByNetid() - throws Exception - { - System.out.println("findByNetid"); - Context context = null; - String netid = ""; - EPerson expResult = null; - EPerson result = EPerson.findByNetid(context, netid); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of search method, of class EPerson. - */ -/* - @Test - public void testSearch_Context_String() - throws Exception - { - System.out.println("search"); - Context context = null; - String query = ""; - EPerson[] expResult = null; - EPerson[] result = EPerson.search(context, query); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of search method, of class EPerson. - */ -/* - @Test - public void testSearch_4args() - throws Exception - { - System.out.println("search"); - Context context = null; - String query = ""; - int offset = 0; - int limit = 0; - EPerson[] expResult = null; - EPerson[] result = EPerson.search(context, query, offset, limit); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of searchResultCount method, of class EPerson. - */ -/* - @Test - public void testSearchResultCount() - throws Exception - { - System.out.println("searchResultCount"); - Context context = null; - String query = ""; - int expResult = 0; - int result = EPerson.searchResultCount(context, query); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of findAll method, of class EPerson. - */ -/* - @Test - public void testFindAll() - throws Exception - { - System.out.println("findAll"); - Context context = null; - int sortField = 0; - EPerson[] expResult = null; - EPerson[] result = EPerson.findAll(context, sortField); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of create method, of class EPerson. - */ -/* - @Test - public void testCreate() - throws Exception - { - System.out.println("create"); - Context context = null; - EPerson expResult = null; - EPerson result = EPerson.create(context); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of delete method, of class EPerson. - */ -/* - @Test - public void testDelete() - throws Exception - { - System.out.println("delete"); - EPerson instance = null; - instance.delete(); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getID method, of class EPerson. - */ -/* - @Test - public void testGetID() - { - System.out.println("getID"); - EPerson instance = null; - int expResult = 0; - int result = instance.getID(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getLanguage method, of class EPerson. - */ -/* - @Test - public void testGetLanguage() - { - System.out.println("getLanguage"); - EPerson instance = null; - String expResult = ""; - String result = instance.getLanguage(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setLanguage method, of class EPerson. - */ -/* - @Test - public void testSetLanguage() - { - System.out.println("setLanguage"); - String language = ""; - EPerson instance = null; - instance.setLanguage(language); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getHandle method, of class EPerson. - */ -/* - @Test - public void testGetHandle() - { - System.out.println("getHandle"); - EPerson instance = null; - String expResult = ""; - String result = instance.getHandle(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getEmail method, of class EPerson. - */ -/* - @Test - public void testGetEmail() - { - System.out.println("getEmail"); - EPerson instance = null; - String expResult = ""; - String result = instance.getEmail(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setEmail method, of class EPerson. - */ -/* - @Test - public void testSetEmail() - { - System.out.println("setEmail"); - String s = ""; - EPerson instance = null; - instance.setEmail(s); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getNetid method, of class EPerson. - */ -/* - @Test - public void testGetNetid() - { - System.out.println("getNetid"); - EPerson instance = null; - String expResult = ""; - String result = instance.getNetid(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setNetid method, of class EPerson. - */ -/* - @Test - public void testSetNetid() - { - System.out.println("setNetid"); - String s = ""; - EPerson instance = null; - instance.setNetid(s); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getFullName method, of class EPerson. - */ -/* - @Test - public void testGetFullName() - { - System.out.println("getFullName"); - EPerson instance = null; - String expResult = ""; - String result = instance.getFullName(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getFirstName method, of class EPerson. - */ -/* - @Test - public void testGetFirstName() - { - System.out.println("getFirstName"); - EPerson instance = null; - String expResult = ""; - String result = instance.getFirstName(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setFirstName method, of class EPerson. - */ -/* - @Test - public void testSetFirstName() - { - System.out.println("setFirstName"); - String firstname = ""; - EPerson instance = null; - instance.setFirstName(firstname); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getLastName method, of class EPerson. - */ -/* - @Test - public void testGetLastName() - { - System.out.println("getLastName"); - EPerson instance = null; - String expResult = ""; - String result = instance.getLastName(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setLastName method, of class EPerson. - */ -/* - @Test - public void testSetLastName() - { - System.out.println("setLastName"); - String lastname = ""; - EPerson instance = null; - instance.setLastName(lastname); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setCanLogIn method, of class EPerson. - */ -/* - @Test - public void testSetCanLogIn() - { - System.out.println("setCanLogIn"); - boolean login = false; - EPerson instance = null; - instance.setCanLogIn(login); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of canLogIn method, of class EPerson. - */ -/* - @Test - public void testCanLogIn() - { - System.out.println("canLogIn"); - EPerson instance = null; - boolean expResult = false; - boolean result = instance.canLogIn(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setRequireCertificate method, of class EPerson. - */ -/* - @Test - public void testSetRequireCertificate() - { - System.out.println("setRequireCertificate"); - boolean isrequired = false; - EPerson instance = null; - instance.setRequireCertificate(isrequired); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getRequireCertificate method, of class EPerson. - */ -/* - @Test - public void testGetRequireCertificate() - { - System.out.println("getRequireCertificate"); - EPerson instance = null; - boolean expResult = false; - boolean result = instance.getRequireCertificate(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setSelfRegistered method, of class EPerson. - */ -/* - @Test - public void testSetSelfRegistered() - { - System.out.println("setSelfRegistered"); - boolean sr = false; - EPerson instance = null; - instance.setSelfRegistered(sr); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getSelfRegistered method, of class EPerson. - */ -/* - @Test - public void testGetSelfRegistered() - { - System.out.println("getSelfRegistered"); - EPerson instance = null; - boolean expResult = false; - boolean result = instance.getSelfRegistered(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getMetadata method, of class EPerson. - */ -/* - @Test - public void testGetMetadata() - { - System.out.println("getMetadata"); - String field = ""; - EPerson instance = null; - String expResult = ""; - String result = instance.getMetadata(field); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setMetadata method, of class EPerson. - */ -/* - @Test - public void testSetMetadata() - { - System.out.println("setMetadata"); - String field = ""; - String value = ""; - EPerson instance = null; - instance.setMetadata(field, value); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setPassword method, of class EPerson. - */ -/* - @Test - public void testSetPassword() - { - System.out.println("setPassword"); - String s = ""; - EPerson instance = null; - instance.setPassword(s); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of setPasswordHash method, of class EPerson. - */ -/* - @Test - public void testSetPasswordHash() - { - System.out.println("setPasswordHash"); - PasswordHash password = null; - EPerson instance = null; - instance.setPasswordHash(password); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getPasswordHash method, of class EPerson. - */ -/* - @Test - public void testGetPasswordHash() - { - System.out.println("getPasswordHash"); - EPerson instance = null; - PasswordHash expResult = null; - PasswordHash result = instance.getPasswordHash(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - /** * Test of checkPassword method, of class EPerson. + * + * @throws SQLException + * @throws DecoderException */ @Test public void testCheckPassword() - throws SQLException, DecoderException { - EPerson eperson = ePersonService.findByEmail(context, "kevin@dspace.org"); - ePersonService.checkPassword(context, eperson, "test"); + throws SQLException, DecoderException { + EPerson eperson = ePersonService.findByEmail(context, EMAIL); + ePersonService.checkPassword(context, eperson, PASSWORD); } - /** - * Test of update method, of class EPerson. - */ -/* - @Test - public void testUpdate() - throws Exception - { - System.out.println("update"); - EPerson instance = null; - instance.update(); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - /** * Test of getType method, of class EPerson. + * + * @throws SQLException */ @Test public void testGetType() - throws SQLException { + throws SQLException { System.out.println("getType"); int expResult = Constants.EPERSON; int result = eperson.getType(); @@ -721,37 +186,350 @@ public class EPersonTest extends AbstractUnitTest { } /** - * Test of getDeleteConstraints method, of class EPerson. + * Simple test if deletion of an EPerson throws any exceptions. + * + * @throws SQLException + * @throws AuthorizeException */ -/* @Test - public void testGetDeleteConstraints() - throws Exception - { - System.out.println("getDeleteConstraints"); - EPerson instance = null; - List expResult = null; - List result = instance.getDeleteConstraints(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + public void testDeleteEPerson() throws SQLException, AuthorizeException { + EPerson deleteEperson = ePersonService.findByEmail(context, EMAIL); + context.turnOffAuthorisationSystem(); + + try { + ePersonService.delete(context, deleteEperson); + } catch (AuthorizeException | IOException ex) { + log.error("Cannot delete EPersion, caught " + ex.getClass().getName() + ":", ex); + fail("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + context.restoreAuthSystemState(); + context.commit(); + EPerson fundDeletedEperson = ePersonService.findByEmail(context, EMAIL); + assertNull("EPerson has not been deleted correctly!", fundDeletedEperson); } -*/ /** - * Test of getName method, of class EPerson. + * Test that an EPerson has a delete constraint if it submitted an Item. + * + * @throws SQLException */ -/* @Test - public void testGetName() - { - System.out.println("getName"); - EPerson instance = null; - String expResult = ""; - String result = instance.getName(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + public void testDeletionConstraintOfSubmitter() + throws SQLException { + EPerson ep = ePersonService.findByEmail(context, EMAIL); + try { + item = prepareItem(ep); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Caught an Exception while initializing an Item. " + ex.getClass().getName() + ": ", ex); + fail("Caught an Exception while initializing an Item. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + + context.turnOffAuthorisationSystem(); + + List tableList = ePersonService.getDeleteConstraints(context, ep); + Iterator iterator = tableList.iterator(); + while (iterator.hasNext()) { + String tableName = iterator.next(); + if (StringUtils.equalsIgnoreCase(tableName, "item")) { + return; + } + } + // if we did not get and EPersonDeletionException or it did not contain the item table, we should fail + // because it was not recognized that the EPerson is used as submitter. + fail("It was not recognized that a EPerson is referenced in the item table."); + } + + /** + * Test that the submitter is set to null if the specified EPerson was + * deleted using cascading. + * + * @throws SQLException + * @throws AuthorizeException + */ + @Test + public void testDeletionOfSubmitterWithAnItem() + throws SQLException, AuthorizeException { + EPerson ep = ePersonService.findByEmail(context, EMAIL); + try { + item = prepareItem(ep); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Caught an Exception while initializing an Item. " + ex.getClass().getName() + ": ", ex); + fail("Caught an Exception while initializing an Item. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + assertNotNull(item); + context.turnOffAuthorisationSystem(); + try { + ePersonService.delete(context, ep); + } catch (SQLException | IOException | AuthorizeException ex) { + if (ex.getCause() instanceof EPersonDeletionException) { + fail("Caught an EPersonDeletionException while trying to cascading delete an EPerson: " + + ex.getMessage()); + } else { + log.error("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + ": ", ex); + fail("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + } + item = itemService.find(context, item.getID()); + assertNotNull("Could not load item after cascading deletion of the submitter.", item); + assertNull("Cascading deletion of an EPerson did not set the submitter of an submitted item null.", + item.getSubmitter()); + } + + /** + * Test that an unsubmitted workspace items get deleted when an EPerson gets + * deleted. + * + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + @Test + public void testCascadingDeletionOfUnsubmittedWorkspaceItem() + throws SQLException, AuthorizeException, IOException { + EPerson ep = ePersonService.findByEmail(context, EMAIL); + + context.turnOffAuthorisationSystem(); + WorkspaceItem wsi = prepareWorkspaceItem(ep); + Item item = wsi.getItem(); + itemService.addMetadata(context, item, "dc", "title", null, "en", "Testdocument 1"); + itemService.update(context, item); + context.restoreAuthSystemState(); + context.commit(); + context.turnOffAuthorisationSystem(); + + try { + ePersonService.delete(context, ep); + } catch (SQLException | IOException | AuthorizeException ex) { + if (ex.getCause() instanceof EPersonDeletionException) { + fail("Caught an EPersonDeletionException while trying to cascading delete an EPerson: " + + ex.getMessage()); + } else { + log.error("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": ", ex); + fail("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + } + + context.restoreAuthSystemState(); + context.commit(); + + try { + WorkspaceItem restoredWsi = workspaceItemService.find(context, wsi.getID()); + Item restoredItem = itemService.find(context, item.getID()); + assertNull("An unsubmited WorkspaceItem wasn't deleted while cascading deleting the submitter.", + restoredWsi); + assertNull("An unsubmited Item wasn't deleted while cascading deleting the submitter.", restoredItem); + } catch (SQLException ex) { + log.error("SQLException while trying to load previously stored. " + ex); + } + } + + /** + * Test that submitted but not yet archived items do not get delete while + * cascading deletion of an EPerson. + * + * @throws SQLException + * @throws AuthorizeException + * @throws IOException + * @throws MessagingException + * @throws WorkflowException + */ + @Test + public void testCascadingDeleteSubmitterPreservesWorkflowItems() + throws SQLException, AuthorizeException, IOException, MessagingException, WorkflowException { + EPerson ep = ePersonService.findByEmail(context, EMAIL); + WorkspaceItem wsi = null; + + try { + wsi = prepareWorkspaceItem(ep); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Caught an Exception while initializing an WorkspaceItem. " + ex.getClass().getName() + + ": ", ex); + fail("Caught an Exception while initializing an WorkspaceItem. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + assertNotNull(wsi); + context.turnOffAuthorisationSystem(); + + // for this test we need an workflow item that is not yet submitted. Currently the Workflow advance + // automatically if nobody is defined to perform a step (see comments of DS-1941). + // We need to configure a collection to have a workflow step and set a person to perform this step. Then we can + // create an item, start the workflow and delete the item's submitter. + Group wfGroup = collectionService.createWorkflowGroup(context, wsi.getCollection(), 1); + collectionService.update(context, wsi.getCollection()); + EPerson groupMember = ePersonService.create(context); + groupMember.setEmail("testCascadingDeleteSubmitterPreservesWorkflowItems2@example.org"); + ePersonService.update(context, groupMember); + wfGroup.addMember(groupMember); + groupService.update(context, wfGroup); + + // DSpace currently contains two workflow systems. The newer XMLWorfklow needs additional tables that are not + // part of the test database yet. While it is expected that it becomes the default workflow system (DS-2059) + // one day, this won't happen before it its backported to JSPUI (DS-2121). + // TODO: add tests using the configurable workflowsystem + int wfiID = workflowService.startWithoutNotify(context, wsi).getID(); + context.restoreAuthSystemState(); + context.commit(); + context.turnOffAuthorisationSystem(); + + // check that the workflow item exists. + assertNotNull("Cannot find currently created WorkflowItem!", workflowItemService.find(context, wfiID)); + + // delete the submitter + try { + ePersonService.delete(context, ep); + } catch (SQLException | IOException | AuthorizeException ex) { + if (ex.getCause() instanceof EPersonDeletionException) { + fail("Caught an EPersonDeletionException while trying to cascading delete an EPerson: " + + ex.getMessage()); + } else { + log.error("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": ", ex); + fail("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + } + + context.restoreAuthSystemState(); + context.commit(); + context.turnOffAuthorisationSystem(); + + // check whether the workflow item still exists. + WorkflowItem wfi = workflowItemService.find(context, wfiID); + assertNotNull("Could not load WorkflowItem after cascading deletion of the submitter.", wfi); + assertNull("Cascading deletion of an EPerson did not set the submitter of an submitted WorkflowItem null.", + wfi.getSubmitter()); + } + + /** + * Test that deleting a Person that claimed a task, repools the task. + * + * @throws SQLException + * @throws AuthorizeException + * @throws MessagingException + * @throws IOException + * @throws WorkflowException + */ + @Test + public void testDeletingAnTaskHolderUnclaimsTask() + throws SQLException, AuthorizeException, MessagingException, IOException, WorkflowException { + EPerson ep = ePersonService.findByEmail(context, EMAIL); + WorkspaceItem wsi = null; + + try { + wsi = prepareWorkspaceItem(ep); + } catch (SQLException | AuthorizeException | IOException ex) { + log.error("Caught an Exception while initializing an WorkspaceItem. " + ex.getClass().getName() + + ": ", ex); + fail("Caught an Exception while initializing an WorkspaceItem. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + assertNotNull(wsi); + context.turnOffAuthorisationSystem(); + + // for this test we need an workflow item that is not yet submitted. Currently the Workflow advance + // automatically if nobody is defined to perform a step (see comments of DS-1941). + // We need to configure a collection to have a workflow step and set at least two persons to perform this step. + // Then we can create an item, start the workflow, claim the task and delete the person who claimed it. + Group wfGroup = collectionService.createWorkflowGroup(context, wsi.getCollection(), 1); + collectionService.update(context, wsi.getCollection()); + EPerson worker = ePersonService.create(context); + worker.setEmail("testDeletingAnTaskHolderUnclaimsTask2@example.org"); + ePersonService.update(context, worker); + wfGroup.addMember(worker); + EPerson coWorker = ePersonService.create(context); + coWorker.setEmail("testDeletingAnTaskHolderUnclamisTask3example.org"); + ePersonService.update(context, coWorker); + wfGroup.addMember(coWorker); + groupService.update(context, wfGroup); + context.restoreAuthSystemState(); + context.commit(); + context.turnOffAuthorisationSystem(); + // DSpace currently contains two workflow systems. The newer XMLWorfklow needs additional tables that are not + // part of the test database yet. While it is expected that it becomes the default workflow system (DS-2059) + // one day, this won't happen before it its backported to JSPUI (DS-2121). + // TODO: add tests using the configurable xmlworkflow system + BasicWorkflowItem wfi = basicWorkflowService.startWithoutNotify(context, workspaceItemService + .find(context, wsi.getID())); + context.commit(); + + basicWorkflowService.claim(context, wfi, worker); + context.restoreAuthSystemState(); + context.commit(); + context.turnOffAuthorisationSystem(); + + // delete the task owner + try { + ePersonService.delete(context, ePersonService.find(context, worker.getID())); + } catch (SQLException | IOException | AuthorizeException ex) { + if (ex.getCause() instanceof EPersonDeletionException) { + fail("Caught an EPersonDeletionException while trying to cascading delete an EPerson: " + + ex.getMessage()); + } else { + log.error("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + ": ", ex); + fail("Caught an Exception while deleting an EPerson. " + ex.getClass().getName() + + ": " + ex.getMessage()); + } + } + context.restoreAuthSystemState(); + context.commit(); + context.turnOffAuthorisationSystem(); + // check whether the workflow item still exists and is unclaimed. + BasicWorkflowItem bwfi = BasicWorkflowServiceFactory.getInstance().getBasicWorkflowItemService() + .find(context, wfi.getID()); + assertNotNull("Could not load WorkflowItem after cascading deletion of its owner.", wfi); + assertNull("Cascading deletion of an EPerson did not repooled a claimed task.", bwfi.getOwner()); + } + + /** + * Creates an item, sets the specified submitter. + * + * This method is just an shortcut, so we must not use all the code again + * and again. + * + * @param submitter + * @return the created item. + * @throws SQLException + * @throws AuthorizeException + * @throws IOException + */ + private Item prepareItem(EPerson submitter) + throws SQLException, AuthorizeException, IOException { + context.turnOffAuthorisationSystem(); + WorkspaceItem wsi = prepareWorkspaceItem(submitter); + item = installItemService.installItem(context, wsi); + //we need to commit the changes so we don't block the table for testing + context.restoreAuthSystemState(); + return item; + } + + /** + * Creates a WorkspaceItem and sets the specified submitter. + * + * This method is just an shortcut, so we must not use all the code again + * and again. + * + * @param submitter + * @return the created WorkspaceItem. + * @throws SQLException + * @throws AuthorizeException + * @throws IOException + */ + private WorkspaceItem prepareWorkspaceItem(EPerson submitter) + throws SQLException, AuthorizeException, IOException { + context.turnOffAuthorisationSystem(); + // create a community, a collection and a WorkspaceItem + + WorkspaceItem wsi = workspaceItemService.create(context, this.collection, false); + // set the submitter + wsi.getItem().setSubmitter(submitter); + workspaceItemService.update(context, wsi); + context.restoreAuthSystemState(); + return wsi; } -*/ } diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java index afe8ff5387..7d8f5b3446 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java @@ -51,7 +51,9 @@ public class ItemRepositoryBuilder { doc.addField("item.id", item.getId()); doc.addField("item.public", item.isPublic()); doc.addField("item.lastmodified", item.getLastModifiedDate()); - doc.addField("item.submitter", item.getSubmitter()); + if (item.getSubmitter() != null) { + doc.addField("item.submitter", item.getSubmitter()); + } doc.addField("item.handle", item.getHandle()); doc.addField("item.deleted", item.isDeleted()); diff --git a/dspace/config/emails/request_item.to_admin b/dspace/config/emails/request_item.to_admin new file mode 100644 index 0000000000..244fa5647b --- /dev/null +++ b/dspace/config/emails/request_item.to_admin @@ -0,0 +1,13 @@ +Subject: Request copy of document + +Dear Administrator, + +A user of {7}, named {0} and using the email {1}, requested a copy of the file(s) associated with the document: "{4}" ({3}). + +This request came along with the following message: + +"{5}" + +To answer, click {6}. + +PLEASE REDIRECT THIS MESSAGE TO THE AUTHOR(S). From a3d37c3b5b9946e89de0ed8b2cca7bdd3f38d3ff Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Thu, 29 Nov 2018 15:50:49 +0100 Subject: [PATCH 002/749] [DS-4036] Resolving changes requested enduring review. --- .../dspace/workflowbasic/BasicWorkflowServiceImpl.java | 9 +++------ .../org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java | 3 +-- .../actions/processingaction/AcceptEditRejectAction.java | 6 +++++- .../state/actions/processingaction/ReviewAction.java | 6 +++++- .../actions/processingaction/SingleUserReviewAction.java | 6 +++++- dspace-api/src/main/resources/Messages.properties | 2 +- .../src/test/java/org/dspace/eperson/EPersonTest.java | 8 ++++---- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java index f01ae510e3..2255ab293d 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java @@ -799,8 +799,7 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { // Get submitter EPerson ep = item.getSubmitter(); - // send the notification only if the person was not deleted in the - // meantime between submission and archiving. + // send the notification to the submitter unless the submitter eperson has been deleted if (ep != null) { // Get the Locale Locale supportedLocale = I18nUtil.getEPersonLocale(ep); @@ -884,10 +883,8 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { workflowItemService.deleteWrapper(context, wi); // Now delete the item itemService.delete(context, myitem); - log.info(LogManager.getHeader(context, "delete_workflow", "workflow_item_id=" - + workflowID + "item_id=" + itemID - + "collection_id=" + collID + "eperson_id=" - + e.getID())); + log.info(LogManager.getHeader(context, "delete_workflow", String.format("workflow_item_id=%s " + + "item_id=%s collection_id=%s eperson_id=%s", workflowID, itemID, collID, e.getID()))); } @Override 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 2ce7076f58..ae89b5ea8f 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -566,8 +566,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { try { // Get submitter EPerson ep = item.getSubmitter(); - // send the notification only if the person was not deleted in the - // meantime between submission and archiving. + // send the notification to the submitter unless the submitter eperson has been deleted if (null != ep) { // Get the Locale Locale supportedLocale = I18nUtil.getEPersonLocale(ep); 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 eded62cc4e..636a5cf0ef 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 @@ -69,7 +69,11 @@ public class AcceptEditRejectAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } else if (request.getParameter("submit_reject") != null) { // Make sure we indicate which page we want to process - request.setAttribute("page", REJECT_PAGE); + if (wfi.getSubmitter() == null) { + request.setAttribute("page", SUBMITTER_IS_DELETED_PAGE); + } else { + 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 { 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 f4f8126384..87a7f22e89 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 @@ -67,7 +67,11 @@ public class ReviewAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } else if (request.getParameter("submit_reject") != null) { // Make sure we indicate which page we want to process - request.setAttribute("page", REJECT_PAGE); + if (wfi.getSubmitter() == null) { + request.setAttribute("page", SUBMITTER_IS_DELETED_PAGE); + } else { + 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 { 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 f46d08865d..79670ccfed 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 @@ -70,7 +70,11 @@ public class SingleUserReviewAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } else if (request.getParameter("submit_reject") != null) { // Make sure we indicate which page we want to process - request.setAttribute("page", REJECT_PAGE); + if (wfi.getSubmitter() == null) { + request.setAttribute("page", SUBMITTER_IS_DELETED_PAGE); + } else { + 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) { diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index fe991562d8..974c71083d 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -1839,7 +1839,7 @@ Best regards,\n\ {3} <{4}> itemRequest.admin.response.body.approve = Dear {0},\n\ -In response to your request I have the pleasure to send you in attachment a copy of the file(s) concerning the document: "{2}" ({1}).\n\n\ +In response to your request please see the attached copy of the file(s) related to the document: "{2}" ({1}).\n\n\ Best regards,\n\ {3} diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index 0b36eb398d..c5202088a5 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -70,7 +70,7 @@ public class EPersonTest extends AbstractUnitTest { private Collection collection = null; private Item item = null; - private static final String EMAIL = "kevin@dspace.org"; + private static final String EMAIL = "test@example.com"; private static final String FIRSTNAME = "Kevin"; private static final String LASTNAME = "Van de Velde"; private static final String NETID = "1985"; @@ -116,7 +116,7 @@ public class EPersonTest extends AbstractUnitTest { public void destroy() { context.turnOffAuthorisationSystem(); try { - EPerson testPerson = ePersonService.findByEmail(context, "kevin@dspace.org"); + EPerson testPerson = ePersonService.findByEmail(context, EMAIL); if (testPerson != null) { ePersonService.delete(context, testPerson); } @@ -205,8 +205,8 @@ public class EPersonTest extends AbstractUnitTest { } context.restoreAuthSystemState(); context.commit(); - EPerson fundDeletedEperson = ePersonService.findByEmail(context, EMAIL); - assertNull("EPerson has not been deleted correctly!", fundDeletedEperson); + EPerson findDeletedEperson = ePersonService.findByEmail(context, EMAIL); + assertNull("EPerson has not been deleted correctly!", findDeletedEperson); } /** From 3890c11bf7ab28dc465f3a24dc6296b466b67fa4 Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Fri, 30 Nov 2018 12:34:24 +0100 Subject: [PATCH 003/749] [DS-4036] Resolving merge conflicts. --- .../java/org/dspace/eperson/EPersonDeletionException.java | 2 +- dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java index 93e2eddb58..b86d5f5e8e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonDeletionException.java @@ -9,7 +9,7 @@ package org.dspace.eperson; import java.util.List; -import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.ArrayUtils; /** * Exception indicating that an EPerson may not be deleted due to the presence diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index c5202088a5..1e191d56bc 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -19,8 +19,8 @@ import java.util.List; import javax.mail.MessagingException; import org.apache.commons.codec.DecoderException; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.apache.commons.lang.StringUtils; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; @@ -76,7 +76,7 @@ public class EPersonTest extends AbstractUnitTest { private static final String NETID = "1985"; private static final String PASSWORD = "test"; - private static final Logger log = Logger.getLogger(EPersonTest.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(EPersonTest.class); public EPersonTest() { } From d8ca94d304c831d6b11240da7d9ddb2df671848b Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Wed, 27 Mar 2019 17:26:41 +0100 Subject: [PATCH 004/749] IPMatcher: Fix netmask conversion --- .../java/org/dspace/authenticate/IPMatcher.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java b/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java index 955b6c86d3..439e53af1d 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java @@ -87,13 +87,16 @@ public class IPMatcher { + ipSpec); } - int maskBytes = maskBits / 8; - for (int i = 0; i < maskBytes; i++) { - netmask[i] = (byte) 0Xff; - } - netmask[maskBytes] = (byte) ((byte) 0Xff << 8 - (maskBits % 8)); // FIXME test! - for (int i = maskBytes + 1; i < (128 / 8); i++) { - netmask[i] = 0; + for (int i = 0; i < netmask.length; i++) { + if (maskBits <= 0) { + netmask[i] = 0; + } else if (maskBits > 8) { + netmask[i] = (byte) 0Xff; + } else { + netmask[i] = (byte) ((byte) 0Xff << 8 - maskBits); + } + + maskBits = maskBits - 8; } break; case 1: // No explicit mask: fill the mask with 1s From 8b1a75d309e0e2e5bddad1eebb8258c1679fd742 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Mon, 1 Apr 2019 18:49:27 +0200 Subject: [PATCH 005/749] IPMatcher: Add test to verify parsing of netmask --- .../test/java/org/dspace/authenticate/IPMatcherTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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..31ca92e02a 100644 --- a/dspace-api/src/test/java/org/dspace/authenticate/IPMatcherTest.java +++ b/dspace-api/src/test/java/org/dspace/authenticate/IPMatcherTest.java @@ -153,6 +153,14 @@ public class IPMatcherTest { assertFalse(ipMatcher.match("0:0:0:0:0:0:0:1")); } + @Test + public void testIPv6FullMaskMatching() throws Exception { + final IPMatcher ipMatcher = new IPMatcher("::2/128"); + + assertTrue(ipMatcher.match("0:0:0:0:0:0:0:2")); + assertFalse(ipMatcher.match("0:0:0:0:0:0:0:1")); + } + @Test public void testAsteriskMatchingSuccess() throws Exception { From 003daf0a054932e600569966be938ef8844bec1b Mon Sep 17 00:00:00 2001 From: xuejiangtao <779480193@qq.com> Date: Wed, 15 May 2019 23:26:02 +0800 Subject: [PATCH 006/749] remove the condition that is always 'true' --- .../src/main/java/org/dspace/app/util/IndexVersion.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java index d8b2d6868a..a01c8af616 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java +++ b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java @@ -252,10 +252,6 @@ public class IndexVersion { return GREATER_THAN; } else if (firstMinor < secondMinor) { return LESS_THAN; - } else { - // This is an impossible scenario. - // This 'else' should never be triggered since we've checked for equality above already - return EQUAL; } } From 6ad6186375d596014530fc502e8d721eb197d543 Mon Sep 17 00:00:00 2001 From: xuejiangtao <779480193@qq.com> Date: Mon, 20 May 2019 22:54:45 +0800 Subject: [PATCH 007/749] add default return statement --- dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java index a01c8af616..0c4e475339 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java +++ b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java @@ -253,6 +253,7 @@ public class IndexVersion { } else if (firstMinor < secondMinor) { return LESS_THAN; } + return EQUAL; } /** From 9766fe131d241bf55695b7b8768d5a33ac62940b Mon Sep 17 00:00:00 2001 From: xuejiangtao <779480193@qq.com> Date: Tue, 21 May 2019 21:52:36 +0800 Subject: [PATCH 008/749] remove the last if statement to avoid the dead code --- dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java index 0c4e475339..7bdaa95b5c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java +++ b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java @@ -250,10 +250,9 @@ public class IndexVersion { } else if (firstMinor > secondMinor) { // If we get here, major versions must be EQUAL. Now, time to check our minor versions return GREATER_THAN; - } else if (firstMinor < secondMinor) { + } else { return LESS_THAN; } - return EQUAL; } /** From 08c7da4446109a440e0263266cc173db4a6d85cb Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 21 Nov 2019 13:22:09 +0100 Subject: [PATCH 009/749] Implementing file import and export scripts --- .../org/dspace/app/bulkedit/DSpaceCSV.java | 11 + .../dspace/app/bulkedit/MetadataExport.java | 290 +-- .../dspace/app/bulkedit/MetadataImport.java | 1862 ++++++++++------- .../content/MetadataExportServiceImpl.java | 130 ++ .../service/MetadataExportService.java | 26 + .../org/dspace/scripts/DSpaceRunnable.java | 16 + .../dspace/scripts/ProcessServiceImpl.java | 59 + .../handler/DSpaceRunnableHandler.java | 9 + .../CommandLineDSpaceRunnableHandler.java | 22 + .../scripts/service/ProcessService.java | 11 + .../app/rest/ScriptProcessesController.java | 9 +- .../DSpaceApiExceptionControllerAdvice.java | 5 +- .../rest/repository/ScriptRestRepository.java | 41 +- .../impl/RestDSpaceRunnableHandler.java | 37 +- dspace/config/spring/rest/scripts.xml | 9 + 15 files changed, 1501 insertions(+), 1036 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java create mode 100644 dspace/config/spring/rest/scripts.xml diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index ea95be7e72..98f343c96f 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Serializable; @@ -27,7 +28,9 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.pdfbox.util.Charsets; import org.dspace.authority.AuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; @@ -637,6 +640,14 @@ public class DSpaceCSV implements Serializable { out.close(); } + public InputStream getInputStream() { + StringBuilder stringBuilder = new StringBuilder(); + for (String csvLine : getCSVLinesAsStringArray()) { + stringBuilder.append(csvLine + "\n"); + } + return IOUtils.toInputStream(stringBuilder.toString(), Charsets.UTF_8); + } + /** * Is it Ok to export this value? When exportAll is set to false, we don't export * some of the metadata elements. diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java index bc015ef5e0..f47e79d0ee 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -7,272 +7,84 @@ */ package org.dspace.app.bulkedit; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import com.google.common.collect.Iterators; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.commons.cli.PosixParser; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Constants; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.content.service.MetadataExportService; import org.dspace.core.Context; -import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.scripts.DSpaceRunnable; +import org.springframework.beans.factory.annotation.Autowired; /** * Metadata exporter to allow the batch export of metadata into a file * * @author Stuart Lewis */ -public class MetadataExport { - /** - * The items to export - */ - protected Iterator toExport; +public class MetadataExport extends DSpaceRunnable { - protected ItemService itemService; + private Context c = null; + private boolean help = false; + private String filename = null; + private String handle = null; + private boolean exportAllMetadata = false; + private boolean exportAllItems = false; + + @Autowired + private MetadataExportService metadataExportService; protected Context context; - /** - * Whether to export all metadata, or just normally edited metadata - */ - protected boolean exportAll; - - protected MetadataExport() { - itemService = ContentServiceFactory.getInstance().getItemService(); + private MetadataExport() { + Options options = constructOptions(); + this.options = options; } - /** - * Set up a new metadata export - * - * @param c The Context - * @param toExport The ItemIterator of items to export - * @param exportAll whether to export all metadata or not (include handle, provenance etc) - */ - public MetadataExport(Context c, Iterator toExport, boolean exportAll) { - itemService = ContentServiceFactory.getInstance().getItemService(); - - // Store the export settings - this.toExport = toExport; - this.exportAll = exportAll; - this.context = c; - } - - /** - * Method to export a community (and sub-communities and collections) - * - * @param c The Context - * @param toExport The Community to export - * @param exportAll whether to export all metadata or not (include handle, provenance etc) - */ - public MetadataExport(Context c, Community toExport, boolean exportAll) { - itemService = ContentServiceFactory.getInstance().getItemService(); - - try { - // Try to export the community - this.toExport = buildFromCommunity(c, toExport, 0); - this.exportAll = exportAll; - this.context = c; - } catch (SQLException sqle) { - // Something went wrong... - System.err.println("Error running exporter:"); - sqle.printStackTrace(System.err); - System.exit(1); - } - } - - /** - * Build an array list of item ids that are in a community (include sub-communities and collections) - * - * @param context DSpace context - * @param community The community to build from - * @param indent How many spaces to use when writing out the names of items added - * @return The list of item ids - * @throws SQLException if database error - */ - protected Iterator buildFromCommunity(Context context, Community community, int indent) - throws SQLException { - // Add all the collections - List collections = community.getCollections(); - Iterator result = null; - for (Collection collection : collections) { - for (int i = 0; i < indent; i++) { - System.out.print(" "); - } - - Iterator items = itemService.findByCollection(context, collection); - result = addItemsToResult(result, items); - - } - // Add all the sub-communities - List communities = community.getSubcommunities(); - for (Community subCommunity : communities) { - for (int i = 0; i < indent; i++) { - System.out.print(" "); - } - Iterator items = buildFromCommunity(context, subCommunity, indent + 1); - result = addItemsToResult(result, items); - } - - return result; - } - - private Iterator addItemsToResult(Iterator result, Iterator items) { - if (result == null) { - result = items; - } else { - result = Iterators.concat(result, items); - } - - return result; - } - - /** - * Run the export - * - * @return the exported CSV lines - */ - public DSpaceCSV export() { - try { - Context.Mode originalMode = context.getCurrentMode(); - context.setMode(Context.Mode.READ_ONLY); - - // Process each item - DSpaceCSV csv = new DSpaceCSV(exportAll); - while (toExport.hasNext()) { - Item item = toExport.next(); - csv.addItem(item); - context.uncacheEntity(item); - } - - context.setMode(originalMode); - // Return the results - return csv; - } catch (Exception e) { - // Something went wrong... - System.err.println("Error exporting to CSV:"); - e.printStackTrace(); - return null; - } - } - - /** - * Print the help message - * - * @param options The command line options the user gave - * @param exitCode the system exit code to use - */ - private static void printHelp(Options options, int exitCode) { - // print the help message - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MetadataExport\n", options); - System.out.println("\nfull export: metadataexport -f filename"); - System.out.println("partial export: metadataexport -i handle -f filename"); - System.exit(exitCode); - } - - /** - * main method to run the metadata exporter - * - * @param argv the command line arguments given - * @throws Exception if error occurs - */ - public static void main(String[] argv) throws Exception { - // Create an options object and populate it - CommandLineParser parser = new PosixParser(); - + private Options constructOptions() { Options options = new Options(); options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)"); options.addOption("f", "file", true, "destination where you want file written"); + options.getOption("f").setRequired(true); options.addOption("a", "all", false, "include all metadata fields that are not normally changed (e.g. provenance)"); options.addOption("h", "help", false, "help"); - CommandLine line = null; + return options; + } - try { - line = parser.parse(options, argv); - } catch (ParseException pe) { - System.err.println("Error with commands."); - printHelp(options, 1); - System.exit(0); + public void internalRun() throws Exception { + if (help) { + handler.logInfo("\nfull export: metadataexport -f filename"); + handler.logInfo("partial export: metadataexport -i handle -f filename"); + printHelp(); + return; } - if (line.hasOption('h')) { - printHelp(options, 0); - } - - // Check a filename is given - if (!line.hasOption('f')) { - System.err.println("Required parameter -f missing!"); - printHelp(options, 1); - } - String filename = line.getOptionValue('f'); - - // Create a context - Context c = new Context(Context.Mode.READ_ONLY); - c.turnOffAuthorisationSystem(); - - // The things we'll export - Iterator toExport = null; - MetadataExport exporter = null; - - // Export everything? - boolean exportAll = line.hasOption('a'); - - ContentServiceFactory contentServiceFactory = ContentServiceFactory.getInstance(); - // Check we have an item OK - ItemService itemService = contentServiceFactory.getItemService(); - if (!line.hasOption('i')) { - System.out.println("Exporting whole repository WARNING: May take some time!"); - exporter = new MetadataExport(c, itemService.findAll(c), exportAll); - } else { - String handle = line.getOptionValue('i'); - DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(c, handle); - if (dso == null) { - System.err.println("Item '" + handle + "' does not resolve to an item in your repository!"); - printHelp(options, 1); - } - - if (dso.getType() == Constants.ITEM) { - System.out.println("Exporting item '" + dso.getName() + "' (" + handle + ")"); - List item = new ArrayList<>(); - item.add((Item) dso); - exporter = new MetadataExport(c, item.iterator(), exportAll); - } else if (dso.getType() == Constants.COLLECTION) { - System.out.println("Exporting collection '" + dso.getName() + "' (" + handle + ")"); - Collection collection = (Collection) dso; - toExport = itemService.findByCollection(c, collection); - exporter = new MetadataExport(c, toExport, exportAll); - } else if (dso.getType() == Constants.COMMUNITY) { - System.out.println("Exporting community '" + dso.getName() + "' (" + handle + ")"); - exporter = new MetadataExport(c, (Community) dso, exportAll); - } else { - System.err.println("Error identifying '" + handle + "'"); - System.exit(1); - } - } - - // Perform the export - DSpaceCSV csv = exporter.export(); - - // Save the files to the file - csv.save(filename); - - // Finish off and tidy up + DSpaceCSV dSpaceCSV = metadataExportService.handleExport(c, exportAllItems, exportAllMetadata, handle); + handler.writeFilestream(c, filename, dSpaceCSV.getInputStream(), "exportCSV"); c.restoreAuthSystemState(); c.complete(); } + + public void setup() throws ParseException { + c = new Context(); + c.turnOffAuthorisationSystem(); + + if (commandLine.hasOption('h')) { + help = true; + } + + // Check a filename is given + if (!commandLine.hasOption('f')) { + throw new ParseException("Required parameter -f missing!"); + } + filename = commandLine.getOptionValue('f'); + + exportAllMetadata = commandLine.hasOption('a'); + + if (commandLine.hasOption('i')) { + exportAllItems = true; + } + handle = commandLine.getOptionValue('i'); + } } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index e1b1224809..02db493be2 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -7,36 +7,32 @@ */ package org.dspace.app.bulkedit; -import java.io.BufferedReader; -import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; +import javax.annotation.Nullable; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.commons.cli.PosixParser; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authority.AuthorityValue; -import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Entity; -import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.Relationship; @@ -49,6 +45,8 @@ import org.dspace.content.service.EntityService; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; @@ -58,64 +56,115 @@ import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; -import org.dspace.util.UUIDUtils; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; -/** - * Metadata importer to allow the batch import of metadata from a file - * - * @author Stuart Lewis - */ -public class MetadataImport { - /** - * The Context - */ - Context c; +public class MetadataImport extends DSpaceRunnable implements InitializingBean { - /** - * The DSpaceCSV object we're processing - */ - DSpaceCSV csv; + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataImport.class); - /** - * The lines to import - */ - List toImport; /** * The authority controlled fields */ - protected static Set authorityControlled; + protected Set authorityControlled; - static { - setAuthorizedMetadataFields(); - } + @Autowired + protected ItemService itemService; + @Autowired + protected InstallItemService installItemService; + @Autowired + protected CollectionService collectionService; + @Autowired + protected HandleService handleService; + @Autowired + protected WorkspaceItemService workspaceItemService; + @Autowired + protected RelationshipTypeService relationshipTypeService; + @Autowired + protected RelationshipService relationshipService; + @Autowired + protected EntityTypeService entityTypeService; + @Autowired + protected EntityService entityService; + @Autowired + protected AuthorityValueService authorityValueService; /** * The prefix of the authority controlled field */ - protected static final String AC_PREFIX = "authority.controlled."; + protected final String AC_PREFIX = "authority.controlled."; + + + private boolean useTemplate = false; + private String filename = null; + private boolean useWorkflow = false; + private boolean workflowNotify = false; + private boolean change = false; /** - * Logger + * The Context */ - protected static final Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataImport.class); + private Context c; - protected final AuthorityValueService authorityValueService; + /** + * The DSpaceCSV object we're processing + */ + private DSpaceCSV csv; - protected final ItemService itemService; - protected final InstallItemService installItemService; - protected final CollectionService collectionService; - protected final HandleService handleService; - protected final WorkspaceItemService workspaceItemService; - protected final RelationshipTypeService relationshipTypeService; - protected final RelationshipService relationshipService; - protected final EntityTypeService entityTypeService; - protected final EntityService entityService; + /** + * The lines to import + */ + private List toImport; + + private boolean help = false; + + /** + * Map of field:value to csv row number, used to resolve indirect entity target references. + * + * @see #populateRefAndRowMap(DSpaceCSVLine, UUID) + */ + protected Map> csvRefMap = new HashMap<>(); + + /** + * Map of csv row number to UUID, used to resolve indirect entity target references. + * + * @see #populateRefAndRowMap(DSpaceCSVLine, UUID) + */ + protected HashMap csvRowMap = new HashMap<>(); + + /** + * Map of UUIDs to their entity types. + * + * @see #populateRefAndRowMap(DSpaceCSVLine, UUID) + */ + protected static HashMap entityTypeMap = new HashMap<>(); + + /** + * Map of UUIDs to their relations that are referenced within any import with their referers. + * + * @see #populateEntityRelationMap(String, String, String) + */ + protected static HashMap>> entityRelationMap = new HashMap<>(); + + + /** + * Collection of errors generated during relation validation process. + */ + protected ArrayList relationValidationErrors = new ArrayList<>(); + + /** + * Counter of rows proccssed in a CSV. + */ + protected Integer rowCount = 1; + + protected boolean validateOnly; /** * Create an instance of the metadata importer. Requires a context and an array of CSV lines @@ -124,21 +173,368 @@ public class MetadataImport { * @param c The context * @param toImport An array of CSV lines to examine */ - public MetadataImport(Context c, DSpaceCSV toImport) { + public void initMetadataImport(Context c, DSpaceCSV toImport) { // Store the import settings this.c = c; csv = toImport; this.toImport = toImport.getCSVLines(); - installItemService = ContentServiceFactory.getInstance().getInstallItemService(); - itemService = ContentServiceFactory.getInstance().getItemService(); - collectionService = ContentServiceFactory.getInstance().getCollectionService(); - handleService = HandleServiceFactory.getInstance().getHandleService(); - authorityValueService = AuthorityServiceFactory.getInstance().getAuthorityValueService(); - workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); - relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); - relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); - entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); - entityService = ContentServiceFactory.getInstance().getEntityService(); + } + + @Override + public void internalRun() throws Exception { + if (help) { + printHelp(); + return; + } + // Read commandLines from the CSV file + try { + + csv = new DSpaceCSV(handler.getFileStream(c, filename), c); + } catch (MetadataImportInvalidHeadingException miihe) { + throw miihe; + } catch (Exception e) { + throw new Exception("Error reading file: " + e.getMessage(), e); + } + + // Perform the first import - just highlight differences + initMetadataImport(c, csv); + List changes; + + if (!commandLine.hasOption('s') || validateOnly) { + // See what has changed + try { + changes = runImport(false, useWorkflow, workflowNotify, useTemplate); + } catch (MetadataImportException mie) { + throw mie; + } + + // Display the changes + int changeCounter = displayChanges(changes, false); + + // If there were changes, ask if we should execute them + if (!validateOnly && changeCounter > 0) { + try { + // Ask the user if they want to make the changes + handler.logInfo("\n" + changeCounter + " item(s) will be changed\n"); + change = handler.getUserValidation(); + + } catch (IOException ioe) { + throw new IOException("Error: " + ioe.getMessage() + ", No changes have been made", ioe); + } + } else { + handler.logInfo("There were no changes detected"); + } + } else { + change = true; + } + + try { + // If required, make the change + if (change && !validateOnly) { + try { + // Make the changes + changes = runImport(true, useWorkflow, workflowNotify, useTemplate); + } catch (MetadataImportException mie) { + throw mie; + } + + // Display the changes + displayChanges(changes, true); + } + + // Finsh off and tidy up + c.restoreAuthSystemState(); + c.complete(); + } catch (Exception e) { + c.abort(); + throw new Exception( + "Error committing changes to database: " + e.getMessage() + ", aborting most recent changes", e); + } + + } + + public void setup() throws ParseException { + useTemplate = false; + filename = null; + useWorkflow = false; + workflowNotify = false; + + if (commandLine.hasOption('h')) { + help = true; + } + + // Check a filename is given + if (!commandLine.hasOption('f')) { + throw new ParseException("Required parameter -f missing!"); + } + filename = commandLine.getOptionValue('f'); + + // Option to apply template to new items + if (commandLine.hasOption('t')) { + useTemplate = true; + } + + // Options for workflows, and workflow notifications for new items + if (commandLine.hasOption('w')) { + useWorkflow = true; + if (commandLine.hasOption('n')) { + workflowNotify = true; + } + } else if (commandLine.hasOption('n')) { + throw new ParseException( + "Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option."); + } + validateOnly = commandLine.hasOption('v'); + + + // Create a context + try { + c = new Context(); + c.turnOffAuthorisationSystem(); + } catch (Exception e) { + throw new ParseException("Unable to create a new DSpace Context: " + e.getMessage()); + } + + // Find the EPerson, assign to context + try { + if (commandLine.hasOption('e')) { + EPerson eperson; + String e = commandLine.getOptionValue('e'); + if (e.indexOf('@') != -1) { + eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(c, e); + } else { + eperson = EPersonServiceFactory.getInstance().getEPersonService().find(c, UUID.fromString(e)); + } + + if (eperson == null) { + throw new ParseException("Error, eperson cannot be found: " + e); + } + c.setCurrentUser(eperson); + } + } catch (Exception e) { + throw new ParseException("Unable to find DSpace user: " + e.getMessage()); + } + + // Is this a silent run? + change = false; + } + + private MetadataImport() { + Options options = constructOptions(); + this.options = options; + } + + private Options constructOptions() { + Options options = new Options(); + + options.addOption("f", "file", true, "source file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(true); + options.addOption("e", "email", true, "email address or user id of user (required if adding new items)"); + options.getOption("e").setType(String.class); + options.getOption("e").setRequired(true); + options.addOption("s", "silent", false, + "silent operation - doesn't request confirmation of changes USE WITH CAUTION"); + options.getOption("s").setType(boolean.class); + options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow"); + options.getOption("w").setType(boolean.class); + options.addOption("n", "notify", false, + "notify - when adding new items using a workflow, send notification emails"); + options.getOption("n").setType(boolean.class); + options.addOption("v", "validate-only", false, + "validate - just validate the csv, don't run the import"); + options.getOption("v").setType(boolean.class); + options.addOption("t", "template", false, + "template - when adding new items, use the collection template (if it exists)"); + options.getOption("t").setType(boolean.class); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + + return options; + } + + /** + * Display the changes that have been detected, or that have been made + * + * @param changes The changes detected + * @param changed Whether or not the changes have been made + * @return The number of items that have changed + */ + private int displayChanges(List changes, boolean changed) { + // Display the changes + int changeCounter = 0; + for (BulkEditChange change : changes) { + // Get the changes + List adds = change.getAdds(); + List removes = change.getRemoves(); + List newCollections = change.getNewMappedCollections(); + List oldCollections = change.getOldMappedCollections(); + if ((adds.size() > 0) || (removes.size() > 0) || + (newCollections.size() > 0) || (oldCollections.size() > 0) || + (change.getNewOwningCollection() != null) || (change.getOldOwningCollection() != null) || + (change.isDeleted()) || (change.isWithdrawn()) || (change.isReinstated())) { + // Show the item + Item i = change.getItem(); + + handler.logInfo("-----------------------------------------------------------"); + if (!change.isNewItem()) { + handler.logInfo("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); + } else { + handler.logInfo("New item: "); + if (i != null) { + if (i.getHandle() != null) { + handler.logInfo(i.getID() + " (" + i.getHandle() + ")"); + } else { + handler.logInfo(i.getID() + " (in workflow)"); + } + } + } + changeCounter++; + } + + // Show actions + if (change.isDeleted()) { + if (changed) { + handler.logInfo(" - EXPUNGED!"); + } else { + handler.logInfo(" - EXPUNGE!"); + } + } + if (change.isWithdrawn()) { + if (changed) { + handler.logInfo(" - WITHDRAWN!"); + } else { + handler.logInfo(" - WITHDRAW!"); + } + } + if (change.isReinstated()) { + if (changed) { + handler.logInfo(" - REINSTATED!"); + } else { + handler.logInfo(" - REINSTATE!"); + } + } + + if (change.getNewOwningCollection() != null) { + Collection c = change.getNewOwningCollection(); + if (c != null) { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) { + handler.logInfo(" + New owning collection (" + cHandle + "): "); + } else { + handler.logInfo(" + New owning collection (" + cHandle + "): "); + } + handler.logInfo(cName); + } + + c = change.getOldOwningCollection(); + if (c != null) { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) { + handler.logInfo(" + Old owning collection (" + cHandle + "): "); + } else { + handler.logInfo(" + Old owning collection (" + cHandle + "): "); + } + handler.logInfo(cName); + } + } + + // Show new mapped collections + for (Collection c : newCollections) { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) { + handler.logInfo(" + Map to collection (" + cHandle + "): "); + } else { + handler.logInfo(" + Mapped to collection (" + cHandle + "): "); + } + handler.logInfo(cName); + } + + // Show old mapped collections + for (Collection c : oldCollections) { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) { + handler.logInfo(" + Un-map from collection (" + cHandle + "): "); + } else { + handler.logInfo(" + Un-mapped from collection (" + cHandle + "): "); + } + handler.logInfo(cName); + } + + // Show additions + for (BulkEditMetadataValue metadataValue : adds) { + String md = metadataValue.getSchema() + "." + metadataValue.getElement(); + if (metadataValue.getQualifier() != null) { + md += "." + metadataValue.getQualifier(); + } + if (metadataValue.getLanguage() != null) { + md += "[" + metadataValue.getLanguage() + "]"; + } + if (!changed) { + handler.logInfo(" + Add (" + md + "): "); + } else { + handler.logInfo(" + Added (" + md + "): "); + } + handler.logInfo(metadataValue.getValue()); + if (isAuthorityControlledField(md)) { + handler.logInfo(", authority = " + metadataValue.getAuthority()); + handler.logInfo(", confidence = " + metadataValue.getConfidence()); + } + handler.logInfo(""); + } + + // Show removals + for (BulkEditMetadataValue metadataValue : removes) { + String md = metadataValue.getSchema() + "." + metadataValue.getElement(); + if (metadataValue.getQualifier() != null) { + md += "." + metadataValue.getQualifier(); + } + if (metadataValue.getLanguage() != null) { + md += "[" + metadataValue.getLanguage() + "]"; + } + if (!changed) { + handler.logInfo(" - Remove (" + md + "): "); + } else { + handler.logInfo(" - Removed (" + md + "): "); + } + handler.logInfo(metadataValue.getValue()); + if (isAuthorityControlledField(md)) { + handler.logInfo(", authority = " + metadataValue.getAuthority()); + handler.logInfo(", confidence = " + metadataValue.getConfidence()); + } + handler.logInfo(""); + } + } + return changeCounter; + } + + /** + * is the field is defined as authority controlled + */ + private boolean isAuthorityControlledField(String md) { + String mdf = StringUtils.substringAfter(md, ":"); + mdf = StringUtils.substringBefore(mdf, "["); + return authorityControlled.contains(mdf); + } + + + /** + * Set authority controlled fields + */ + private void setAuthorizedMetadataFields() { + authorityControlled = new HashSet(); + Enumeration propertyNames = ConfigurationManager.getProperties().propertyNames(); + while (propertyNames.hasMoreElements()) { + String key = ((String) propertyNames.nextElement()).trim(); + if (key.startsWith(AC_PREFIX) + && ConfigurationManager.getBooleanProperty(key, false)) { + authorityControlled.add(key.substring(AC_PREFIX.length())); + } + } } /** @@ -155,7 +551,8 @@ public class MetadataImport { public List runImport(boolean change, boolean useWorkflow, boolean workflowNotify, - boolean useTemplate) throws MetadataImportException { + boolean useTemplate) + throws MetadataImportException, SQLException, AuthorizeException, WorkflowException, IOException { // Store the changes ArrayList changes = new ArrayList(); @@ -165,7 +562,11 @@ public class MetadataImport { c.setMode(Context.Mode.BATCH_EDIT); // Process each change + rowCount = 1; for (DSpaceCSVLine line : toImport) { + // Resolve target references to other items + populateRefAndRowMap(line, line.getID()); + line = resolveEntityRefs(line); // Get the DSpace item to compare with UUID id = line.getID(); @@ -178,7 +579,7 @@ public class MetadataImport { WorkflowItem wfItem = null; Item item = null; - // Is this a new item? + // Is this an existing item? if (id != null) { // Get the item item = itemService.find(c, id); @@ -215,9 +616,8 @@ public class MetadataImport { } } } - // Compare - compare(item, fromCSV, change, md, whatHasChanged, line); + compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line); } } @@ -278,7 +678,7 @@ public class MetadataImport { BulkEditChange whatHasChanged = new BulkEditChange(); for (String md : line.keys()) { // Get the values we already have - if (!"id".equals(md)) { + if (!"id".equals(md) && !"rowName".equals(md)) { // Get the values from the CSV String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); @@ -355,30 +755,23 @@ public class MetadataImport { item = wsItem.getItem(); // Add the metadata to the item - List relationships = new LinkedList<>(); + for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { + itemService.addMetadata(c, item, dcv.getSchema(), + dcv.getElement(), + dcv.getQualifier(), + dcv.getLanguage(), + dcv.getValue(), + dcv.getAuthority(), + dcv.getConfidence()); + } + //Add relations after all metadata has been processed for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { - - if (!StringUtils.equals(dcv.getElement(), "type")) { - relationships.add(dcv); - } else { - handleRelationshipMetadataValueFromBulkEditMetadataValue(item, dcv); - } - - } else { - itemService.addMetadata(c, item, dcv.getSchema(), - dcv.getElement(), - dcv.getQualifier(), - dcv.getLanguage(), - dcv.getValue(), - dcv.getAuthority(), - dcv.getConfidence()); + addRelationship(c, item, dcv.getElement(), dcv.getValue()); } } - for (BulkEditMetadataValue relationship : relationships) { - handleRelationshipMetadataValueFromBulkEditMetadataValue(item, relationship); - } + // Should the workflow be used? if (useWorkflow) { WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); @@ -401,8 +794,6 @@ public class MetadataImport { } } - // Commit changes to the object -// c.commit(); whatHasChanged.setItem(item); } @@ -416,40 +807,23 @@ public class MetadataImport { c.uncacheEntity(wfItem); c.uncacheEntity(item); } + populateRefAndRowMap(line, item == null ? null : item.getID()); + // keep track of current rows processed + rowCount++; } c.setMode(originalMode); } catch (MetadataImportException mie) { throw mie; - } catch (Exception e) { - e.printStackTrace(); } // Return the changes + if (!change) { + validateExpressedRelations(); + } return changes; } - - /** - * This metod handles the BulkEditMetadataValue objects that correspond to Relationship metadatavalues - * @param item The item to which this metadatavalue will belong - * @param dcv The BulkEditMetadataValue to be processed - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - private void handleRelationshipMetadataValueFromBulkEditMetadataValue(Item item, BulkEditMetadataValue dcv) - throws SQLException, AuthorizeException { - LinkedList values = new LinkedList<>(); - values.add(dcv.getValue()); - LinkedList authorities = new LinkedList<>(); - authorities.add(dcv.getAuthority()); - LinkedList confidences = new LinkedList<>(); - confidences.add(dcv.getConfidence()); - handleRelationMetadata(c, item, dcv.getSchema(), dcv.getElement(), - dcv.getQualifier(), - dcv.getLanguage(), values, authorities, confidences); - } - /** * Compare an item metadata with a line from CSV, and optionally update the item * @@ -461,10 +835,11 @@ public class MetadataImport { * @param line line in CSV file * @throws SQLException if there is a problem accessing a Collection from the database, from its handle * @throws AuthorizeException if there is an authorization problem with permissions + * @throws MetadataImportException custom exception for error handling within metadataimport */ - protected void compare(Item item, String[] fromCSV, boolean change, - String md, BulkEditChange changes, DSpaceCSVLine line) - throws SQLException, AuthorizeException { + protected void compareAndUpdate(Item item, String[] fromCSV, boolean change, + String md, BulkEditChange changes, DSpaceCSVLine line) + throws SQLException, AuthorizeException, MetadataImportException { // Log what metadata element we're looking at String all = ""; for (String part : fromCSV) { @@ -474,8 +849,8 @@ public class MetadataImport { log.debug(LogManager.getHeader(c, "metadata_import", "item_id=" + item.getID() + ",fromCSV=" + all)); - // Don't compare collections or actions - if (("collection".equals(md)) || ("action".equals(md))) { + // Don't compare collections or actions or rowNames + if (("collection".equals(md)) || ("action".equals(md)) || ("rowName".equals(md))) { return; } @@ -637,10 +1012,9 @@ public class MetadataImport { } } - if (StringUtils.equals(schema, MetadataSchemaEnum.RELATION.getName())) { List relationshipTypeList = relationshipTypeService - .findByLeftOrRightLabel(c, element); + .findByLeftwardOrRightwardTypeName(c, element); for (RelationshipType relationshipType : relationshipTypeList) { for (Relationship relationship : relationshipService .findByItemAndRelationshipType(c, item, relationshipType)) { @@ -648,7 +1022,7 @@ public class MetadataImport { relationshipService.update(c, relationship); } } - handleRelationMetadata(c, item, schema, element, qualifier, language, values, authorities, confidences); + addRelationships(c, item, element, values); } else { itemService.clearMetadata(c, item, schema, element, qualifier, language); itemService.addMetadata(c, item, schema, element, qualifier, @@ -659,233 +1033,217 @@ public class MetadataImport { } /** - * This method decides whether the metadatavalue is of type relation.type or if it corresponds to - * a relationship and handles it accordingly to their respective methods + * Add an item metadata with a line from CSV, and optionally update the item + * + * @param fromCSV The metadata from the CSV file + * @param md The element to compare + * @param changes The changes object to populate + * @throws SQLException when an SQL error has occurred (querying DSpace) + * @throws AuthorizeException If the user can't make the changes + */ + protected void add(String[] fromCSV, String md, BulkEditChange changes) + throws SQLException, AuthorizeException { + // Don't add owning collection or action + if (("collection".equals(md)) || ("action".equals(md))) { + return; + } + + // Make a String array of the values + // First, strip of language if it is there + String language = null; + if (md.contains("[")) { + String[] bits = md.split("\\["); + language = bits[1].substring(0, bits[1].length() - 1); + } + AuthorityValue fromAuthority = authorityValueService.getAuthorityValueType(md); + if (md.indexOf(':') > 0) { + md = md.substring(md.indexOf(':') + 1); + } + + String[] bits = md.split("\\."); + String schema = bits[0]; + String element = bits[1]; + // If there is a language on the element, strip if off + if (element.contains("[")) { + element = element.substring(0, element.indexOf('[')); + } + String qualifier = null; + if (bits.length > 2) { + qualifier = bits[2]; + + // If there is a language, strip if off + if (qualifier.contains("[")) { + qualifier = qualifier.substring(0, qualifier.indexOf('[')); + } + } + + // Add all the values + for (String value : fromCSV) { + BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value, + fromAuthority); + if (fromAuthority != null) { + value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv + .getAuthoritySeparator() + dcv.getConfidence(); + } + + // Add it + if ((value != null) && (!"".equals(value))) { + changes.registerAdd(dcv); + } + } + } + + protected BulkEditMetadataValue getBulkEditValueFromCSV(String language, String schema, String element, + String qualifier, String value, + AuthorityValue fromAuthority) { + // Look to see if it should be removed + BulkEditMetadataValue dcv = new BulkEditMetadataValue(); + dcv.setSchema(schema); + dcv.setElement(element); + dcv.setQualifier(qualifier); + dcv.setLanguage(language); + if (fromAuthority != null) { + if (value.indexOf(':') > 0) { + value = value.substring(0, value.indexOf(':')); + } + + // look up the value and authority in solr + List byValue = authorityValueService.findByValue(c, schema, element, qualifier, value); + AuthorityValue authorityValue = null; + if (byValue.isEmpty()) { + String toGenerate = fromAuthority.generateString() + value; + String field = schema + "_" + element + (StringUtils.isNotBlank(qualifier) ? "_" + qualifier : ""); + authorityValue = authorityValueService.generate(c, toGenerate, value, field); + dcv.setAuthority(toGenerate); + } else { + authorityValue = byValue.get(0); + dcv.setAuthority(authorityValue.getId()); + } + + dcv.setValue(authorityValue.getValue()); + dcv.setConfidence(Choices.CF_ACCEPTED); + } else if (value == null || !value.contains(csv.getAuthoritySeparator())) { + simplyCopyValue(value, dcv); + } else { + String[] parts = value.split(csv.getEscapedAuthoritySeparator()); + dcv.setValue(parts[0]); + dcv.setAuthority(parts[1]); + dcv.setConfidence((parts.length > 2 ? Integer.valueOf(parts[2]) : Choices.CF_ACCEPTED)); + } + return dcv; + } + + protected void simplyCopyValue(String value, BulkEditMetadataValue dcv) { + dcv.setValue(value); + dcv.setAuthority(null); + dcv.setConfidence(Choices.CF_UNSET); + } + + /** + * + * Adds multiple relationships with a matching typeName to an item. + * * @param c The relevant DSpace context * @param item The item to which this metadatavalue belongs to - * @param schema The schema for the metadatavalue - * @param element The element for the metadatavalue - * @param qualifier The qualifier for the metadatavalue - * @param language The language for the metadatavalue - * @param values The values for the metadatavalue - * @param authorities The authorities for the metadatavalue - * @param confidences The confidences for the metadatavalue + * @param typeName The element for the metadatavalue + * @param values to iterate over * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - private void handleRelationMetadata(Context c, Item item, String schema, String element, String qualifier, - String language, List values, List authorities, - List confidences) throws SQLException, AuthorizeException { - - if (StringUtils.equals(element, "type") && StringUtils.isBlank(qualifier)) { - handleRelationTypeMetadata(c, item, schema, element, qualifier, language, values, authorities, confidences); - - } else { - for (String value : values) { - handleRelationOtherMetadata(c, item, element, value); - } + private void addRelationships(Context c, Item item, String typeName, List values) + throws SQLException, AuthorizeException, + MetadataImportException { + for (String value : values) { + addRelationship(c, item, typeName, value); } - } /** - * This method takes the item, element and values to determine what relationships should be built - * for these parameters and calls on the method to construct them + * Gets an existing entity from a target reference. + * + * @param context the context to use. + * @param targetReference the target reference which may be a UUID, metadata reference, or rowName reference. + * @return the entity, which is guaranteed to exist. + * @throws MetadataImportException if the target reference is badly formed or refers to a non-existing item. + */ + private Entity getEntity(Context context, String targetReference) throws MetadataImportException { + Entity entity = null; + UUID uuid = resolveEntityRef(context, targetReference); + // At this point, we have a uuid, so we can get an entity + try { + entity = entityService.findByItemId(context, uuid); + if (entity.getItem() == null) { + throw new IllegalArgumentException("No item found in repository with uuid: " + uuid); + } + return entity; + } catch (SQLException sqle) { + throw new MetadataImportException("Unable to find entity using reference: " + targetReference, sqle); + } + } + + /** + * + * Creates a relationship for the given item + * * @param c The relevant DSpace context * @param item The item that the relationships will be made for - * @param element The string determining which relationshiptype is to be used + * @param typeName The relationship typeName * @param value The value for the relationship * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - private void handleRelationOtherMetadata(Context c, Item item, String element, String value) - throws SQLException, AuthorizeException { - Entity entity = entityService.findByItemId(c, item.getID()); + private void addRelationship(Context c, Item item, String typeName, String value) + throws SQLException, AuthorizeException, MetadataImportException { + if (value.isEmpty()) { + return; + } boolean left = false; - List acceptableRelationshipTypes = new LinkedList<>(); - String url = handleService.resolveToURL(c, value); - UUID uuid = UUIDUtils.fromString(value); - if (uuid == null && StringUtils.isNotBlank(url)) { - return; + + // Get entity from target reference + Entity relationEntity = getEntity(c, value); + // Get relationship type of entity and item + String relationEntityRelationshipType = itemService.getMetadata(relationEntity.getItem(), + "relationship", "type", + null, Item.ANY).get(0).getValue(); + String itemRelationshipType = itemService.getMetadata(item, "relationship", "type", + null, Item.ANY).get(0).getValue(); + + // Get the correct RelationshipType based on typeName + List relType = relationshipTypeService.findByLeftwardOrRightwardTypeName(c, typeName); + RelationshipType foundRelationshipType = matchRelationshipType(relType, relationEntityRelationshipType, + itemRelationshipType, typeName); + + if (foundRelationshipType == null) { + throw new MetadataImportException("Error on CSV row " + rowCount + ":" + "\n" + + "No Relationship type found for:\n" + + "Target type: " + relationEntityRelationshipType + "\n" + + "Origin referer type: " + itemRelationshipType + "\n" + + "with typeName: " + typeName); } - Entity relationEntity = entityService.findByItemId(c, uuid); - - - List leftRelationshipTypesForEntity = entityService.getLeftRelationshipTypes(c, entity); - List rightRelationshipTypesForEntity = entityService.getRightRelationshipTypes(c, entity); - - for (RelationshipType relationshipType : entityService.getAllRelationshipTypes(c, entity)) { - if (StringUtils.equalsIgnoreCase(relationshipType.getLeftLabel(), element)) { - left = handleLeftLabelEqualityRelationshipTypeElement(c, entity, relationEntity, left, - acceptableRelationshipTypes, - leftRelationshipTypesForEntity, - relationshipType); - } else if (StringUtils.equalsIgnoreCase(relationshipType.getRightLabel(), element)) { - left = handleRightLabelEqualityRelationshipTypeElement(c, entity, relationEntity, left, - acceptableRelationshipTypes, - rightRelationshipTypesForEntity, - relationshipType); - } + if (foundRelationshipType.getLeftwardType().equalsIgnoreCase(typeName)) { + left = true; } - if (acceptableRelationshipTypes.size() > 1) { - log.error("Ambiguous relationship_types were found"); - return; - } - if (acceptableRelationshipTypes.size() == 0) { - log.error("no relationship_types were found"); - return; - } - - //There is exactly one - buildRelationObject(c, item, value, left, acceptableRelationshipTypes.get(0)); - } - - /** - * This method creates the relationship for the item and stores it in the database - * @param c The relevant DSpace context - * @param item The item for which this relationship will be constructed - * @param value The value for the relationship - * @param left A boolean indicating whether the item is the leftItem or the rightItem - * @param acceptedRelationshipType The acceptable relationship type - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - private void buildRelationObject(Context c, Item item, String value, boolean left, - RelationshipType acceptedRelationshipType) - throws SQLException, AuthorizeException { + // Placeholder items for relation placing Item leftItem = null; Item rightItem = null; if (left) { leftItem = item; - rightItem = itemService.findByIdOrLegacyId(c, value); + rightItem = relationEntity.getItem(); } else { + leftItem = relationEntity.getItem(); rightItem = item; - leftItem = itemService.findByIdOrLegacyId(c, value); } - RelationshipType relationshipType = acceptedRelationshipType; + + // Create the relationship int leftPlace = relationshipService.findLeftPlaceByLeftItem(c, leftItem) + 1; int rightPlace = relationshipService.findRightPlaceByRightItem(c, rightItem) + 1; Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, - relationshipType, leftPlace, rightPlace); + foundRelationshipType, leftPlace, rightPlace); relationshipService.update(c, persistedRelationship); } - /** - * This method will add RelationshipType objects to the acceptableRelationshipTypes list - * if applicable and valid RelationshipType objects are found. It will also return a boolean indicating - * whether we're dealing with a left Relationship or not - * @param c The relevant DSpace context - * @param entity The Entity for which the RelationshipType has to be checked - * @param relationEntity The other Entity of the Relationship - * @param left Boolean indicating whether the Relationship is left or not - * @param acceptableRelationshipTypes The list of RelationshipType objects that will be added to - * @param rightRelationshipTypesForEntity The list of RelationshipType objects that are possible - * for the right entity - * @param relationshipType The RelationshipType object that we want to check whether it's - * valid to be added or not - * @return A boolean indicating whether the relationship is left or right, will - * be false in this case - * @throws SQLException If something goes wrong - */ - private boolean handleRightLabelEqualityRelationshipTypeElement(Context c, Entity entity, Entity relationEntity, - boolean left, - List acceptableRelationshipTypes, - List - rightRelationshipTypesForEntity, - RelationshipType relationshipType) - throws SQLException { - if (StringUtils.equalsIgnoreCase(entityService.getType(c, entity).getLabel(), - relationshipType.getRightType().getLabel()) && - StringUtils.equalsIgnoreCase(entityService.getType(c, relationEntity).getLabel(), - relationshipType.getLeftType().getLabel())) { - - for (RelationshipType rightRelationshipType : rightRelationshipTypesForEntity) { - if (StringUtils.equalsIgnoreCase(rightRelationshipType.getLeftType().getLabel(), - relationshipType.getLeftType().getLabel()) || - StringUtils.equalsIgnoreCase(rightRelationshipType.getRightType().getLabel(), - relationshipType.getLeftType().getLabel())) { - left = false; - acceptableRelationshipTypes.add(relationshipType); - } - } - } - return left; - } - - /** - * This method will add RelationshipType objects to the acceptableRelationshipTypes list - * if applicable and valid RelationshipType objects are found. It will also return a boolean indicating - * whether we're dealing with a left Relationship or not - * @param c The relevant DSpace context - * @param entity The Entity for which the RelationshipType has to be checked - * @param relationEntity The other Entity of the Relationship - * @param left Boolean indicating whether the Relationship is left or not - * @param acceptableRelationshipTypes The list of RelationshipType objects that will be added to - * @param leftRelationshipTypesForEntity The list of RelationshipType objects that are possible - * for the left entity - * @param relationshipType The RelationshipType object that we want to check whether it's - * valid to be added or not - * @return A boolean indicating whether the relationship is left or right, will - * be true in this case - * @throws SQLException If something goes wrong - */ - private boolean handleLeftLabelEqualityRelationshipTypeElement(Context c, Entity entity, Entity relationEntity, - boolean left, - List acceptableRelationshipTypes, - List - leftRelationshipTypesForEntity, - RelationshipType relationshipType) - throws SQLException { - if (StringUtils.equalsIgnoreCase(entityService.getType(c, entity).getLabel(), - relationshipType.getLeftType().getLabel()) && - StringUtils.equalsIgnoreCase(entityService.getType(c, relationEntity).getLabel(), - relationshipType.getRightType().getLabel())) { - for (RelationshipType leftRelationshipType : leftRelationshipTypesForEntity) { - if (StringUtils.equalsIgnoreCase(leftRelationshipType.getRightType().getLabel(), - relationshipType.getRightType().getLabel()) || - StringUtils.equalsIgnoreCase(leftRelationshipType.getLeftType().getLabel(), - relationshipType.getRightType().getLabel())) { - left = true; - acceptableRelationshipTypes.add(relationshipType); - } - } - } - return left; - } - - /** - * This method will add the relationship.type metadata to the item if an EntityType can be found for the value in - * the values list. - * @param c The relevant DSpace context - * @param item The item to which this metadatavalue will be added - * @param schema The schema for the metadatavalue to be added - * @param element The element for the metadatavalue to be added - * @param qualifier The qualifier for the metadatavalue to be added - * @param language The language for the metadatavalue to be added - * @param values The value on which we'll search for EntityType object and it's the value - * for the metadatavalue that will be created - * @param authorities The authority for the metadatavalue. This will be filled with the ID - * of the found EntityType for the value if it exists - * @param confidences The confidence for the metadatavalue - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - private void handleRelationTypeMetadata(Context c, Item item, String schema, String element, String qualifier, - String language, List values, List authorities, - List confidences) - throws SQLException, AuthorizeException { - EntityType entityType = entityTypeService.findByEntityType(c, values.get(0)); - if (entityType != null) { - authorities.add(String.valueOf(entityType.getID())); - itemService.clearMetadata(c, item, schema, element, qualifier, language); - itemService.addMetadata(c, item, schema, element, qualifier, language, - values, authorities, confidences); - itemService.update(c, item); - } - } - /** * Compare changes between an items owning collection and mapped collections * and what is in the CSV file @@ -1016,111 +1374,421 @@ public class MetadataImport { } } + public void afterPropertiesSet() throws Exception { + setAuthorizedMetadataFields(); + } + /** - * Add an item metadata with a line from CSV, and optionally update the item + * Gets a copy of the given csv line with all entity target references resolved to UUID strings. + * Keys being iterated over represent metadatafields or special columns to be processed. * - * @param fromCSV The metadata from the CSV file - * @param md The element to compare - * @param changes The changes object to populate - * @throws SQLException when an SQL error has occurred (querying DSpace) - * @throws AuthorizeException If the user can't make the changes + * @param line the csv line to process. + * @return a copy, with all references resolved. + * @throws MetadataImportException if there is an error resolving any entity target reference. */ - protected void add(String[] fromCSV, String md, BulkEditChange changes) - throws SQLException, AuthorizeException { - // Don't add owning collection or action - if (("collection".equals(md)) || ("action".equals(md))) { - return; - } - - // Make a String array of the values - // First, strip of language if it is there - String language = null; - if (md.contains("[")) { - String[] bits = md.split("\\["); - language = bits[1].substring(0, bits[1].length() - 1); - } - AuthorityValue fromAuthority = authorityValueService.getAuthorityValueType(md); - if (md.indexOf(':') > 0) { - md = md.substring(md.indexOf(':') + 1); - } - - String[] bits = md.split("\\."); - String schema = bits[0]; - String element = bits[1]; - // If there is a language on the element, strip if off - if (element.contains("[")) { - element = element.substring(0, element.indexOf('[')); - } - String qualifier = null; - if (bits.length > 2) { - qualifier = bits[2]; - - // If there is a language, strip if off - if (qualifier.contains("[")) { - qualifier = qualifier.substring(0, qualifier.indexOf('[')); - } - } - - // Add all the values - for (String value : fromCSV) { - BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value, - fromAuthority); - if (fromAuthority != null) { - value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv - .getAuthoritySeparator() + dcv.getConfidence(); - } - - // Add it - if ((value != null) && (!"".equals(value))) { - changes.registerAdd(dcv); - } - } - } - - protected BulkEditMetadataValue getBulkEditValueFromCSV(String language, String schema, String element, - String qualifier, String value, - AuthorityValue fromAuthority) { - // Look to see if it should be removed - BulkEditMetadataValue dcv = new BulkEditMetadataValue(); - dcv.setSchema(schema); - dcv.setElement(element); - dcv.setQualifier(qualifier); - dcv.setLanguage(language); - if (fromAuthority != null) { - if (value.indexOf(':') > 0) { - value = value.substring(0, value.indexOf(':')); - } - - // look up the value and authority in solr - List byValue = authorityValueService.findByValue(c, schema, element, qualifier, value); - AuthorityValue authorityValue = null; - if (byValue.isEmpty()) { - String toGenerate = fromAuthority.generateString() + value; - String field = schema + "_" + element + (StringUtils.isNotBlank(qualifier) ? "_" + qualifier : ""); - authorityValue = authorityValueService.generate(c, toGenerate, value, field); - dcv.setAuthority(toGenerate); + public DSpaceCSVLine resolveEntityRefs(DSpaceCSVLine line) throws MetadataImportException { + DSpaceCSVLine newLine = new DSpaceCSVLine(line.getID()); + UUID originId = evaluateOriginId(line.getID()); + for (String key : line.keys()) { + // If a key represents a relation field attempt to resolve the target reference from the csvRefMap + if (key.split("\\.")[0].equalsIgnoreCase("relation")) { + if (line.get(key).size() > 0) { + for (String val : line.get(key)) { + // Attempt to resolve the relation target reference + // These can be a UUID, metadata target reference or rowName target reference + String uuid = resolveEntityRef(c, val).toString(); + newLine.add(key, uuid); + //Entity refs have been resolved / placeholdered + //Populate the EntityRelationMap + populateEntityRelationMap(uuid, key, originId.toString()); + } + } } else { - authorityValue = byValue.get(0); - dcv.setAuthority(authorityValue.getId()); + if (line.get(key).size() > 1) { + for (String value : line.get(key)) { + newLine.add(key, value); + } + } else { + if (line.get(key).size() > 0) { + newLine.add(key, line.get(key).get(0)); + } + } } - - dcv.setValue(authorityValue.getValue()); - dcv.setConfidence(Choices.CF_ACCEPTED); - } else if (value == null || !value.contains(csv.getAuthoritySeparator())) { - simplyCopyValue(value, dcv); - } else { - String[] parts = value.split(csv.getEscapedAuthoritySeparator()); - dcv.setValue(parts[0]); - dcv.setAuthority(parts[1]); - dcv.setConfidence((parts.length > 2 ? Integer.valueOf(parts[2]) : Choices.CF_ACCEPTED)); } - return dcv; + + return newLine; } - protected void simplyCopyValue(String value, BulkEditMetadataValue dcv) { - dcv.setValue(value); - dcv.setAuthority(null); - dcv.setConfidence(Choices.CF_UNSET); + /** + * Populate the entityRelationMap with all target references and it's asscoiated typeNames + * to their respective origins + * + * @param refUUID the target reference UUID for the relation + * @param relationField the field of the typeNames to relate from + */ + private void populateEntityRelationMap(String refUUID, String relationField, String originId) { + HashMap> typeNames = null; + if (entityRelationMap.get(refUUID) == null) { + typeNames = new HashMap<>(); + ArrayList originIds = new ArrayList<>(); + originIds.add(originId); + typeNames.put(relationField, originIds); + entityRelationMap.put(refUUID, typeNames); + } else { + typeNames = entityRelationMap.get(refUUID); + if (typeNames.get(relationField) == null) { + ArrayList originIds = new ArrayList<>(); + originIds.add(originId); + typeNames.put(relationField, originIds); + } else { + ArrayList originIds = typeNames.get(relationField); + originIds.add(originId); + typeNames.put(relationField, originIds); + } + entityRelationMap.put(refUUID, typeNames); + } + } + + /** + * Populates the csvRefMap, csvRowMap, and entityTypeMap for the given csv line. + * + * The csvRefMap is an index that keeps track of which rows have a specific value for + * a specific metadata field or the special "rowName" column. This is used to help resolve indirect + * entity target references in the same CSV. + * + * The csvRowMap is a row number to UUID map, and contains an entry for every row that has + * been processed so far which has a known (minted) UUID for its item. This is used to help complete + * the resolution after the row number has been determined. + * + * @param line the csv line. + * @param uuid the uuid of the item, which may be null if it has not been minted yet. + */ + private void populateRefAndRowMap(DSpaceCSVLine line, @Nullable UUID uuid) { + if (uuid != null) { + csvRowMap.put(rowCount, uuid); + } else { + csvRowMap.put(rowCount, new UUID(0, rowCount)); + } + for (String key : line.keys()) { + if (key.contains(".") && !key.split("\\.")[0].equalsIgnoreCase("relation") || + key.equalsIgnoreCase("rowName")) { + for (String value : line.get(key)) { + String valueKey = key + ":" + value; + Set rowNums = csvRefMap.get(valueKey); + if (rowNums == null) { + rowNums = new HashSet<>(); + csvRefMap.put(valueKey, rowNums); + } + rowNums.add(rowCount); + } + } + //Populate entityTypeMap + if (key.equalsIgnoreCase("relationship.type") && line.get(key).size() > 0) { + if (uuid == null) { + entityTypeMap.put(new UUID(0, rowCount), line.get(key).get(0)); + } else { + entityTypeMap.put(uuid, line.get(key).get(0)); + } + } + } + } + + /** + * Gets the UUID of the item indicated by the given target reference, + * which may be a direct UUID string, a row reference + * of the form rowName:VALUE, or a metadata value reference of the form schema.element[.qualifier]:VALUE. + * + * The reference may refer to a previously-processed item in the CSV or an item in the database. + * + * @param context the context to use. + * @param reference the target reference which may be a UUID, metadata reference, or rowName reference. + * @return the uuid. + * @throws MetadataImportException if the target reference is malformed or ambiguous (refers to multiple items). + */ + private UUID resolveEntityRef(Context context, String reference) throws MetadataImportException { + // value reference + UUID uuid = null; + if (!reference.contains(":")) { + // assume it's a UUID + try { + return UUID.fromString(reference); + } catch (IllegalArgumentException e) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Not a UUID or indirect entity reference: '" + reference + "'"); + } + } else if (!reference.startsWith("rowName:")) { // Not a rowName ref; so it's a metadata value reference + MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + MetadataFieldService metadataFieldService = + ContentServiceFactory.getInstance().getMetadataFieldService(); + int i = reference.indexOf(":"); + String mfValue = reference.substring(i + 1); + String mf[] = reference.substring(0, i).split("\\."); + if (mf.length < 2) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Bad metadata field in reference: '" + reference + + "' (expected syntax is schema.element[.qualifier])"); + } + String schema = mf[0]; + String element = mf[1]; + String qualifier = mf.length == 2 ? null : mf[2]; + try { + MetadataField mfo = metadataFieldService.findByElement(context, schema, element, qualifier); + Iterator mdv = metadataValueService.findByFieldAndValue(context, mfo, mfValue); + if (mdv.hasNext()) { + MetadataValue mdvVal = mdv.next(); + uuid = mdvVal.getDSpaceObject().getID(); + if (mdv.hasNext()) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in db: " + reference); + } + } + } catch (SQLException e) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Error looking up item by metadata reference: " + reference, e); + } + } + // Lookup UUIDs that may have already been processed into the csvRefMap + // See populateRefAndRowMap() for how the csvRefMap is populated + // See getMatchingCSVUUIDs() for how the reference param is sourced from the csvRefMap + Set csvUUIDs = getMatchingCSVUUIDs(reference); + if (csvUUIDs.size() > 1) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in csv: " + reference); + } else if (csvUUIDs.size() == 1) { + UUID csvUUID = csvUUIDs.iterator().next(); + if (csvUUID.equals(uuid)) { + return uuid; // one match from csv and db (same item) + } else if (uuid != null) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in db and csv: " + reference); + } else { + return csvUUID; // one match from csv + } + } else { // size == 0; the reference does not exist throw an error + if (uuid == null) { + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "No matches found for reference: " + reference + + "\nKeep in mind you can only reference entries that " + + "are listed before this one within the CSV."); + } else { + return uuid; // one match from db + } + } + } + + /** + * Gets the set of matching lines as UUIDs that have already been processed given a metadata value. + * + * @param mdValueRef the metadataValue reference to search for. + * @return the set of matching lines as UUIDs. + */ + private Set getMatchingCSVUUIDs(String mdValueRef) { + Set set = new HashSet<>(); + if (csvRefMap.containsKey(mdValueRef)) { + for (Integer rowNum : csvRefMap.get(mdValueRef)) { + set.add(getUUIDForRow(rowNum)); + } + } + return set; + } + + /** + * Gets the UUID of the item of a given row in the CSV, if it has been minted. + * If the UUID has not yet been minted, gets a UUID representation of the row + * (a UUID whose numeric value equals the row number). + * + * @param rowNum the row number. + * @return the UUID of the item + */ + private UUID getUUIDForRow(int rowNum) { + if (csvRowMap.containsKey(rowNum)) { + return csvRowMap.get(rowNum); + } else { + return new UUID(0, rowNum); + } + } + + /** + * Return a UUID of the origin in process or a placeholder for the origin to be evaluated later + * + * @param originId UUID of the origin + * @return the UUID of the item or UUID placeholder + */ + private UUID evaluateOriginId(@Nullable UUID originId) { + if (originId != null) { + return originId; + } else { + return new UUID(0, rowCount); + } + } + + /** + * Validate every relation modification expressed in the CSV. + * + */ + private void validateExpressedRelations() throws MetadataImportException { + for (String targetUUID : entityRelationMap.keySet()) { + String targetType = null; + try { + // Get the type of reference. Attempt lookup in processed map first before looking in archive. + if (entityTypeMap.get(UUID.fromString(targetUUID)) != null) { + targetType = entityTypeService.findByEntityType(c, entityTypeMap.get(UUID.fromString(targetUUID))) + .getLabel(); + } else { + // Target item may be archived; check there. + // Add to errors if Realtionship.type cannot be derived + Item targetItem = null; + if (itemService.find(c, UUID.fromString(targetUUID)) != null) { + targetItem = itemService.find(c, UUID.fromString(targetUUID)); + List relTypes = itemService.getMetadata(targetItem, "relationship", + "type", null, Item.ANY); + String relTypeValue = null; + if (relTypes.size() > 0) { + relTypeValue = relTypes.get(0).getValue(); + targetType = entityTypeService.findByEntityType(c, relTypeValue).getLabel(); + } else { + relationValidationErrors.add("Cannot resolve Entity type for target UUID: " + + targetUUID); + } + } else { + relationValidationErrors.add("Cannot resolve Entity type for target UUID: " + + targetUUID); + } + } + if (targetType == null) { + continue; + } + // Get typeNames for each origin referer of this target. + for (String typeName : entityRelationMap.get(targetUUID).keySet()) { + // Resolve Entity Type for each origin referer. + for (String originRefererUUID : entityRelationMap.get(targetUUID).get(typeName)) { + // Evaluate row number for origin referer. + String originRow = "N/A"; + if (csvRowMap.containsValue(UUID.fromString(originRefererUUID))) { + for (int key : csvRowMap.keySet()) { + if (csvRowMap.get(key).toString().equalsIgnoreCase(originRefererUUID)) { + originRow = key + ""; + break; + } + } + } + String originType = ""; + // Validate target type and origin type pairing with typeName or add to errors. + // Attempt lookup in processed map first before looking in archive. + if (entityTypeMap.get(UUID.fromString(originRefererUUID)) != null) { + originType = entityTypeMap.get(UUID.fromString(originRefererUUID)); + validateTypesByTypeByTypeName(targetType, originType, typeName, originRow); + } else { + // Origin item may be archived; check there. + // Add to errors if Realtionship.type cannot be derived. + Item originItem = null; + if (itemService.find(c, UUID.fromString(targetUUID)) != null) { + originItem = itemService.find(c, UUID.fromString(originRefererUUID)); + List relTypes = itemService.getMetadata(originItem, "relationship", + "type", null, Item.ANY); + String relTypeValue = null; + if (relTypes.size() > 0) { + relTypeValue = relTypes.get(0).getValue(); + originType = entityTypeService.findByEntityType(c, relTypeValue).getLabel(); + validateTypesByTypeByTypeName(targetType, originType, typeName, originRow); + } else { + relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + + "Cannot resolve Entity type for reference: " + + originRefererUUID); + } + + } else { + relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + + "Cannot resolve Entity type for reference: " + + originRefererUUID + " in row: " + originRow); + } + } + } + } + + } catch (SQLException sqle) { + throw new MetadataImportException("Error interacting with database!", sqle); + } + + } // If relationValidationErrors is empty all described relationships are valid. + if (!relationValidationErrors.isEmpty()) { + StringBuilder errors = new StringBuilder(); + for (String error : relationValidationErrors) { + errors.append(error + "\n"); + } + throw new MetadataImportException("Error validating relationships: \n" + errors); + } + } + + /** + * Generates a list of potenital Relationship Types given a typeName and attempts to match the given + * targetType and originType to a Relationship Type in the list. + * + * @param targetType entity type of target. + * @param originType entity type of origin referer. + * @param typeName left or right typeName of the respective Relationship. + * @return the UUID of the item. + */ + private void validateTypesByTypeByTypeName(String targetType, String originType, String typeName, String originRow) + throws MetadataImportException { + try { + RelationshipType foundRelationshipType = null; + List relationshipTypeList = relationshipTypeService + .findByLeftwardOrRightwardTypeName(c, typeName.split("\\.")[1]); + // Validate described relationship form the CSV. + foundRelationshipType = matchRelationshipType(relationshipTypeList, targetType, originType, typeName); + if (foundRelationshipType == null) { + relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + + "No Relationship type found for:\n" + + "Target type: " + targetType + "\n" + + "Origin referer type: " + originType + "\n" + + "with typeName: " + typeName + " for type: " + originType); + } + } catch (SQLException sqle) { + throw new MetadataImportException("Error interacting with database!", sqle); + } + } + + /** + * Matches two Entity types to a Relationship Type from a set of Relationship Types. + * + * @param relTypes set of Relationship Types. + * @param targetType entity type of target. + * @param originType entity type of origin referer. + * @return null or matched Relationship Type. + */ + private RelationshipType matchRelationshipType(List relTypes, + String targetType, String originType, String originTypeName) { + RelationshipType foundRelationshipType = null; + if (originTypeName.split("\\.").length > 1) { + originTypeName = originTypeName.split("\\.")[1]; + } + for (RelationshipType relationshipType : relTypes) { + // Is origin type leftward or righward + boolean isLeft = false; + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) { + isLeft = true; + } + if (isLeft) { + // Validate typeName reference + if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) && + relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { + foundRelationshipType = relationshipType; + } + } else { + if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) && + relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { + foundRelationshipType = relationshipType; + } + } + } + return foundRelationshipType; } /** @@ -1155,386 +1823,4 @@ public class MetadataImport { // Remove newlines as different operating systems sometimes use different formats return in.replaceAll("\r\n", "").replaceAll("\n", "").trim(); } - - /** - * Print the help message - * - * @param options The command line options the user gave - * @param exitCode the system exit code to use - */ - private static void printHelp(Options options, int exitCode) { - // print the help message - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MetatadataImport\n", options); - System.out.println("\nmetadataimport: MetadataImport -f filename"); - System.exit(exitCode); - } - - /** - * Display the changes that have been detected, or that have been made - * - * @param changes The changes detected - * @param changed Whether or not the changes have been made - * @return The number of items that have changed - */ - private static int displayChanges(List changes, boolean changed) { - // Display the changes - int changeCounter = 0; - for (BulkEditChange change : changes) { - // Get the changes - List adds = change.getAdds(); - List removes = change.getRemoves(); - List newCollections = change.getNewMappedCollections(); - List oldCollections = change.getOldMappedCollections(); - if ((adds.size() > 0) || (removes.size() > 0) || - (newCollections.size() > 0) || (oldCollections.size() > 0) || - (change.getNewOwningCollection() != null) || (change.getOldOwningCollection() != null) || - (change.isDeleted()) || (change.isWithdrawn()) || (change.isReinstated())) { - // Show the item - Item i = change.getItem(); - - System.out.println("-----------------------------------------------------------"); - if (!change.isNewItem()) { - System.out.println("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); - } else { - System.out.print("New item: "); - if (i != null) { - if (i.getHandle() != null) { - System.out.print(i.getID() + " (" + i.getHandle() + ")"); - } else { - System.out.print(i.getID() + " (in workflow)"); - } - } - System.out.println(); - } - changeCounter++; - } - - // Show actions - if (change.isDeleted()) { - if (changed) { - System.out.println(" - EXPUNGED!"); - } else { - System.out.println(" - EXPUNGE!"); - } - } - if (change.isWithdrawn()) { - if (changed) { - System.out.println(" - WITHDRAWN!"); - } else { - System.out.println(" - WITHDRAW!"); - } - } - if (change.isReinstated()) { - if (changed) { - System.out.println(" - REINSTATED!"); - } else { - System.out.println(" - REINSTATE!"); - } - } - - if (change.getNewOwningCollection() != null) { - Collection c = change.getNewOwningCollection(); - if (c != null) { - String cHandle = c.getHandle(); - String cName = c.getName(); - if (!changed) { - System.out.print(" + New owning collection (" + cHandle + "): "); - } else { - System.out.print(" + New owning collection (" + cHandle + "): "); - } - System.out.println(cName); - } - - c = change.getOldOwningCollection(); - if (c != null) { - String cHandle = c.getHandle(); - String cName = c.getName(); - if (!changed) { - System.out.print(" + Old owning collection (" + cHandle + "): "); - } else { - System.out.print(" + Old owning collection (" + cHandle + "): "); - } - System.out.println(cName); - } - } - - // Show new mapped collections - for (Collection c : newCollections) { - String cHandle = c.getHandle(); - String cName = c.getName(); - if (!changed) { - System.out.print(" + Map to collection (" + cHandle + "): "); - } else { - System.out.print(" + Mapped to collection (" + cHandle + "): "); - } - System.out.println(cName); - } - - // Show old mapped collections - for (Collection c : oldCollections) { - String cHandle = c.getHandle(); - String cName = c.getName(); - if (!changed) { - System.out.print(" + Un-map from collection (" + cHandle + "): "); - } else { - System.out.print(" + Un-mapped from collection (" + cHandle + "): "); - } - System.out.println(cName); - } - - // Show additions - for (BulkEditMetadataValue metadataValue : adds) { - String md = metadataValue.getSchema() + "." + metadataValue.getElement(); - if (metadataValue.getQualifier() != null) { - md += "." + metadataValue.getQualifier(); - } - if (metadataValue.getLanguage() != null) { - md += "[" + metadataValue.getLanguage() + "]"; - } - if (!changed) { - System.out.print(" + Add (" + md + "): "); - } else { - System.out.print(" + Added (" + md + "): "); - } - System.out.print(metadataValue.getValue()); - if (isAuthorityControlledField(md)) { - System.out.print(", authority = " + metadataValue.getAuthority()); - System.out.print(", confidence = " + metadataValue.getConfidence()); - } - System.out.println(""); - } - - // Show removals - for (BulkEditMetadataValue metadataValue : removes) { - String md = metadataValue.getSchema() + "." + metadataValue.getElement(); - if (metadataValue.getQualifier() != null) { - md += "." + metadataValue.getQualifier(); - } - if (metadataValue.getLanguage() != null) { - md += "[" + metadataValue.getLanguage() + "]"; - } - if (!changed) { - System.out.print(" - Remove (" + md + "): "); - } else { - System.out.print(" - Removed (" + md + "): "); - } - System.out.print(metadataValue.getValue()); - if (isAuthorityControlledField(md)) { - System.out.print(", authority = " + metadataValue.getAuthority()); - System.out.print(", confidence = " + metadataValue.getConfidence()); - } - System.out.println(""); - } - } - return changeCounter; - } - - /** - * is the field is defined as authority controlled - */ - private static boolean isAuthorityControlledField(String md) { - String mdf = StringUtils.substringAfter(md, ":"); - mdf = StringUtils.substringBefore(mdf, "["); - return authorityControlled.contains(mdf); - } - - /** - * Set authority controlled fields - */ - private static void setAuthorizedMetadataFields() { - authorityControlled = new HashSet(); - Enumeration propertyNames = ConfigurationManager.getProperties().propertyNames(); - while (propertyNames.hasMoreElements()) { - String key = ((String) propertyNames.nextElement()).trim(); - if (key.startsWith(AC_PREFIX) - && ConfigurationManager.getBooleanProperty(key, false)) { - authorityControlled.add(key.substring(AC_PREFIX.length())); - } - } - } - - /** - * main method to run the metadata exporter - * - * @param argv the command line arguments given - */ - public static void main(String[] argv) { - // Create an options object and populate it - CommandLineParser parser = new PosixParser(); - - Options options = new Options(); - - options.addOption("f", "file", true, "source file"); - options.addOption("e", "email", true, "email address or user id of user (required if adding new items)"); - options.addOption("s", "silent", false, - "silent operation - doesn't request confirmation of changes USE WITH CAUTION"); - options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow"); - options.addOption("n", "notify", false, - "notify - when adding new items using a workflow, send notification emails"); - options.addOption("t", "template", false, - "template - when adding new items, use the collection template (if it exists)"); - options.addOption("h", "help", false, "help"); - - // Parse the command line arguments - CommandLine line; - try { - line = parser.parse(options, argv); - } catch (ParseException pe) { - System.err.println("Error parsing command line arguments: " + pe.getMessage()); - System.exit(1); - return; - } - - if (line.hasOption('h')) { - printHelp(options, 0); - } - - // Check a filename is given - if (!line.hasOption('f')) { - System.err.println("Required parameter -f missing!"); - printHelp(options, 1); - } - String filename = line.getOptionValue('f'); - - // Option to apply template to new items - boolean useTemplate = false; - if (line.hasOption('t')) { - useTemplate = true; - } - - // Options for workflows, and workflow notifications for new items - boolean useWorkflow = false; - boolean workflowNotify = false; - if (line.hasOption('w')) { - useWorkflow = true; - if (line.hasOption('n')) { - workflowNotify = true; - } - } else if (line.hasOption('n')) { - System.err.println("Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option."); - System.exit(1); - } - - // Create a context - Context c; - try { - c = new Context(); - c.turnOffAuthorisationSystem(); - } catch (Exception e) { - System.err.println("Unable to create a new DSpace Context: " + e.getMessage()); - System.exit(1); - return; - } - - // Find the EPerson, assign to context - try { - if (line.hasOption('e')) { - EPerson eperson; - String e = line.getOptionValue('e'); - if (e.indexOf('@') != -1) { - eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(c, e); - } else { - eperson = EPersonServiceFactory.getInstance().getEPersonService().find(c, UUID.fromString(e)); - } - - if (eperson == null) { - System.out.println("Error, eperson cannot be found: " + e); - System.exit(1); - } - c.setCurrentUser(eperson); - } - } catch (Exception e) { - System.err.println("Unable to find DSpace user: " + e.getMessage()); - System.exit(1); - return; - } - - // Is this a silent run? - boolean change = false; - - // Read lines from the CSV file - DSpaceCSV csv; - try { - csv = new DSpaceCSV(new File(filename), c); - } catch (MetadataImportInvalidHeadingException miihe) { - System.err.println(miihe.getMessage()); - System.exit(1); - return; - } catch (Exception e) { - System.err.println("Error reading file: " + e.getMessage()); - System.exit(1); - return; - } - - // Perform the first import - just highlight differences - MetadataImport importer = new MetadataImport(c, csv); - List changes; - - if (!line.hasOption('s')) { - // See what has changed - try { - changes = importer.runImport(false, useWorkflow, workflowNotify, useTemplate); - } catch (MetadataImportException mie) { - System.err.println("Error: " + mie.getMessage()); - System.exit(1); - return; - } - - // Display the changes - int changeCounter = displayChanges(changes, false); - - // If there were changes, ask if we should execute them - if (changeCounter > 0) { - try { - // Ask the user if they want to make the changes - System.out.println("\n" + changeCounter + " item(s) will be changed\n"); - System.out.print("Do you want to make these changes? [y/n] "); - String yn = (new BufferedReader(new InputStreamReader(System.in))).readLine(); - if ("y".equalsIgnoreCase(yn)) { - change = true; - } else { - System.out.println("No data has been changed."); - } - } catch (IOException ioe) { - System.err.println("Error: " + ioe.getMessage()); - System.err.println("No changes have been made"); - System.exit(1); - } - } else { - System.out.println("There were no changes detected"); - } - } else { - change = true; - } - - try { - // If required, make the change - if (change) { - try { - // Make the changes - changes = importer.runImport(true, useWorkflow, workflowNotify, useTemplate); - } catch (MetadataImportException mie) { - System.err.println("Error: " + mie.getMessage()); - System.exit(1); - return; - } - - // Display the changes - displayChanges(changes, true); - - // Commit the change to the DB -// c.commit(); - } - - // Finsh off and tidy up - c.restoreAuthSystemState(); - c.complete(); - } catch (Exception e) { - c.abort(); - System.err.println("Error committing changes to database: " + e.getMessage()); - System.err.println("Aborting most recent changes."); - System.exit(1); - } - } } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java new file mode 100644 index 0000000000..a76d4aabb0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java @@ -0,0 +1,130 @@ +/** + * 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.content; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.google.common.collect.Iterators; +import org.apache.logging.log4j.Logger; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataExportService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public class MetadataExportServiceImpl implements MetadataExportService { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataExportServiceImpl.class); + + @Autowired + private ItemService itemService; + + public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle) + throws Exception { + Iterator toExport = null; + + if (!exportAllItems) { + log.info("Exporting whole repository WARNING: May take some time!"); + toExport = itemService.findAll(context); + } else { + DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, handle); + if (dso == null) { + throw new IllegalArgumentException( + "Item '" + handle + "' does not resolve to an item in your repository!"); + } + + if (dso.getType() == Constants.ITEM) { + log.info("Exporting item '" + dso.getName() + "' (" + handle + ")"); + List item = new ArrayList<>(); + item.add((Item) dso); + toExport = item.iterator(); + } else if (dso.getType() == Constants.COLLECTION) { + log.info("Exporting collection '" + dso.getName() + "' (" + handle + ")"); + Collection collection = (Collection) dso; + toExport = itemService.findByCollection(context, collection); + } else if (dso.getType() == Constants.COMMUNITY) { + log.info("Exporting community '" + dso.getName() + "' (" + handle + ")"); + toExport = buildFromCommunity(context, (Community) dso); + } else { + throw new IllegalArgumentException("Error identifying '" + handle + "'"); + } + } + + DSpaceCSV csv = this.export(context, toExport, exportAllMetadata); + return csv; + } + + /** + * Run the export + * + * @return the exported CSV lines + */ + public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception { + Context.Mode originalMode = context.getCurrentMode(); + context.setMode(Context.Mode.READ_ONLY); + + // Process each item + DSpaceCSV csv = new DSpaceCSV(exportAll); + while (toExport.hasNext()) { + Item item = toExport.next(); + csv.addItem(item); + context.uncacheEntity(item); + } + + context.setMode(originalMode); + // Return the results + return csv; + } + + public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception { + return export(context, buildFromCommunity(context, community), exportAll); + } + + /** + * Build an array list of item ids that are in a community (include sub-communities and collections) + * + * @param context DSpace context + * @param community The community to build from + * @return The list of item ids + * @throws SQLException if database error + */ + private Iterator buildFromCommunity(Context context, Community community) + throws SQLException { + // Add all the collections + List collections = community.getCollections(); + Iterator result = null; + for (Collection collection : collections) { + Iterator items = itemService.findByCollection(context, collection); + result = addItemsToResult(result, items); + + } + // Add all the sub-communities + List communities = community.getSubcommunities(); + for (Community subCommunity : communities) { + Iterator items = buildFromCommunity(context, subCommunity); + result = addItemsToResult(result, items); + } + + return result; + } + + private Iterator addItemsToResult(Iterator result, Iterator items) { + if (result == null) { + result = items; + } else { + result = Iterators.concat(result, items); + } + + return result; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java b/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java new file mode 100644 index 0000000000..de2ef4984f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java @@ -0,0 +1,26 @@ +/** + * 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.content.service; + +import java.util.Iterator; + +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; + +public interface MetadataExportService { + + public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle) + throws Exception; + + public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception; + + public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception; + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 4ce1c5063a..87c06d2f46 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -7,10 +7,14 @@ */ package org.dspace.scripts; +import java.io.InputStream; import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dspace.authorize.service.AuthorizeService; @@ -73,6 +77,18 @@ public abstract class DSpaceRunnable implements Runnable { return options; } + public List getFileNamesFromInputStreamOptions() { + List fileNames = new LinkedList<>(); + + for (Option option : options.getOptions()) { + if (option.getType() == InputStream.class) { + fileNames.add(commandLine.getOptionValue(option.getOpt())); + } + } + + return fileNames; + } + /** * This method will take the primitive array of String objects that represent the parameters given to the String * and it'll parse these into a CommandLine object that can be used by the script to retrieve the data diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index cb5a5c9944..8e2565428a 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -7,6 +7,8 @@ */ package org.dspace.scripts; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; @@ -17,8 +19,14 @@ import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; import org.dspace.content.ProcessStatus; import org.dspace.content.dao.ProcessDAO; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; @@ -35,6 +43,15 @@ public class ProcessServiceImpl implements ProcessService { @Autowired private ProcessDAO processDAO; + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private BitstreamFormatService bitstreamFormatService; + + @Autowired + private AuthorizeService authorizeService; + @Override public Process create(Context context, EPerson ePerson, String scriptName, List parameters) throws SQLException { @@ -112,6 +129,21 @@ public class ProcessServiceImpl implements ProcessService { } + @Override + public void appendFile(Context context, Process process, InputStream is, String type, String fileName) + throws IOException, SQLException, AuthorizeException { + Bitstream bitstream = bitstreamService.create(context, is); + bitstream.setName(context, fileName); + bitstreamService.setFormat(context, bitstream, bitstreamFormatService.guessFormat(context, bitstream)); + bitstreamService.addMetadata(context, bitstream, "process", "type", null, null, type); + authorizeService.addPolicy(context, bitstream, Constants.READ, context.getCurrentUser()); + authorizeService.addPolicy(context, bitstream, Constants.WRITE, context.getCurrentUser()); + authorizeService.addPolicy(context, bitstream, Constants.DELETE, context.getCurrentUser()); + bitstreamService.update(context, bitstream); + process.addBitstream(bitstream); + update(context, process); + } + @Override public void delete(Context context, Process process) throws SQLException { processDAO.delete(context, process); @@ -141,6 +173,33 @@ public class ProcessServiceImpl implements ProcessService { return parameterList; } + @Override + public Bitstream getBitstreamByName(Context context, Process process, String bitstreamName) { + for (Bitstream bitstream : getBitstreams(context, process, null)) { + if (StringUtils.equals(bitstream.getName(), bitstreamName)) { + return bitstream; + } + } + + return null; + } + + public List getBitstreams(Context context, Process process, String type) { + List allBitstreams = process.getBitstreams(); + + if (type == null) { + return allBitstreams; + } else { + List filteredBitstreams = new ArrayList<>(); + for (Bitstream bitstream : allBitstreams) { + if (StringUtils.equals(bitstreamService.getMetadata(bitstream, "process.type"), type)) { + filteredBitstreams.add(bitstream); + } + } + return filteredBitstreams; + } + } + public int countTotal(Context context) throws SQLException { return processDAO.countRows(context); } diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java index 01ca2fafd9..7e1bd713f3 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java @@ -7,9 +7,13 @@ */ package org.dspace.scripts.handler; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import org.apache.commons.cli.Options; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; /** * This is an interface meant to be implemented by any DSpaceRunnableHandler to specify specific execution methods @@ -78,4 +82,9 @@ public interface DSpaceRunnableHandler { * @param name The name of the script */ public void printHelp(Options options, String name); + + public InputStream getFileStream(Context context, String fileName) throws IOException, AuthorizeException; + + public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) + throws IOException; } diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java index 97925c1843..f7a62b8acf 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java @@ -7,9 +7,15 @@ */ package org.dspace.scripts.handler.impl; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; +import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; +import org.dspace.core.Context; import org.dspace.scripts.handler.DSpaceRunnableHandler; /** @@ -84,4 +90,20 @@ public class CommandLineDSpaceRunnableHandler implements DSpaceRunnableHandler { formatter.printHelp(name, options); } } + + @Override + public InputStream getFileStream(Context context, String fileName) throws IOException { + File file = new File(fileName); + if (!file.isFile()) { + return null; + } + return FileUtils.openInputStream(file); + } + + @Override + public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) + throws IOException { + File file = new File(fileName); + FileUtils.copyInputStreamToFile(inputStream, file); + } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java index e277ab32f4..c2363effd5 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -7,9 +7,13 @@ */ package org.dspace.scripts.service; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.List; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; @@ -104,6 +108,9 @@ public interface ProcessService { */ public void complete(Context context, Process process) throws SQLException; + public void appendFile(Context context, Process process, InputStream is, String type, String fileName) + throws IOException, SQLException, AuthorizeException; + /** * This method will delete the given Process object from the database * @param context The relevant DSpace context @@ -128,6 +135,10 @@ public interface ProcessService { */ public List getParameters(Process process); + public Bitstream getBitstreamByName(Context context, Process process, String bitstreamName); + + public List getBitstreams(Context context, Process process, String type); + /** * Returns the total amount of Process objects in the dataase * @param context The relevant DSpace context diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java index b89e4126bf..6c1c12500b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import java.util.List; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.link.HalLinkService; @@ -24,7 +26,9 @@ 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.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; /** * This controller adds additional subresource methods to allow connecting scripts with processes @@ -53,12 +57,13 @@ public class ScriptProcessesController { */ @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasAuthority('ADMIN')") - public ResponseEntity startProcess(@PathVariable(name = "name") String scriptName) + public ResponseEntity startProcess(@PathVariable(name = "name") String scriptName, + @RequestParam(name = "file") List files) throws Exception { if (log.isTraceEnabled()) { log.trace("Starting Process for Script with name: " + scriptName); } - ProcessRest processRest = scriptRestRepository.startProcess(scriptName); + ProcessRest processRest = scriptRestRepository.startProcess(scriptName, files); ProcessResource processResource = new ProcessResource(processRest, utils, null); halLinkService.addLinks(processResource); return ControllerUtils.toResponseEntity(HttpStatus.ACCEPTED, null, processResource); 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 f075828e88..e92c204cfb 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 @@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.WebRequest; +import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; /** @@ -54,8 +55,8 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH } } - @ExceptionHandler(IllegalArgumentException.class) - protected void handleIllegalArgumentException(HttpServletRequest request, HttpServletResponse response, + @ExceptionHandler({IllegalArgumentException.class, MultipartException.class}) + protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_BAD_REQUEST); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index 12b33d441c..3341156529 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -24,6 +24,7 @@ import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; import org.dspace.app.rest.converter.ScriptConverter; import org.dspace.app.rest.converter.processes.ProcessConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ParameterValueRest; import org.dspace.app.rest.model.ProcessRest; import org.dspace.app.rest.model.ScriptRest; @@ -40,6 +41,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; /** * This is the REST repository dealing with the Script logic @@ -103,7 +105,8 @@ public class ScriptRestRepository extends DSpaceRestRepository files) throws SQLException, IOException, AuthorizeException { Context context = obtainContext(); String properties = requestService.getCurrentRequest().getServletRequest().getParameter("properties"); List dSpaceCommandLineParameters = @@ -119,7 +122,7 @@ public class ScriptRestRepository extends DSpaceRestRepository args = constructArgs(dSpaceCommandLineParameters); try { - runDSpaceScript(scriptToExecute, restDSpaceRunnableHandler, args); + runDSpaceScript(files, context, scriptToExecute, restDSpaceRunnableHandler, args); context.complete(); return processConverter.fromModel(restDSpaceRunnableHandler.getProcess()); } catch (SQLException e) { @@ -167,10 +170,13 @@ public class ScriptRestRepository extends DSpaceRestRepository args) { + private void runDSpaceScript(List files, Context context, DSpaceRunnable scriptToExecute, + RestDSpaceRunnableHandler restDSpaceRunnableHandler, List args) + throws IOException { try { scriptToExecute.initialize(args.toArray(new String[0]), restDSpaceRunnableHandler); + checkFileNames(scriptToExecute, files); + processFiles(context, restDSpaceRunnableHandler, files); restDSpaceRunnableHandler.schedule(scriptToExecute); } catch (ParseException e) { scriptToExecute.printHelp(); @@ -181,4 +187,31 @@ public class ScriptRestRepository extends DSpaceRestRepository files) + throws IOException { + for (MultipartFile file : files) { + restDSpaceRunnableHandler + .writeFilestream(context, file.getOriginalFilename(), file.getInputStream(), "inputfile"); + } + } + + private void checkFileNames(DSpaceRunnable scriptToExecute, List files) { + List fileNames = new LinkedList<>(); + for (MultipartFile file : files) { + String fileName = file.getOriginalFilename(); + if (fileNames.contains(fileName)) { + throw new UnprocessableEntityException("There are two files with the same name: " + fileName); + } else { + fileNames.add(fileName); + } + } + + List fileNamesFromOptions = scriptToExecute.getFileNamesFromInputStreamOptions(); + if (!fileNames.containsAll(fileNamesFromOptions)) { + throw new UnprocessableEntityException("Files given in properties aren't all present in the request"); + } + } + + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index dd549a5873..890f59cc4b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.scripts.handler.impl; +import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; @@ -16,8 +18,12 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; import org.dspace.content.ProcessStatus; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.factory.ProcessServiceFactory; +import org.dspace.content.service.BitstreamService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; @@ -25,6 +31,8 @@ import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.Process; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.scripts.service.ProcessService; +import org.dspace.utils.DSpace; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * The {@link DSpaceRunnableHandler} dealing with Scripts started from the REST api @@ -34,6 +42,7 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { .getLogger(RestDSpaceRunnableHandler.class); private ProcessService processService = ProcessServiceFactory.getInstance().getProcessService(); + private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); private Integer processId; private String scriptName; @@ -176,6 +185,29 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { } } + @Override + public InputStream getFileStream(Context context, String fileName) throws IOException, AuthorizeException { + try { + Process process = processService.find(context, processId); + Bitstream bitstream = processService.getBitstreamByName(context, process, fileName); + return bitstreamService.retrieve(context, bitstream); + } catch (SQLException sqlException) { + log.error("SQL exception while attempting to find process", sqlException); + } + return null; + } + + @Override + public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) + throws IOException { + try { + Process process = processService.find(context, processId); + processService.appendFile(context, process, inputStream, type, fileName); + } catch (SQLException | AuthorizeException exception) { + log.error("Exception occurred while attempting to find process", exception); + } + } + /** * This method will return the process created by this handler * @return The Process database object created by this handler @@ -200,6 +232,9 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { * @param script The script to be ran */ public void schedule(DSpaceRunnable script) { + ThreadPoolTaskExecutor taskExecutor = new DSpace().getServiceManager() + .getServiceByName("dspaceRunnableThreadExecutor", + ThreadPoolTaskExecutor.class); Context context = new Context(); try { Process process = processService.find(context, processId); @@ -213,6 +248,6 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { context.abort(); } } - script.run(); + taskExecutor.execute(script); } } diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml new file mode 100644 index 0000000000..8fadf8be72 --- /dev/null +++ b/dspace/config/spring/rest/scripts.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file From 84775128b1ed5b768c24a1d18a5da972366a20ec Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Thu, 5 Dec 2019 15:59:52 +0100 Subject: [PATCH 010/749] Adding missing MetadataExportServiceImpl to the core-services.xml file --- dspace/config/spring/api/core-services.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index f1ce9103b0..6a1d4f897a 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -44,6 +44,7 @@ + From e295a76c70a8f211531248957ceb27df2019a078 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 10 Dec 2019 16:13:54 +0100 Subject: [PATCH 011/749] [Task 66428] Applied feedback to the MetadataImport and Export scripts and framework --- .../org/dspace/app/bulkedit/DSpaceCSV.java | 22 ------------ .../dspace/app/bulkedit/MetadataExport.java | 29 ++++++++------- .../dspace/app/bulkedit/MetadataImport.java | 24 ++++--------- .../app/bulkedit/MetadataImportCLI.java | 29 +++++++++++++++ .../content/MetadataExportServiceImpl.java | 24 ++++++------- .../service/MetadataExportService.java | 36 +++++++++++++++++-- .../org/dspace/scripts/DSpaceRunnable.java | 5 +++ .../dspace/scripts/ProcessServiceImpl.java | 1 + .../handler/DSpaceRunnableHandler.java | 22 ++++++++++-- .../CommandLineDSpaceRunnableHandler.java | 12 ------- .../scripts/service/ProcessService.java | 16 +++++++++ .../impl/RestDSpaceRunnableHandler.java | 5 --- dspace/config/spring/api/scripts.xml | 2 +- dspace/config/spring/rest/scripts.xml | 4 +++ 14 files changed, 142 insertions(+), 89 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index 93f8c07bc3..d85f327092 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -8,12 +8,8 @@ package org.dspace.app.bulkedit; import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStreamWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -623,24 +619,6 @@ public class DSpaceCSV implements Serializable { return csvLines; } - /** - * Save the CSV file to the given filename - * - * @param filename The filename to save the CSV file to - * @throws IOException Thrown if an error occurs when writing the file - */ - public final void save(String filename) throws IOException { - // Save the file - BufferedWriter out = new BufferedWriter( - new OutputStreamWriter( - new FileOutputStream(filename), "UTF-8")); - for (String csvLine : getCSVLinesAsStringArray()) { - out.write(csvLine + "\n"); - } - out.flush(); - out.close(); - } - public InputStream getInputStream() { StringBuilder stringBuilder = new StringBuilder(); for (String csvLine : getCSVLinesAsStringArray()) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java index a27ee5cdf2..f6527855f0 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class MetadataExport extends DSpaceRunnable { - private Context c = null; + private Context context = null; private boolean help = false; private String filename = null; private String handle = null; @@ -31,43 +31,46 @@ public class MetadataExport extends DSpaceRunnable { @Autowired private MetadataExportService metadataExportService; - protected Context context; - private MetadataExport() { - Options options = constructOptions(); - this.options = options; + this.options = constructOptions(); } private Options constructOptions() { Options options = new Options(); options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)"); + options.getOption("i").setType(String.class); options.addOption("f", "file", true, "destination where you want file written"); + options.getOption("f").setType(String.class); options.getOption("f").setRequired(true); options.addOption("a", "all", false, "include all metadata fields that are not normally changed (e.g. provenance)"); + options.getOption("a").setType(boolean.class); options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + return options; } public void internalRun() throws Exception { if (help) { - handler.logInfo("\nfull export: metadataexport -f filename"); - handler.logInfo("partial export: metadataexport -i handle -f filename"); + handler.logInfo("\nfull export: metadata-export -f filename"); + handler.logInfo("partial export: metadata-export -i handle -f filename"); printHelp(); return; } - DSpaceCSV dSpaceCSV = metadataExportService.handleExport(c, exportAllItems, exportAllMetadata, handle); - handler.writeFilestream(c, filename, dSpaceCSV.getInputStream(), "exportCSV"); - c.restoreAuthSystemState(); - c.complete(); + DSpaceCSV dSpaceCSV = metadataExportService.handleExport(context, exportAllItems, exportAllMetadata, handle, + handler); + handler.writeFilestream(context, filename, dSpaceCSV.getInputStream(), "exportCSV"); + context.restoreAuthSystemState(); + context.complete(); } public void setup() throws ParseException { - c = new Context(); - c.turnOffAuthorisationSystem(); + context = new Context(); + context.turnOffAuthorisationSystem(); if (commandLine.hasOption('h')) { help = true; diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 83e3e2d8b4..6fd4be68fe 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -21,7 +21,6 @@ import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; @@ -59,6 +58,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -219,7 +219,7 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { try { // Ask the user if they want to make the changes handler.logInfo("\n" + changeCounter + " item(s) will be changed\n"); - change = handler.getUserValidation(); + change = determineChange(handler); } catch (IOException ioe) { throw new IOException("Error: " + ioe.getMessage() + ", No changes have been made", ioe); @@ -256,6 +256,10 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { } + protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException { + return true; + } + public void setup() throws ParseException { useTemplate = false; filename = null; @@ -322,7 +326,7 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { change = false; } - private MetadataImport() { + public MetadataImport() { Options options = constructOptions(); this.options = options; } @@ -1230,20 +1234,6 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { return in.replaceAll("\r\n", "").replaceAll("\n", "").trim(); } - /** - * Print the help message - * - * @param options The command line options the user gave - * @param exitCode the system exit code to use - */ - private static void printHelp(Options options, int exitCode) { - // print the help message - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MetatadataImport\n", options); - System.out.println("\nmetadataimport: MetadataImport -f filename"); - System.exit(exitCode); - } - /** * Display the changes that have been detected, or that have been made * diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java new file mode 100644 index 0000000000..f7ed5adce5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.bulkedit; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.dspace.scripts.handler.DSpaceRunnableHandler; + +public class MetadataImportCLI extends MetadataImport { + + @Override + protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException { + handler.logInfo("Do you want to make these changes? [y/n] "); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))) { + String yn = bufferedReader.readLine(); + if ("y".equalsIgnoreCase(yn)) { + return true; + } + return false; + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java index a76d4aabb0..24338954d0 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java @@ -13,28 +13,27 @@ import java.util.Iterator; import java.util.List; import com.google.common.collect.Iterators; -import org.apache.logging.log4j.Logger; import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataExportService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.springframework.beans.factory.annotation.Autowired; public class MetadataExportServiceImpl implements MetadataExportService { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataExportServiceImpl.class); - @Autowired private ItemService itemService; - public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle) - throws Exception { + @Override + public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle, + DSpaceRunnableHandler handler) throws Exception { Iterator toExport = null; if (!exportAllItems) { - log.info("Exporting whole repository WARNING: May take some time!"); + handler.logInfo("Exporting whole repository WARNING: May take some time!"); toExport = itemService.findAll(context); } else { DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, handle); @@ -44,16 +43,16 @@ public class MetadataExportServiceImpl implements MetadataExportService { } if (dso.getType() == Constants.ITEM) { - log.info("Exporting item '" + dso.getName() + "' (" + handle + ")"); + handler.logInfo("Exporting item '" + dso.getName() + "' (" + handle + ")"); List item = new ArrayList<>(); item.add((Item) dso); toExport = item.iterator(); } else if (dso.getType() == Constants.COLLECTION) { - log.info("Exporting collection '" + dso.getName() + "' (" + handle + ")"); + handler.logInfo("Exporting collection '" + dso.getName() + "' (" + handle + ")"); Collection collection = (Collection) dso; toExport = itemService.findByCollection(context, collection); } else if (dso.getType() == Constants.COMMUNITY) { - log.info("Exporting community '" + dso.getName() + "' (" + handle + ")"); + handler.logInfo("Exporting community '" + dso.getName() + "' (" + handle + ")"); toExport = buildFromCommunity(context, (Community) dso); } else { throw new IllegalArgumentException("Error identifying '" + handle + "'"); @@ -64,11 +63,7 @@ public class MetadataExportServiceImpl implements MetadataExportService { return csv; } - /** - * Run the export - * - * @return the exported CSV lines - */ + @Override public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception { Context.Mode originalMode = context.getCurrentMode(); context.setMode(Context.Mode.READ_ONLY); @@ -86,6 +81,7 @@ public class MetadataExportServiceImpl implements MetadataExportService { return csv; } + @Override public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception { return export(context, buildFromCommunity(context, community), exportAll); } diff --git a/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java b/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java index de2ef4984f..77584ceeb0 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java @@ -13,14 +13,46 @@ import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +/** + * This is the interface to be implemented by a Service that deals with the exporting of Metadata + */ public interface MetadataExportService { - public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle) - throws Exception; + /** + * This method will export DSpaceObject objects depending on the parameters it gets. It can export all the items + * in the repository, all the items in a community, all the items in a collection or a specific item. The latter + * three are specified by the handle parameter. The entire repository can be exported by defining the + * exportAllItems parameter as true + * @param context The relevant DSpace context + * @param exportAllItems A boolean indicating whether or not the entire repository should be exported + * @param exportAllMetadata Defines if all metadata should be exported or only the allowed ones + * @param handle The handle for the DSpaceObject to be exported, can be a Community, Collection or Item + * @return A DSpaceCSV object containing the exported information + * @throws Exception If something goes wrong + */ + public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, + String handle, DSpaceRunnableHandler dSpaceRunnableHandler) throws Exception; + /** + * This method will export all the Items in the given toExport iterator to a DSpaceCSV + * @param context The relevant DSpace context + * @param toExport The iterator containing the items to export + * @param exportAll Defines if all metadata should be exported or only the allowed ones + * @return A DSpaceCSV object containing the exported information + * @throws Exception If something goes wrong + */ public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception; + /** + * This method will export all the Items within the given Community to a DSpaceCSV + * @param context The relevant DSpace context + * @param community The Community that contains the Items to be exported + * @param exportAll Defines if all metadata should be exported or only the allowed ones + * @return A DSpaceCSV object containing the exported information + * @throws Exception If something goes wrong + */ public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception; } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 87c06d2f46..8d8d8caa60 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -77,6 +77,11 @@ public abstract class DSpaceRunnable implements Runnable { return options; } + /** + * This method will traverse all the options and it'll grab options defined as an InputStream type to then save + * the filename specified by that option in a list of Strings that'll be returned in the end + * @return The list of Strings representing filenames from the options given to the script + */ public List getFileNamesFromInputStreamOptions() { List fileNames = new LinkedList<>(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index 8e2565428a..67fd151ac9 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -184,6 +184,7 @@ public class ProcessServiceImpl implements ProcessService { return null; } + @Override public List getBitstreams(Context context, Process process, String type) { List allBitstreams = process.getBitstreams(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java index 7d615f4f49..543f21855c 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java @@ -83,11 +83,27 @@ public interface DSpaceRunnableHandler { */ public void printHelp(Options options, String name); + /** + * This method will grab the InputStream for the file defined by the given file name. The exact implementation will + * differ based on whether it's a REST call or CommandLine call. The REST Call will look for Bitstreams in the + * Database whereas the CommandLine call will look on the filesystem + * @param context The relevant DSpace context + * @param fileName The filename for the file that holds the InputStream + * @return The InputStream for the file defined by the given file name + * @throws IOException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ public InputStream getFileStream(Context context, String fileName) throws IOException, AuthorizeException; + /** + * This method will write the InputStream to either a file on the filesystem or a bitstream in the database + * depending on whether it's coming from a CommandLine call or REST call respectively + * @param context The relevant DSpace context + * @param fileName The filename + * @param inputStream The inputstream to be written + * @param type The type of the file + * @throws IOException If something goes wrong + */ public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) throws IOException; - - boolean getUserValidation() throws IOException; - } diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java index f0482cde92..f7a62b8acf 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java @@ -7,11 +7,9 @@ */ package org.dspace.scripts.handler.impl; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; @@ -108,14 +106,4 @@ public class CommandLineDSpaceRunnableHandler implements DSpaceRunnableHandler { File file = new File(fileName); FileUtils.copyInputStreamToFile(inputStream, file); } - - @Override - public boolean getUserValidation() throws IOException { - logInfo("Do you want to make these changes? [y/n] "); - String yn = (new BufferedReader(new InputStreamReader(System.in))).readLine(); - if ("y".equalsIgnoreCase(yn)) { - return true; - } - return false; - } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java index c2363effd5..b8e78a5f4f 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -135,8 +135,24 @@ public interface ProcessService { */ public List getParameters(Process process); + /** + * This method will return the Bitstream that matches the given name for the given Process + * @param context The relevant DSpace context + * @param process The process that should hold the requested Bitstream + * @param bitstreamName The name of the requested Bitstream + * @return The Bitstream from the given Process that matches the given bitstream name + */ public Bitstream getBitstreamByName(Context context, Process process, String bitstreamName); + /** + * This method will return all the Bitstreams for a given process if the type is defined as null. If type is + * different than null, the bitstreams with metadata process.type equal to the given type from that process + * are returned + * @param context The relevant DSpace context + * @param process The process that holds the Bitstreams to be searched in + * @param type The type that the Bitstream must have + * @return The list of Bitstreams of the given type for the given Process + */ public List getBitstreams(Context context, Process process, String type); /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index 4b45fecb78..890f59cc4b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -250,9 +250,4 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { } taskExecutor.execute(script); } - - @Override - public boolean getUserValidation() throws IOException { - return true; - } } diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index c57500d271..fc10384e95 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -9,7 +9,7 @@ - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 8fadf8be72..09fca4e47f 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -6,4 +6,8 @@ + + + + \ No newline at end of file From a208058d387a2ec06c9153735e6deba15c0deded Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 6 Jan 2020 14:12:08 +0100 Subject: [PATCH 012/749] Working on fixes test issues --- .../org/dspace/app/launcher/ScriptLauncher.java | 15 ++++----------- dspace/config/spring/api/scripts.xml | 2 +- dspace/config/spring/rest/scripts.xml | 4 +++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java index fd0465c931..1ed9a2ac78 100644 --- a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java +++ b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java @@ -13,12 +13,6 @@ import java.lang.reflect.Method; import java.util.List; import java.util.TreeMap; -import org.apache.commons.cli.ParseException; -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.scripts.DSpaceRunnable; -import org.dspace.scripts.handler.DSpaceRunnableHandler; -import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; import org.apache.commons.cli.ParseException; import org.apache.log4j.Logger; import org.dspace.scripts.DSpaceRunnable; @@ -28,7 +22,6 @@ import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.dspace.services.RequestService; -import org.dspace.utils.DSpace; import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; @@ -115,11 +108,11 @@ public class ScriptLauncher { * @param commandConfigs The Document * @param dSpaceRunnableHandler The DSpaceRunnableHandler for this execution * @param kernelImpl The relevant DSpaceKernelImpl - * @return A 1 or 0 depending on whether the script failed or passed respectively + * @return A 1 or 0 depending on whether the script failed or passed respectively */ public static int handleScript(String[] args, Document commandConfigs, - DSpaceRunnableHandler dSpaceRunnableHandler, - DSpaceKernelImpl kernelImpl) { + DSpaceRunnableHandler dSpaceRunnableHandler, + DSpaceKernelImpl kernelImpl) { int status; DSpaceRunnable script = ScriptServiceFactory.getInstance().getScriptService().getScriptForName(args[0]); if (script != null) { @@ -135,7 +128,7 @@ public class ScriptLauncher { * @param args The arguments of the script with the script name as first place in the array * @param dSpaceRunnableHandler The relevant DSpaceRunnableHandler * @param script The script to be executed - * @return A 1 or 0 depending on whether the script failed or passed respectively + * @return A 1 or 0 depending on whether the script failed or passed respectively */ private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, DSpaceRunnable script) { diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index fc10384e95..81acb052c3 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -9,7 +9,7 @@ - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 09fca4e47f..dcdd55ebee 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -6,7 +6,9 @@ - + + From 46a3407642f5aca0535edbfecb6892cde6623b1d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 9 Jan 2020 13:19:43 +0100 Subject: [PATCH 013/749] 67688: Add IRUS patch to DSpace 7 --- .../export/AbstractUsageEventListener.java | 66 +++ .../export/ExportUsageEventListener.java | 417 ++++++++++++++++++ .../statistics/export/OpenURLTracker.java | 95 ++++ .../OpenURLTrackerLoggerServiceImpl.java | 41 ++ .../export/RetryOpenUrlTracker.java | 74 ++++ .../statistics/export/SpiderDetector.java | 289 ++++++++++++ .../export/dao/OpenURLTrackerDAO.java | 19 + .../dao/impl/OpenURLTrackerDAOImpl.java | 24 + .../OpenURLTrackerLoggerServiceFactory.java | 27 ++ ...penURLTrackerLoggerServiceFactoryImpl.java | 25 ++ .../service/OpenURLTrackerLoggerService.java | 26 ++ .../V7.0_2020.01.08__Statistics-harvester.sql | 29 ++ .../V7.0_2020.01.08__Statistics-harvester.sql | 29 ++ .../V7.0_2020.01.08__Statistics-harvester.sql | 29 ++ dspace/config/dspace.cfg | 1 + dspace/config/hibernate.cfg.xml | 2 + dspace/config/launcher.xml | 7 + dspace/config/log4j2.xml | 2 + dspace/config/modules/stats.cfg | 35 ++ .../config/spring/api/core-dao-services.xml | 1 + .../spring/api/core-factory-services.xml | 2 + dspace/config/spring/api/core-services.xml | 1 + dspace/config/spring/api/scripts.xml | 4 + .../spring/rest/event-service-listeners.xml | 5 + dspace/src/main/config/build.xml | 22 + 25 files changed, 1272 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.01.08__Statistics-harvester.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__Statistics-harvester.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql create mode 100644 dspace/config/modules/stats.cfg diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java new file mode 100644 index 0000000000..798514e447 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java @@ -0,0 +1,66 @@ +/** + * 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.statistics.export; + +import org.dspace.services.EventService; +import org.dspace.services.model.EventListener; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * AbstractUsageEventListener is used as the base class for listening events running + * in the EventService. + * + * @author Mark Diggory (mdiggory at atmire.com) + * @version $Revision: $ + */ +public abstract class AbstractUsageEventListener implements EventListener, BeanPostProcessor { + + public AbstractUsageEventListener() { + super(); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (beanName.equals("org.dspace.services.EventService")) { + setEventService((EventService) bean); + } + return bean; + } + + /** + * Empty String[] flags to have Listener + * consume any event name prefixes. + */ + public String[] getEventNamePrefixes() { + return new String[0]; + } + + /** + * Currently consumes events generated for + * all resources. + */ + public String getResourcePrefix() { + return null; + } + + public void setEventService(EventService service) { + if (service != null) { + service.registerEventListener(this); + } else { + throw new IllegalStateException("EventService handed to Listener cannot be null"); + } + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java new file mode 100644 index 0000000000..66c5414237 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java @@ -0,0 +1,417 @@ +/** + * 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.statistics.export; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.util.Util; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.DCDate; +import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.core.Context; +import org.dspace.core.LogManager; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.model.Event; +import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; +import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.util.SpiderDetector; +import org.dspace.usage.AbstractUsageEventListener; +import org.dspace.usage.UsageEvent; + +/** + * User: kevin (kevin at atmire.com) + * Date: 30-mrt-2010 + * Time: 16:37:56 + */ +public class ExportUsageEventListener extends AbstractUsageEventListener { + /* Log4j logger*/ + private static Logger log = Logger.getLogger(ExportUsageEventListener.class); + + /* The metadata field which is to be checked for */ + private static MetadataField trackerType; + + /* A list of values the type might have */ + private static List trackerValues; + + /* The base url of the tracker */ + private static String baseUrl; + + private static String trackerUrlVersion; + + private static final String ITEM_VIEW = "Investigation"; + private static final String BITSTREAM_DOWNLOAD = "Request"; + + private static ConfigurationService configurationService; + + + public void init(Context context) { + try { + if (configurationService == null) { + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + } + if (trackerType == null) { + trackerType = resolveConfigPropertyToMetadataField(context, "tracker.type-field"); + + String[] metadataValues = configurationService.getArrayProperty("stats.tracker.type-value"); + if (metadataValues.length > 0) { + trackerValues = new ArrayList<>(); + for (String metadataValue : metadataValues) { + trackerValues.add(metadataValue.toLowerCase()); + } + } else { + trackerValues = null; + } + + if (StringUtils.equals(configurationService.getProperty("stats.tracker.environment"), "production")) { + baseUrl = configurationService.getProperty("stats.tracker.produrl"); + } else { + baseUrl = configurationService.getProperty("stats.tracker.testurl"); + } + + trackerUrlVersion = configurationService.getProperty("stats.tracker.urlversion"); + + + } + } catch (Exception e) { + log.error("Unknown error resolving configuration for the export usage event.", e); + trackerType = null; + trackerValues = null; + baseUrl = null; + trackerUrlVersion = null; + } + } + + public void receiveEvent(Event event) { + if (event instanceof UsageEvent) { + UsageEvent ue = (UsageEvent) event; + Context context = ue.getContext(); + try { + //Check for item investigation + if (ue.getObject() instanceof Item) { + Item item = (Item) ue.getObject(); + if (item.isArchived() && !ContentServiceFactory.getInstance().getItemService() + .canEdit(context, item)) { + init(context); + + if (shouldProcessItem(item)) { + processItem(ue.getContext(), item, null, ue.getRequest(), ITEM_VIEW); + } + } + } + //Check for bitstream download + if (ue.getObject() instanceof Bitstream) { + Bitstream bit = (Bitstream) ue.getObject(); + //Check for an item + if (0 < bit.getBundles().size()) { + if (!SpiderDetector.isSpider(ue.getRequest())) { + Bundle bundle = bit.getBundles().get(0); + if (bundle.getName() == null || !bundle.getName().equals("ORIGINAL")) { + return; + } + + if (0 < bundle.getItems().size()) { + Item item = bundle.getItems().get(0); + + if (item.isArchived() && !ContentServiceFactory.getInstance().getItemService() + .canEdit(context, item)) { + //Check if we have a valid type of item ! + init(context); + if (shouldProcessItem(item)) { + processItem(ue.getContext(), item, bit, ue.getRequest(), BITSTREAM_DOWNLOAD); + } + } + } + } else { + log.info("Robot (" + ue.getRequest().getHeader("user-agent") + ") accessed " + bit + .getName() + "/" + bit.getSource()); + } + } + } + } catch (Exception e) { + UUID id; + id = ue.getObject().getID(); + + int type; + try { + type = ue.getObject().getType(); + } catch (Exception e1) { + type = -1; + } + log.error(LogManager.getHeader(ue.getContext(), "Error while processing export of use event", + "Id: " + id + " type: " + type), e); + e.printStackTrace(); + } + } + } + + private boolean shouldProcessItem(Item item) { + if (trackerType != null && trackerValues != null) { + List types = ContentServiceFactory.getInstance().getItemService() + .getMetadata(item, trackerType.getMetadataSchema().getName(), + trackerType.getElement(), + trackerType.getQualifier(), Item.ANY); + + if (!types.isEmpty()) { + //Find out if we have a type that needs to be excluded + for (MetadataValue type : types) { + if (trackerValues.contains(type.getValue().toLowerCase())) { + //We have found no type so process this item + return false; + } + } + return true; + } else { + // No types in this item, so not excluded + return true; + } + } else { + // No types to be excluded + return true; + } + } + + private void processItem(Context context, Item item, Bitstream bitstream, HttpServletRequest request, + String eventType) throws IOException, SQLException { + //We have a valid url collect the rest of the data + String clientIP = request.getRemoteAddr(); + if (configurationService.getBooleanProperty("useProxies", false) && request + .getHeader("X-Forwarded-For") != null) { + /* This header is a comma delimited list */ + for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { + /* proxy itself will sometime populate this header with the same value in + remote address. ordering in spec is vague, we'll just take the last + not equal to the proxy + */ + if (!request.getHeader("X-Forwarded-For").contains(clientIP)) { + clientIP = xfip.trim(); + } + } + } + String clientUA = StringUtils.defaultIfBlank(request.getHeader("USER-AGENT"), ""); + String referer = StringUtils.defaultIfBlank(request.getHeader("referer"), ""); + + //Start adding our data + StringBuilder data = new StringBuilder(); + data.append(URLEncoder.encode("url_ver", "UTF-8") + "=" + URLEncoder.encode(trackerUrlVersion, "UTF-8")); + data.append("&").append(URLEncoder.encode("req_id", "UTF-8")).append("=") + .append(URLEncoder.encode(clientIP, "UTF-8")); + data.append("&").append(URLEncoder.encode("req_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(clientUA, "UTF-8")); + data.append("&").append(URLEncoder.encode("rft.artnum", "UTF-8")).append("="). + append(URLEncoder.encode("oai:" + configurationService.getProperty("dspace.hostname") + ":" + item + .getHandle(), "UTF-8")); + data.append("&").append(URLEncoder.encode("rfr_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(referer, "UTF-8")); + data.append("&").append(URLEncoder.encode("rfr_id", "UTF-8")).append("=") + .append(URLEncoder.encode(configurationService.getProperty("dspace.hostname"), "UTF-8")); + data.append("&").append(URLEncoder.encode("url_tim", "UTF-8")).append("=") + .append(URLEncoder.encode(new DCDate(new Date()).toString(), "UTF-8")); + + if (BITSTREAM_DOWNLOAD.equals(eventType)) { + String bitstreamInfo = getBitstreamInfo(item, bitstream); + data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(bitstreamInfo, "UTF-8")); + data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(BITSTREAM_DOWNLOAD, "UTF-8")); + } else if (ITEM_VIEW.equals(eventType)) { + String itemInfo = getItemInfo(item); + data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(itemInfo, "UTF-8")); + data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(ITEM_VIEW, "UTF-8")); + } + + processUrl(context, baseUrl + "?" + data.toString()); + + } + + private String getBitstreamInfo(final Item item, final Bitstream bitstream) { + //only for jsp ui + // http://demo.dspace.org/jspui/handle/10673/2235 + // http://demo.dspace.org/jspui/bitstream/10673/2235/1/Captura.JPG + // + + + //only fror xmlui + // http://demo.dspace.org/xmlui/handle/10673/2235 + // http://demo.dspace.org/xmlui/bitstream/handle/10673/2235/Captura.JPG?sequence=1 + // + + String uiType = configurationService.getProperty("stats.dspace.type"); + StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); + if ("jspui".equals(uiType)) { + + sb.append("/bitstream/").append(item.getHandle()).append("/").append(bitstream.getSequenceID()); + + // If we can, append the pretty name of the bitstream to the URL + try { + if (bitstream.getName() != null) { + sb.append("/").append(Util.encodeBitstreamName(bitstream.getName(), "UTF-8")); + } + } catch (UnsupportedEncodingException uee) { + // just ignore it, we don't have to have a pretty + // name at the end of the URL because the sequence id will + // locate it. However it means that links in this file might + // not work.... + } + + + } else { //xmlui + + String identifier = null; + if (item != null && item.getHandle() != null) { + identifier = "handle/" + item.getHandle(); + } else if (item != null) { + identifier = "item/" + item.getID(); + } else { + identifier = "id/" + bitstream.getID(); + } + + + sb.append("/bitstream/").append(identifier).append("/"); + + // If we can, append the pretty name of the bitstream to the URL + try { + if (bitstream.getName() != null) { + sb.append(Util.encodeBitstreamName(bitstream.getName(), "UTF-8")); + } + } catch (UnsupportedEncodingException uee) { + // just ignore it, we don't have to have a pretty + // name at the end of the URL because the sequence id will + // locate it. However it means that links in this file might + // not work.... + } + + sb.append("?sequence=").append(bitstream.getSequenceID()); + } + return sb.toString(); + } + + private String getItemInfo(final Item item) { + StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); + sb.append("/handle/").append(item.getHandle()); + + return sb.toString(); + } + + + private static void processUrl(Context c, String urlStr) throws IOException, SQLException { + log.debug("Prepared to send url to tracker URL: " + urlStr); + System.out.println(urlStr); + URLConnection conn; + + try { + // Send data + URL url = new URL(urlStr); + conn = url.openConnection(); + + if (((HttpURLConnection) conn).getResponseCode() != 200) { + ExportUsageEventListener.logfailed(c, urlStr); + } else if (log.isDebugEnabled()) { + log.debug("Successfully posted " + urlStr + " on " + new Date()); + } + } catch (Exception e) { + log.error("Failed to send url to tracker URL: " + urlStr); + ExportUsageEventListener.logfailed(c, urlStr); + } + } + + private static void tryReprocessFailed(Context context, OpenURLTracker tracker) throws SQLException { + boolean success = false; + URLConnection conn; + try { + URL url = new URL(tracker.getUrl()); + conn = url.openConnection(); + + if (((HttpURLConnection) conn).getResponseCode() == HttpURLConnection.HTTP_OK) { + success = true; + } + } catch (Exception e) { + success = false; + } finally { + if (success) { + OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService() + .remove(context, tracker); + // If the tracker was able to post successfully, we remove it from the database + log.info("Successfully posted " + tracker.getUrl() + " from " + tracker.getUploadDate()); + } else { + // Still no luck - write an error msg but keep the entry in the table for future executions + log.error("Failed attempt from " + tracker.getUrl() + " originating from " + tracker.getUploadDate()); + } + } + } + + public static void reprocessFailedQueue(Context context) throws SQLException { + Context c = new Context(); + OpenURLTrackerLoggerServiceFactory instance = OpenURLTrackerLoggerServiceFactory.getInstance(); + if (instance == null) { + log.error("Error retrieving the \"OpenURLTrackerLoggerServiceFactory\" instance, aborting the processing"); + return; + } + OpenURLTrackerLoggerService openUrlTrackerLoggerService = instance.getOpenUrlTrackerLoggerService(); + if (openUrlTrackerLoggerService == null) { + log.error("Error retrieving the \"openUrlTrackerLoggerService\" instance, aborting the processing"); + return; + } + List openURLTrackers = openUrlTrackerLoggerService.findAll(c); + for (OpenURLTracker openURLTracker : openURLTrackers) { + ExportUsageEventListener.tryReprocessFailed(context, openURLTracker); + } + + try { + c.abort(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + public static void logfailed(Context context, String url) throws SQLException { + Date now = new Date(); + if (url.equals("")) { + return; + } + OpenURLTrackerLoggerService service = OpenURLTrackerLoggerServiceFactory.getInstance() + .getOpenUrlTrackerLoggerService(); + OpenURLTracker tracker = service.create(context); + tracker.setUploadDate(now); + tracker.setUrl(url); + // TODO service tracker update + } + + private static MetadataField resolveConfigPropertyToMetadataField(Context context, String fieldName) + throws SQLException { + String metadataField = configurationService.getProperty("stats." + fieldName); + if (metadataField != null && 0 < metadataField.trim().length()) { + metadataField = metadataField.trim(); + MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); + return metadataFieldService + .findByElement(context, metadataField.split("\\.")[0], metadataField.split("\\.")[1], + metadataField.split("\\.").length == 2 ? null : metadataField.split("\\.")[2]); + } + return null; + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java new file mode 100644 index 0000000000..ba23d9e1c6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.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.statistics.export; + +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.dspace.core.ReloadableEntity; +import org.hibernate.proxy.HibernateProxyHelper; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +@Entity +@Table(name = "OpenUrlTracker") +public class OpenURLTracker implements ReloadableEntity { + + @Id + @Column(name = "tracker_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "openurltracker_seq") + @SequenceGenerator(name = "openurltracker_seq", sequenceName = "openurltracker_seq", allocationSize = 1) + private Integer id; + + @Column(name = "tracker_url", length = 1000) + private String url; + + @Column(name = "uploaddate") + @Temporal(TemporalType.DATE) + private Date uploadDate; + + protected OpenURLTracker() { + + } + + @Override + public Integer getID() { + return id; + } + + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Date getUploadDate() { + return uploadDate; + } + + public void setUploadDate(Date uploadDate) { + this.uploadDate = uploadDate; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o); + if (getClass() != objClass) { + return false; + } + + final OpenURLTracker that = (OpenURLTracker) o; + if (this.getID() != that.getID()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hash = 8; + hash = 74 * hash + this.getID(); + return hash; + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java new file mode 100644 index 0000000000..c957b4c7a0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java @@ -0,0 +1,41 @@ +/** + * 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.statistics.export; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.statistics.export.dao.OpenURLTrackerDAO; +import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +public class OpenURLTrackerLoggerServiceImpl implements OpenURLTrackerLoggerService { + + @Autowired(required = true) + protected OpenURLTrackerDAO openURLTrackerDAO; + + @Override + public void remove(Context context, OpenURLTracker openURLTracker) throws SQLException { + openURLTrackerDAO.delete(context, openURLTracker); + } + + @Override + public List findAll(Context context) throws SQLException { + return openURLTrackerDAO.findAll(context, OpenURLTracker.class); + } + + @Override + public OpenURLTracker create(Context context) throws SQLException { + OpenURLTracker openURLTracker = openURLTrackerDAO.create(context, new OpenURLTracker()); + return openURLTracker; + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java new file mode 100644 index 0000000000..6427c27433 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.export; + +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceRunnable; + + +public class RetryOpenUrlTracker extends DSpaceRunnable { + private static final Logger log = Logger.getLogger(RetryOpenUrlTracker.class); + + private Context context = null; + private String lineToAdd = null; + private boolean help = false; + + public void internalRun() throws Exception { + if (help) { + printHelp(); + return; + } + context.turnOffAuthorisationSystem(); + + if (StringUtils.isNotBlank(lineToAdd)) { + ExportUsageEventListener.logfailed(context, lineToAdd); + log.info("Created dummy entry in OpenUrlTracker with URL: " + lineToAdd); + } else { + ExportUsageEventListener.reprocessFailedQueue(context); + } + context.restoreAuthSystemState(); + try { + context.complete(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + public void setup() throws ParseException { + context = new Context(); + + if (commandLine.hasOption('h')) { + help = true; + } + if (commandLine.hasOption('a')) { + lineToAdd = commandLine.getOptionValue('a'); + } + } + + private RetryOpenUrlTracker() { + Options options = constructOptions(); + this.options = options; + } + + private Options constructOptions() { + Options options = new Options(); + + options.addOption("a", true, "Add a new \"failed\" row to the table with a url (test purposes only)"); + options.getOption("a").setType(String.class); + + options.addOption("h", "help", false, "print this help message"); + options.getOption("h").setType(boolean.class); + + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java b/dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java new file mode 100644 index 0000000000..1f1211470e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java @@ -0,0 +1,289 @@ +/** + * 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.statistics.export; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.statistics.factory.StatisticsServiceFactory; +import org.dspace.statistics.util.IPTable; +import org.dspace.statistics.util.SpiderDetectorService; + +/** + * SpiderDetector is used to find IP's that are spiders... + * In future someone may add UserAgents and Host Domains + * to the detection criteria here. + * + * @author kevinvandevelde at atmire.com + * @author ben at atmire.com + * @author Mark Diggory (mdiggory at atmire.com) + * @author Kevin Van Ransbeeck at atmire.com + */ +public class SpiderDetector { + + private static final Logger log = Logger.getLogger(SpiderDetector.class); + + //Service where all methods get delegated to, this is instantiated by a spring-bean defined in core-services.xml + private static SpiderDetectorService spiderDetectorService = StatisticsServiceFactory.getInstance() + .getSpiderDetectorService(); + + private SpiderDetector() { } + + /** + * Sparse HAshTable structure to hold IP Address Ranges. + */ + private static IPTable table = null; + private static Set spidersRegex = Collections.synchronizedSet(new HashSet()); + private static Set spidersMatched = null; + + /** + * Utility method which Reads the ip addresses out a file & returns them in a Set + * + * @param spiderIpFile the location of our spider file + * @return a vector full of ip's + * @throws IOException could not happen since we check the file be4 we use it + */ + public static Set readIpAddresses(File spiderIpFile) throws IOException { + Set ips = new HashSet<>(); + + if (!spiderIpFile.exists() || !spiderIpFile.isFile()) { + return ips; + } + + //Read our file & get all them ip's + try (BufferedReader in = new BufferedReader(new FileReader(spiderIpFile))) { + String line; + while ((line = in.readLine()) != null) { + if (!line.startsWith("#")) { + line = line.trim(); + + if (!line.equals("") && !Character.isDigit(line.charAt(0))) { + // is a hostname + // add this functionality later... + } else if (!line.equals("")) { + ips.add(line); + // is full v4 ip (too tired to deal with v6)... + } + } else { + // ua.add(line.replaceFirst("#","").replaceFirst("UA","").trim()); + // ... add this functionality later + } + } + } + return ips; + } + + /** + * Get an immutable Set representing all the Spider Addresses here + * + * @return Set setOfIpAddresses + */ + public static Set getSpiderIpAddresses() { + loadSpiderIpAddresses(); + return table.toSet(); + } + + /* + private loader to populate the table from files. + */ + + private static synchronized void loadSpiderIpAddresses() { + if (table == null) { + table = new IPTable(); + + String filePath = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir"); + try { + File spidersDir = new File(filePath, "config/spiders"); + + if (spidersDir.exists() && spidersDir.isDirectory()) { + for (File file : spidersDir.listFiles()) { + for (String ip : readIpAddresses(file)) { + table.add(ip); + } + log.info("Loaded Spider IP file: " + file); + } + } else { + log.info("No spider file loaded"); + } + } catch (Exception e) { + log.error("Error Loading Spiders:" + e.getMessage(), e); + } + } + } + + /** + * Static Service Method for testing spiders against existing spider files. + *

+ * In the future this will be extended to support User Agent and + * domain Name detection. + *

+ * In future spiders HashSet may be optimized as byte offset array to + * improve performance and memory footprint further. + * + * @param request + * @return true|false if the request was detected to be from a spider + */ + public static boolean isSpider(HttpServletRequest request) { + /* + * 1) If the IP address matches the spider IP addresses (this is the current implementation) + */ + boolean checkSpidersIP = DSpaceServicesFactory.getInstance().getConfigurationService() + .getPropertyAsType("stats.spider.ipmatch.enabled", true, true); + if (checkSpidersIP) { + if (StatisticsServiceFactory.getInstance().getSolrLoggerService().isUseProxies() && request + .getHeader("X-Forwarded-For") != null) { + /* This header is a comma delimited list */ + for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { + if (isSpider(xfip)) { + log.debug("spider.ipmatch"); + return true; + } + } + } else if (isSpider(request.getRemoteAddr())) { + log.debug("spider.ipmatch"); + return true; + } + } + /* + * 2) if the user-agent header is empty - DISABLED BY DEFAULT - + */ + boolean checkSpidersEmptyAgent = DSpaceServicesFactory.getInstance().getConfigurationService() + .getPropertyAsType("stats.spider.agentempty.enabled", + false, true); + if (checkSpidersEmptyAgent) { + if (request.getHeader("user-agent") == null || request.getHeader("user-agent").length() == 0) { + log.debug("spider.agentempty"); + return true; + } + } + /* + * 3) if the user-agent corresponds to one of the regexes at http://www.projectcounter + * .org/r4/COUNTER_robot_txt_list_Jan_2011.txt + */ + boolean checkSpidersTxt = DSpaceServicesFactory.getInstance().getConfigurationService() + .getPropertyAsType("stats.spider.agentregex.enabled", true, + true); + if (checkSpidersTxt) { + String userAgent = request.getHeader("user-agent"); + + if (userAgent != null && !userAgent.equals("")) { + return isSpiderRegex(userAgent); + } + } + return false; + } + + /** + * Check individual IP is a spider. + * + * @param ip + * @return if is spider IP + */ + public static boolean isSpider(String ip) { + if (table == null) { + spiderDetectorService.loadSpiderIpAddresses(); + } + + try { + if (table.contains(ip)) { + return true; + } + } catch (Exception e) { + return false; + } + + return false; + } + + /** + * Checks the user-agent string vs a set of known regexes from spiders + * A second Set is kept for fast-matching. + * If a user-agent is matched once, it is added to this set with "known agents". + * If this user-agent comes back later, we can do a quick lookup in this set, + * instead of having to loop over the entire set with regexes again. + * + * @param userAgent String + * @return true if the user-agent matches a regex + */ + public static boolean isSpiderRegex(String userAgent) { + if (spidersMatched != null && spidersMatched.contains(userAgent)) { + log.debug("spider.agentregex"); + return true; + } else { + synchronized (spidersRegex) { + if (spidersRegex.isEmpty()) { + loadSpiderRegexFromFile(); + } + } + + if (spidersRegex != null) { + for (Object regex : spidersRegex.toArray()) { + Matcher matcher = ((Pattern) regex).matcher(userAgent); + if (matcher.find()) { + if (spidersMatched == null) { + spidersMatched = new HashSet<>(); + } + if (spidersMatched.size() >= 100) { + spidersMatched.clear(); + } + spidersMatched.add(userAgent); + log.debug("spider.agentregex"); + return true; + } + } + } + return false; + } + } + + /** + * Populate static Set spidersRegex from local txt file. + * Original file downloaded from http://www.projectcounter.org/r4/COUNTER_robot_txt_list_Jan_2011.txt during build + */ + public static void loadSpiderRegexFromFile() { + String spidersTxt = DSpaceServicesFactory.getInstance().getConfigurationService() + .getPropertyAsType("stats.spider.agentregex.regexfile", String.class); + DataInputStream in = null; + try { + FileInputStream fstream = new FileInputStream(spidersTxt); + in = new DataInputStream(fstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + String strLine; + while ((strLine = br.readLine()) != null) { + spidersRegex.add(Pattern.compile(strLine, Pattern.CASE_INSENSITIVE)); + } + log.info("Loaded Spider Regex file: " + spidersTxt); + } catch (FileNotFoundException e) { + log.error("File with spiders regex not found @ " + spidersTxt); + } catch (IOException e) { + log.error("Could not read from file " + spidersTxt); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("Could not close file " + spidersTxt); + } + } + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java b/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java new file mode 100644 index 0000000000..0aa05065be --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java @@ -0,0 +1,19 @@ +/** + * 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.statistics.export.dao; + +import org.dspace.core.GenericDAO; +import org.dspace.statistics.export.OpenURLTracker; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +public interface OpenURLTrackerDAO extends GenericDAO { + + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java new file mode 100644 index 0000000000..fb08d572f0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.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.statistics.export.dao.impl; + +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.statistics.export.OpenURLTracker; +import org.dspace.statistics.export.dao.OpenURLTrackerDAO; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +public class OpenURLTrackerDAOImpl extends AbstractHibernateDAO implements OpenURLTrackerDAO { + + + protected OpenURLTrackerDAOImpl() { + super(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java new file mode 100644 index 0000000000..6668b8ee48 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.export.factory; + +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +public abstract class OpenURLTrackerLoggerServiceFactory { + + public abstract OpenURLTrackerLoggerService getOpenUrlTrackerLoggerService(); + + public static OpenURLTrackerLoggerServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("openURLTrackerLoggerServiceFactory", + OpenURLTrackerLoggerServiceFactory.class); + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java new file mode 100644 index 0000000000..9a04bf20d3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.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.statistics.export.factory; + +import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +public class OpenURLTrackerLoggerServiceFactoryImpl extends OpenURLTrackerLoggerServiceFactory { + + @Autowired(required = true) + private OpenURLTrackerLoggerService openURLTrackerLoggerService; + + @Override + public OpenURLTrackerLoggerService getOpenUrlTrackerLoggerService() { + return openURLTrackerLoggerService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java new file mode 100644 index 0000000000..6b910f7120 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java @@ -0,0 +1,26 @@ +/** + * 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.statistics.export.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.statistics.export.OpenURLTracker; + +/** + * Created by jonas - jonas@atmire.com on 09/02/17. + */ +public interface OpenURLTrackerLoggerService { + + void remove(Context context, OpenURLTracker openURLTracker) throws SQLException; + + List findAll(Context context) throws SQLException; + + OpenURLTracker create(Context context) throws SQLException; +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.01.08__Statistics-harvester.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.01.08__Statistics-harvester.sql new file mode 100644 index 0000000000..83fe1dcbb5 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.01.08__Statistics-harvester.sql @@ -0,0 +1,29 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +-- =============================================================== +-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +-- +-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED +-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. +-- http://flywaydb.org/ +-- =============================================================== + +------------------------------------------------------------- +-- This will create the setup for the IRUS statistics harvester +------------------------------------------------------------- + +CREATE SEQUENCE openurltracker_seq; + +CREATE TABLE OpenUrlTracker +( + tracker_id INTEGER, + tracker_url VARCHAR(1000), + uploaddate DATE, + CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) +); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__Statistics-harvester.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__Statistics-harvester.sql new file mode 100644 index 0000000000..761361ca7d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__Statistics-harvester.sql @@ -0,0 +1,29 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +-- =============================================================== +-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +-- +-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED +-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. +-- http://flywaydb.org/ +-- =============================================================== + +------------------------------------------------------------- +-- This will create the setup for the IRUS statistics harvester +------------------------------------------------------------- + +CREATE SEQUENCE openurltracker_seq; + +CREATE TABLE OpenUrlTracker +( + tracker_id NUMBER, + tracker_url VARCHAR2(1000), + uploaddate DATE, + CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) +); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql new file mode 100644 index 0000000000..761361ca7d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql @@ -0,0 +1,29 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +-- =============================================================== +-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +-- +-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED +-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. +-- http://flywaydb.org/ +-- =============================================================== + +------------------------------------------------------------- +-- This will create the setup for the IRUS statistics harvester +------------------------------------------------------------- + +CREATE SEQUENCE openurltracker_seq; + +CREATE TABLE OpenUrlTracker +( + tracker_id NUMBER, + tracker_url VARCHAR2(1000), + uploaddate DATE, + CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) +); \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index bce46aaba6..063773fc36 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -2015,3 +2015,4 @@ include = ${module_dir}/translator.cfg include = ${module_dir}/usage-statistics.cfg include = ${module_dir}/versioning.cfg include = ${module_dir}/workflow.cfg +include = ${module_dir}/stats.cfg diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 8b8d43a340..39f5a11378 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -82,5 +82,7 @@ + + diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index 3c4f205c2a..12a5cee8ca 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -208,6 +208,13 @@ org.dspace.administer.RegistryLoader + + retry-tracker + Retry all failed commits to the OpenURLTracker + + org.dspace.statistics.export.RetryOpenUrlTracker + + solr-export-statistics Export usage statistics data from Solr for back-up purposes diff --git a/dspace/config/log4j2.xml b/dspace/config/log4j2.xml index 98a0300125..eb2f5d2cd8 100644 --- a/dspace/config/log4j2.xml +++ b/dspace/config/log4j2.xml @@ -70,6 +70,8 @@ additivity='false'> + + # Block services logging except on exceptions + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index de404cb971..77ab092d24 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -47,4 +47,6 @@ + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index e7e28884c2..c48ed1db92 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -117,6 +117,7 @@ + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 8284edc05a..bbc9c346e0 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -9,5 +9,9 @@ + + + + diff --git a/dspace/config/spring/rest/event-service-listeners.xml b/dspace/config/spring/rest/event-service-listeners.xml index 6d62e60f17..7c22fc1d72 100644 --- a/dspace/config/spring/rest/event-service-listeners.xml +++ b/dspace/config/spring/rest/event-service-listeners.xml @@ -21,4 +21,9 @@ + + + + + \ No newline at end of file diff --git a/dspace/src/main/config/build.xml b/dspace/src/main/config/build.xml index 98414f0cdd..ba12cf85c5 100644 --- a/dspace/src/main/config/build.xml +++ b/dspace/src/main/config/build.xml @@ -118,6 +118,7 @@ Common usage: + @@ -180,6 +181,7 @@ Common usage: + @@ -834,6 +836,8 @@ Common usage: + + ==================================================================== The DSpace code has been installed. @@ -920,4 +924,22 @@ You may manually install this file by following these steps: + + + Downloading: https://raw.githubusercontent.com/atmire/COUNTER-Robots/master/generated/COUNTER_Robots_list.txt + + + + + + + + + + + + + + + From c6ce9aaf104193df115b20ec51c717856055e1f6 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 9 Jan 2020 13:45:47 +0100 Subject: [PATCH 014/749] 67668: Fix to the postgres sql file --- .../postgres/V7.0_2020.01.08__Statistics-harvester.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql index 761361ca7d..e0406b2eba 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql @@ -22,7 +22,7 @@ CREATE SEQUENCE openurltracker_seq; CREATE TABLE OpenUrlTracker ( - tracker_id NUMBER, + tracker_id INTEGER, tracker_url VARCHAR2(1000), uploaddate DATE, CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) From a1009276c30f75d030ee83c4a7fee919280ee8ad Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 9 Jan 2020 13:58:31 +0100 Subject: [PATCH 015/749] 67668: psql fix --- .../postgres/V7.0_2020.01.08__Statistics-harvester.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql index e0406b2eba..83fe1dcbb5 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql @@ -23,7 +23,7 @@ CREATE SEQUENCE openurltracker_seq; CREATE TABLE OpenUrlTracker ( tracker_id INTEGER, - tracker_url VARCHAR2(1000), + tracker_url VARCHAR(1000), uploaddate DATE, CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) ); \ No newline at end of file From 24f5cf0896632c29e0631dfa0909fa0473f5ba1e Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 9 Jan 2020 15:03:42 +0100 Subject: [PATCH 016/749] Fixed tests in CSVMetadataImportReferenceTest and script bean issues --- .../dspace/app/bulkedit/MetadataImport.java | 515 +++++++++--------- .../org/dspace/scripts/DSpaceRunnable.java | 24 +- .../org/dspace/scripts/ScriptServiceImpl.java | 12 +- .../config/spring/api/scripts.xml | 9 +- .../app/rest/ScriptRestRepositoryIT.java | 16 +- dspace/config/spring/api/scripts.xml | 9 +- dspace/config/spring/rest/scripts.xml | 3 +- 7 files changed, 289 insertions(+), 299 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 6fd4be68fe..939c0d1de6 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -59,6 +59,7 @@ import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -374,275 +375,271 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { public List runImport(boolean change, boolean useWorkflow, boolean workflowNotify, - boolean useTemplate) throws MetadataImportException { + boolean useTemplate) + throws MetadataImportException, SQLException, AuthorizeException, WorkflowException, IOException { // Store the changes ArrayList changes = new ArrayList(); // Make the changes - try { - Context.Mode originalMode = c.getCurrentMode(); - c.setMode(Context.Mode.BATCH_EDIT); + Context.Mode originalMode = c.getCurrentMode(); + c.setMode(Context.Mode.BATCH_EDIT); - // Process each change - rowCount = 1; - for (DSpaceCSVLine line : toImport) { - // Resolve target references to other items - populateRefAndRowMap(line, line.getID()); - line = resolveEntityRefs(line); - // Get the DSpace item to compare with - UUID id = line.getID(); + // Process each change + rowCount = 1; + for (DSpaceCSVLine line : toImport) { + // Resolve target references to other items + populateRefAndRowMap(line, line.getID()); + line = resolveEntityRefs(line); + // Get the DSpace item to compare with + UUID id = line.getID(); - // Is there an action column? - if (csv.hasActions() && (!"".equals(line.getAction())) && (id == null)) { - throw new MetadataImportException("'action' not allowed for new items!"); - } - - WorkspaceItem wsItem = null; - WorkflowItem wfItem = null; - Item item = null; - - // Is this an existing item? - if (id != null) { - // Get the item - item = itemService.find(c, id); - if (item == null) { - throw new MetadataImportException("Unknown item ID " + id); - } - - // Record changes - BulkEditChange whatHasChanged = new BulkEditChange(item); - - // Has it moved collection? - List collections = line.get("collection"); - if (collections != null) { - // Sanity check we're not orphaning it - if (collections.size() == 0) { - throw new MetadataImportException("Missing collection from item " + item.getHandle()); - } - List actualCollections = item.getCollections(); - compare(item, collections, actualCollections, whatHasChanged, change); - } - - // Iterate through each metadata element in the csv line - for (String md : line.keys()) { - // Get the values we already have - if (!"id".equals(md)) { - // Get the values from the CSV - String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); - // Remove authority unless the md is not authority controlled - if (!isAuthorityControlledField(md)) { - for (int i = 0; i < fromCSV.length; i++) { - int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); - if (pos > -1) { - fromCSV[i] = fromCSV[i].substring(0, pos); - } - } - } - // Compare - compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line); - } - } - - if (csv.hasActions()) { - // Perform the action - String action = line.getAction(); - if ("".equals(action)) { - // Do nothing - } else if ("expunge".equals(action)) { - // Does the configuration allow deletes? - if (!ConfigurationManager.getBooleanProperty("bulkedit", "allowexpunge", false)) { - throw new MetadataImportException("'expunge' action denied by configuration"); - } - - // Remove the item - - if (change) { - itemService.delete(c, item); - } - - whatHasChanged.setDeleted(); - } else if ("withdraw".equals(action)) { - // Withdraw the item - if (!item.isWithdrawn()) { - if (change) { - itemService.withdraw(c, item); - } - whatHasChanged.setWithdrawn(); - } - } else if ("reinstate".equals(action)) { - // Reinstate the item - if (item.isWithdrawn()) { - if (change) { - itemService.reinstate(c, item); - } - whatHasChanged.setReinstated(); - } - } else { - // Unknown action! - throw new MetadataImportException("Unknown action: " + action); - } - } - - // Only record if changes have been made - if (whatHasChanged.hasChanges()) { - changes.add(whatHasChanged); - } - } else { - // This is marked as a new item, so no need to compare - - // First check a user is set, otherwise this can't happen - if (c.getCurrentUser() == null) { - throw new MetadataImportException( - "When adding new items, a user must be specified with the -e option"); - } - - // Iterate through each metadata element in the csv line - BulkEditChange whatHasChanged = new BulkEditChange(); - for (String md : line.keys()) { - // Get the values we already have - if (!"id".equals(md) && !"rowName".equals(md)) { - // Get the values from the CSV - String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); - - // Remove authority unless the md is not authority controlled - if (!isAuthorityControlledField(md)) { - for (int i = 0; i < fromCSV.length; i++) { - int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); - if (pos > -1) { - fromCSV[i] = fromCSV[i].substring(0, pos); - } - } - } - - // Add all the values from the CSV line - add(fromCSV, md, whatHasChanged); - } - } - - // Check it has an owning collection - List collections = line.get("collection"); - if (collections == null) { - throw new MetadataImportException( - "New items must have a 'collection' assigned in the form of a handle"); - } - - // Check collections are really collections - ArrayList check = new ArrayList(); - Collection collection; - for (String handle : collections) { - try { - // Resolve the handle to the collection - collection = (Collection) handleService.resolveToObject(c, handle); - - // Check it resolved OK - if (collection == null) { - throw new MetadataImportException( - "'" + handle + "' is not a Collection! You must specify a valid collection for " + - "new items"); - } - - // Check for duplicate - if (check.contains(collection)) { - throw new MetadataImportException( - "Duplicate collection assignment detected in new item! " + handle); - } else { - check.add(collection); - } - } catch (Exception ex) { - throw new MetadataImportException( - "'" + handle + "' is not a Collection! You must specify a valid collection for new " + - "items", - ex); - } - } - - // Record the addition to collections - boolean first = true; - for (String handle : collections) { - Collection extra = (Collection) handleService.resolveToObject(c, handle); - if (first) { - whatHasChanged.setOwningCollection(extra); - } else { - whatHasChanged.registerNewMappedCollection(extra); - } - first = false; - } - - // Create the new item? - if (change) { - // Create the item - String collectionHandle = line.get("collection").get(0); - collection = (Collection) handleService.resolveToObject(c, collectionHandle); - wsItem = workspaceItemService.create(c, collection, useTemplate); - item = wsItem.getItem(); - - // Add the metadata to the item - for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { - if (!StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { - itemService.addMetadata(c, item, dcv.getSchema(), - dcv.getElement(), - dcv.getQualifier(), - dcv.getLanguage(), - dcv.getValue(), - dcv.getAuthority(), - dcv.getConfidence()); - } - } - //Add relations after all metadata has been processed - for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { - if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { - addRelationship(c, item, dcv.getElement(), dcv.getValue()); - } - } - - - // Should the workflow be used? - if (useWorkflow) { - WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); - if (workflowNotify) { - wfItem = workflowService.start(c, wsItem); - } else { - wfItem = workflowService.startWithoutNotify(c, wsItem); - } - } else { - // Install the item - installItemService.installItem(c, wsItem); - } - - // Add to extra collections - if (line.get("collection").size() > 0) { - for (int i = 1; i < collections.size(); i++) { - String handle = collections.get(i); - Collection extra = (Collection) handleService.resolveToObject(c, handle); - collectionService.addItem(c, extra, item); - } - } - - whatHasChanged.setItem(item); - } - - // Record the changes - changes.add(whatHasChanged); - } - - if (change) { - //only clear cache if changes have been made. - c.uncacheEntity(wsItem); - c.uncacheEntity(wfItem); - c.uncacheEntity(item); - } - populateRefAndRowMap(line, item == null ? null : item.getID()); - // keep track of current rows processed - rowCount++; + // Is there an action column? + if (csv.hasActions() && (!"".equals(line.getAction())) && (id == null)) { + throw new MetadataImportException("'action' not allowed for new items!"); } - c.setMode(originalMode); - } catch (MetadataImportException mie) { - throw mie; - } catch (Exception e) { - e.printStackTrace(); + WorkspaceItem wsItem = null; + WorkflowItem wfItem = null; + Item item = null; + + // Is this an existing item? + if (id != null) { + // Get the item + item = itemService.find(c, id); + if (item == null) { + throw new MetadataImportException("Unknown item ID " + id); + } + + // Record changes + BulkEditChange whatHasChanged = new BulkEditChange(item); + + // Has it moved collection? + List collections = line.get("collection"); + if (collections != null) { + // Sanity check we're not orphaning it + if (collections.size() == 0) { + throw new MetadataImportException("Missing collection from item " + item.getHandle()); + } + List actualCollections = item.getCollections(); + compare(item, collections, actualCollections, whatHasChanged, change); + } + + // Iterate through each metadata element in the csv line + for (String md : line.keys()) { + // Get the values we already have + if (!"id".equals(md)) { + // Get the values from the CSV + String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); + // Remove authority unless the md is not authority controlled + if (!isAuthorityControlledField(md)) { + for (int i = 0; i < fromCSV.length; i++) { + int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); + if (pos > -1) { + fromCSV[i] = fromCSV[i].substring(0, pos); + } + } + } + // Compare + compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line); + } + } + + if (csv.hasActions()) { + // Perform the action + String action = line.getAction(); + if ("".equals(action)) { + // Do nothing + } else if ("expunge".equals(action)) { + // Does the configuration allow deletes? + if (!ConfigurationManager.getBooleanProperty("bulkedit", "allowexpunge", false)) { + throw new MetadataImportException("'expunge' action denied by configuration"); + } + + // Remove the item + + if (change) { + itemService.delete(c, item); + } + + whatHasChanged.setDeleted(); + } else if ("withdraw".equals(action)) { + // Withdraw the item + if (!item.isWithdrawn()) { + if (change) { + itemService.withdraw(c, item); + } + whatHasChanged.setWithdrawn(); + } + } else if ("reinstate".equals(action)) { + // Reinstate the item + if (item.isWithdrawn()) { + if (change) { + itemService.reinstate(c, item); + } + whatHasChanged.setReinstated(); + } + } else { + // Unknown action! + throw new MetadataImportException("Unknown action: " + action); + } + } + + // Only record if changes have been made + if (whatHasChanged.hasChanges()) { + changes.add(whatHasChanged); + } + } else { + // This is marked as a new item, so no need to compare + + // First check a user is set, otherwise this can't happen + if (c.getCurrentUser() == null) { + throw new MetadataImportException( + "When adding new items, a user must be specified with the -e option"); + } + + // Iterate through each metadata element in the csv line + BulkEditChange whatHasChanged = new BulkEditChange(); + for (String md : line.keys()) { + // Get the values we already have + if (!"id".equals(md) && !"rowName".equals(md)) { + // Get the values from the CSV + String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); + + // Remove authority unless the md is not authority controlled + if (!isAuthorityControlledField(md)) { + for (int i = 0; i < fromCSV.length; i++) { + int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); + if (pos > -1) { + fromCSV[i] = fromCSV[i].substring(0, pos); + } + } + } + + // Add all the values from the CSV line + add(fromCSV, md, whatHasChanged); + } + } + + // Check it has an owning collection + List collections = line.get("collection"); + if (collections == null) { + throw new MetadataImportException( + "New items must have a 'collection' assigned in the form of a handle"); + } + + // Check collections are really collections + ArrayList check = new ArrayList(); + Collection collection; + for (String handle : collections) { + try { + // Resolve the handle to the collection + collection = (Collection) handleService.resolveToObject(c, handle); + + // Check it resolved OK + if (collection == null) { + throw new MetadataImportException( + "'" + handle + "' is not a Collection! You must specify a valid collection for " + + "new items"); + } + + // Check for duplicate + if (check.contains(collection)) { + throw new MetadataImportException( + "Duplicate collection assignment detected in new item! " + handle); + } else { + check.add(collection); + } + } catch (Exception ex) { + throw new MetadataImportException( + "'" + handle + "' is not a Collection! You must specify a valid collection for new " + + "items", + ex); + } + } + + // Record the addition to collections + boolean first = true; + for (String handle : collections) { + Collection extra = (Collection) handleService.resolveToObject(c, handle); + if (first) { + whatHasChanged.setOwningCollection(extra); + } else { + whatHasChanged.registerNewMappedCollection(extra); + } + first = false; + } + + // Create the new item? + if (change) { + // Create the item + String collectionHandle = line.get("collection").get(0); + collection = (Collection) handleService.resolveToObject(c, collectionHandle); + wsItem = workspaceItemService.create(c, collection, useTemplate); + item = wsItem.getItem(); + + // Add the metadata to the item + for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { + if (!StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { + itemService.addMetadata(c, item, dcv.getSchema(), + dcv.getElement(), + dcv.getQualifier(), + dcv.getLanguage(), + dcv.getValue(), + dcv.getAuthority(), + dcv.getConfidence()); + } + } + //Add relations after all metadata has been processed + for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { + if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { + addRelationship(c, item, dcv.getElement(), dcv.getValue()); + } + } + + + // Should the workflow be used? + if (useWorkflow) { + WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); + if (workflowNotify) { + wfItem = workflowService.start(c, wsItem); + } else { + wfItem = workflowService.startWithoutNotify(c, wsItem); + } + } else { + // Install the item + installItemService.installItem(c, wsItem); + } + + // Add to extra collections + if (line.get("collection").size() > 0) { + for (int i = 1; i < collections.size(); i++) { + String handle = collections.get(i); + Collection extra = (Collection) handleService.resolveToObject(c, handle); + collectionService.addItem(c, extra, item); + } + } + + whatHasChanged.setItem(item); + } + + // Record the changes + changes.add(whatHasChanged); + } + + if (change) { + //only clear cache if changes have been made. + c.uncacheEntity(wsItem); + c.uncacheEntity(wfItem); + c.uncacheEntity(item); + } + populateRefAndRowMap(line, item == null ? null : item.getID()); + // keep track of current rows processed + rowCount++; } + c.setMode(originalMode); + + // Return the changes if (!change) { validateExpressedRelations(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 8d8d8caa60..cad875a53b 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -20,6 +20,7 @@ import org.apache.commons.cli.ParseException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; @@ -28,7 +29,7 @@ import org.springframework.beans.factory.annotation.Required; * it provides the basic variables to be hold by the script as well as the means to initialize, parse and run the script * Every DSpaceRunnable that is implemented in this way should be defined in the scripts.xml config file as a bean */ -public abstract class DSpaceRunnable implements Runnable { +public abstract class DSpaceRunnable implements Runnable, BeanNameAware { /** * The name of the script @@ -55,15 +56,6 @@ public abstract class DSpaceRunnable implements Runnable { @Autowired private AuthorizeService authorizeService; - public String getName() { - return name; - } - - @Required - public void setName(String name) { - this.name = name; - } - public String getDescription() { return description; } @@ -174,4 +166,16 @@ public abstract class DSpaceRunnable implements Runnable { } return false; } + + public void setBeanName(String beanName) { + this.name = beanName; + } + + /** + * Generic getter for the name + * @return the name value of this DSpaceRunnable + */ + public String getName() { + return name; + } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index e2a6acf3a8..7f1f0b83e6 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -10,8 +10,8 @@ package org.dspace.scripts; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; +import org.dspace.kernel.ServiceManager; import org.dspace.scripts.service.ScriptService; import org.springframework.beans.factory.annotation.Autowired; @@ -19,21 +19,17 @@ import org.springframework.beans.factory.annotation.Autowired; * The implementation for the {@link ScriptService} */ public class ScriptServiceImpl implements ScriptService { - @Autowired - private List dSpaceRunnables; + private ServiceManager serviceManager; @Override public DSpaceRunnable getScriptForName(String name) { - return dSpaceRunnables.stream() - .filter(dSpaceRunnable -> StringUtils.equalsIgnoreCase(dSpaceRunnable.getName(), name)) - .findFirst() - .orElse(null); + return serviceManager.getServiceByName(name, DSpaceRunnable.class); } @Override public List getDSpaceRunnables(Context context) { - return dSpaceRunnables.stream().filter( + return serviceManager.getServicesByType(DSpaceRunnable.class).stream().filter( dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); } } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index c62fc959ba..8072292b28 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -4,18 +4,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - + - - + - - + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 22f998148b..96a6d3034d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -86,28 +86,28 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts").param("size", "1")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.scripts", hasItem( + .andExpect(jsonPath("$._embedded.scripts", Matchers.not(Matchers.hasItem( ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), dSpaceRunnableList.get(0).getDescription()) - ))) - .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( + )))) + .andExpect(jsonPath("$._embedded.scripts", hasItem( ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), dSpaceRunnableList.get(1).getDescription()) - )))) + ))) .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 1)))); getClient(token).perform(get("/api/system/scripts").param("size", "1").param("page", "1")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( + .andExpect(jsonPath("$._embedded.scripts", hasItem( ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), dSpaceRunnableList.get(0).getDescription()) - )))) - .andExpect(jsonPath("$._embedded.scripts", hasItem( + ))) + .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), dSpaceRunnableList.get(1).getDescription()) - ))) + )))) .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(1, 1)))); } diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 81acb052c3..1efd83d9db 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -4,18 +4,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - + - - + - - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index dcdd55ebee..2a2070f585 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -8,8 +8,7 @@ - - + \ No newline at end of file From 182a8f01f373d25031f9bc1a71c51e8ba8ddbae4 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 15 Jan 2020 13:49:31 +0100 Subject: [PATCH 017/749] Applied feedback to the MetadataExport and MetadataImport scripts --- .../dspace/app/bulkedit/MetadataExport.java | 14 ++- .../dspace/app/bulkedit/MetadataImport.java | 99 +++++++++++-------- .../app/bulkedit/MetadataImportCLI.java | 4 + ...> MetadataDSpaceCsvExportServiceImpl.java} | 4 +- ...va => MetadataDSpaceCsvExportService.java} | 2 +- .../handler/DSpaceRunnableHandler.java | 5 +- .../CommandLineDSpaceRunnableHandler.java | 9 +- .../scripts/service/ProcessService.java | 12 +++ .../rest/repository/ScriptRestRepository.java | 4 +- .../impl/RestDSpaceRunnableHandler.java | 21 ++-- dspace/config/spring/api/core-services.xml | 2 +- 11 files changed, 107 insertions(+), 69 deletions(-) rename dspace-api/src/main/java/org/dspace/content/{MetadataExportServiceImpl.java => MetadataDSpaceCsvExportServiceImpl.java} (96%) rename dspace-api/src/main/java/org/dspace/content/service/{MetadataExportService.java => MetadataDSpaceCsvExportService.java} (98%) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java index f6527855f0..47e9fcb6ab 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -7,9 +7,11 @@ */ package org.dspace.app.bulkedit; +import java.io.OutputStream; + import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.dspace.content.service.MetadataExportService; +import org.dspace.content.service.MetadataDSpaceCsvExportService; import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; import org.springframework.beans.factory.annotation.Autowired; @@ -29,7 +31,7 @@ public class MetadataExport extends DSpaceRunnable { private boolean exportAllItems = false; @Autowired - private MetadataExportService metadataExportService; + private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService; private MetadataExport() { this.options = constructOptions(); @@ -41,7 +43,7 @@ public class MetadataExport extends DSpaceRunnable { options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)"); options.getOption("i").setType(String.class); options.addOption("f", "file", true, "destination where you want file written"); - options.getOption("f").setType(String.class); + options.getOption("f").setType(OutputStream.class); options.getOption("f").setRequired(true); options.addOption("a", "all", false, "include all metadata fields that are not normally changed (e.g. provenance)"); @@ -61,8 +63,9 @@ public class MetadataExport extends DSpaceRunnable { return; } - DSpaceCSV dSpaceCSV = metadataExportService.handleExport(context, exportAllItems, exportAllMetadata, handle, - handler); + DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService + .handleExport(context, exportAllItems, exportAllMetadata, handle, + handler); handler.writeFilestream(context, filename, dSpaceCSV.getInputStream(), "exportCSV"); context.restoreAuthSystemState(); context.complete(); @@ -74,6 +77,7 @@ public class MetadataExport extends DSpaceRunnable { if (commandLine.hasOption('h')) { help = true; + return; } // Check a filename is given diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 939c0d1de6..759981655e 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -17,6 +17,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; @@ -177,10 +178,8 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { * @param c The context * @param toImport An array of CSV lines to examine */ - public void initMetadataImport(Context c, DSpaceCSV toImport) { + public void initMetadataImport(DSpaceCSV toImport) { // Store the import settings - this.c = c; - csv = toImport; this.toImport = toImport.getCSVLines(); } @@ -193,7 +192,13 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { // Read commandLines from the CSV file try { - csv = new DSpaceCSV(handler.getFileStream(c, filename), c); + Optional optionalFileStream = handler.getFileStream(c, filename); + if (optionalFileStream.isPresent()) { + csv = new DSpaceCSV(optionalFileStream.get(), c); + } else { + throw new IllegalArgumentException("Error reading file, the file couldn't be found for filename: " + + filename); + } } catch (MetadataImportInvalidHeadingException miihe) { throw miihe; } catch (Exception e) { @@ -201,7 +206,7 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { } // Perform the first import - just highlight differences - initMetadataImport(c, csv); + initMetadataImport(csv); List changes; if (!commandLine.hasOption('s') || validateOnly) { @@ -257,6 +262,14 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { } + /** + * This method determines whether the changes should be applied or not. This is default set to true for the REST + * script as we don't want to interact with the caller. This will be overwritten in the CLI script to ask for + * confirmation + * @param handler Applicable DSpaceRunnableHandler + * @return boolean indicating the value + * @throws IOException If something goes wrong + */ protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException { return true; } @@ -269,6 +282,7 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { if (commandLine.hasOption('h')) { help = true; + return; } // Check a filename is given @@ -1238,7 +1252,7 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { * @param changed Whether or not the changes have been made * @return The number of items that have changed */ - private static int displayChanges(List changes, boolean changed) { + private int displayChanges(List changes, boolean changed) { // Display the changes int changeCounter = 0; for (BulkEditChange change : changes) { @@ -1253,20 +1267,19 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { (change.isDeleted()) || (change.isWithdrawn()) || (change.isReinstated())) { // Show the item Item i = change.getItem(); - - System.out.println("-----------------------------------------------------------"); + handler.logInfo("-----------------------------------------------------------"); if (!change.isNewItem()) { - System.out.println("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); + handler.logInfo("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); } else { - System.out.print("New item: "); + handler.logInfo("New item: "); if (i != null) { if (i.getHandle() != null) { - System.out.print(i.getID() + " (" + i.getHandle() + ")"); + handler.logInfo(i.getID() + " (" + i.getHandle() + ")"); } else { - System.out.print(i.getID() + " (in workflow)"); + handler.logInfo(i.getID() + " (in workflow)"); } } - System.out.println(); + handler.logInfo(""); } changeCounter++; } @@ -1274,23 +1287,23 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { // Show actions if (change.isDeleted()) { if (changed) { - System.out.println(" - EXPUNGED!"); + handler.logInfo(" - EXPUNGED!"); } else { - System.out.println(" - EXPUNGE!"); + handler.logInfo(" - EXPUNGE!"); } } if (change.isWithdrawn()) { if (changed) { - System.out.println(" - WITHDRAWN!"); + handler.logInfo(" - WITHDRAWN!"); } else { - System.out.println(" - WITHDRAW!"); + handler.logInfo(" - WITHDRAW!"); } } if (change.isReinstated()) { if (changed) { - System.out.println(" - REINSTATED!"); + handler.logInfo(" - REINSTATED!"); } else { - System.out.println(" - REINSTATE!"); + handler.logInfo(" - REINSTATE!"); } } @@ -1300,11 +1313,11 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + New owning collection (" + cHandle + "): "); + handler.logInfo(" + New owning collection (" + cHandle + "): "); } else { - System.out.print(" + New owning collection (" + cHandle + "): "); + handler.logInfo(" + New owning collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } c = change.getOldOwningCollection(); @@ -1312,11 +1325,11 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + Old owning collection (" + cHandle + "): "); + handler.logInfo(" + Old owning collection (" + cHandle + "): "); } else { - System.out.print(" + Old owning collection (" + cHandle + "): "); + handler.logInfo(" + Old owning collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } } @@ -1325,11 +1338,11 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + Map to collection (" + cHandle + "): "); + handler.logInfo(" + Map to collection (" + cHandle + "): "); } else { - System.out.print(" + Mapped to collection (" + cHandle + "): "); + handler.logInfo(" + Mapped to collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } // Show old mapped collections @@ -1337,11 +1350,11 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + Un-map from collection (" + cHandle + "): "); + handler.logInfo(" + Un-map from collection (" + cHandle + "): "); } else { - System.out.print(" + Un-mapped from collection (" + cHandle + "): "); + handler.logInfo(" + Un-mapped from collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } // Show additions @@ -1354,16 +1367,16 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { md += "[" + metadataValue.getLanguage() + "]"; } if (!changed) { - System.out.print(" + Add (" + md + "): "); + handler.logInfo(" + Add (" + md + "): "); } else { - System.out.print(" + Added (" + md + "): "); + handler.logInfo(" + Added (" + md + "): "); } - System.out.print(metadataValue.getValue()); + handler.logInfo(metadataValue.getValue()); if (isAuthorityControlledField(md)) { - System.out.print(", authority = " + metadataValue.getAuthority()); - System.out.print(", confidence = " + metadataValue.getConfidence()); + handler.logInfo(", authority = " + metadataValue.getAuthority()); + handler.logInfo(", confidence = " + metadataValue.getConfidence()); } - System.out.println(""); + handler.logInfo(""); } // Show removals @@ -1376,16 +1389,16 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { md += "[" + metadataValue.getLanguage() + "]"; } if (!changed) { - System.out.print(" - Remove (" + md + "): "); + handler.logInfo(" - Remove (" + md + "): "); } else { - System.out.print(" - Removed (" + md + "): "); + handler.logInfo(" - Removed (" + md + "): "); } - System.out.print(metadataValue.getValue()); + handler.logInfo(metadataValue.getValue()); if (isAuthorityControlledField(md)) { - System.out.print(", authority = " + metadataValue.getAuthority()); - System.out.print(", confidence = " + metadataValue.getConfidence()); + handler.logInfo(", authority = " + metadataValue.getAuthority()); + handler.logInfo(", confidence = " + metadataValue.getConfidence()); } - System.out.println(""); + handler.logInfo(""); } } return changeCounter; diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java index f7ed5adce5..efc396d68f 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java @@ -13,6 +13,10 @@ import java.io.InputStreamReader; import org.dspace.scripts.handler.DSpaceRunnableHandler; +/** + * CLI variant for the {@link MetadataImport} class + * This has been made so that we can specify the behaviour of the determineChanges method to be specific for the CLI + */ public class MetadataImportCLI extends MetadataImport { @Override diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java rename to dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 24338954d0..57027a5100 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -15,14 +15,14 @@ import java.util.List; import com.google.common.collect.Iterators; import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.content.service.ItemService; -import org.dspace.content.service.MetadataExportService; +import org.dspace.content.service.MetadataDSpaceCsvExportService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.springframework.beans.factory.annotation.Autowired; -public class MetadataExportServiceImpl implements MetadataExportService { +public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExportService { @Autowired private ItemService itemService; diff --git a/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java similarity index 98% rename from dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java rename to dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java index 77584ceeb0..aeb956fc49 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/MetadataExportService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java @@ -18,7 +18,7 @@ import org.dspace.scripts.handler.DSpaceRunnableHandler; /** * This is the interface to be implemented by a Service that deals with the exporting of Metadata */ -public interface MetadataExportService { +public interface MetadataDSpaceCsvExportService { /** * This method will export DSpaceObject objects depending on the parameters it gets. It can export all the items diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java index 543f21855c..078ba6bfa2 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java @@ -10,6 +10,7 @@ package org.dspace.scripts.handler; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.Optional; import org.apache.commons.cli.Options; import org.dspace.authorize.AuthorizeException; @@ -93,7 +94,7 @@ public interface DSpaceRunnableHandler { * @throws IOException If something goes wrong * @throws AuthorizeException If something goes wrong */ - public InputStream getFileStream(Context context, String fileName) throws IOException, AuthorizeException; + public Optional getFileStream(Context context, String fileName) throws IOException, AuthorizeException; /** * This method will write the InputStream to either a file on the filesystem or a bitstream in the database @@ -105,5 +106,5 @@ public interface DSpaceRunnableHandler { * @throws IOException If something goes wrong */ public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) - throws IOException; + throws IOException, SQLException, AuthorizeException; } diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java index f7a62b8acf..6775b9a455 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java @@ -10,6 +10,7 @@ package org.dspace.scripts.handler.impl; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; @@ -92,12 +93,12 @@ public class CommandLineDSpaceRunnableHandler implements DSpaceRunnableHandler { } @Override - public InputStream getFileStream(Context context, String fileName) throws IOException { + public Optional getFileStream(Context context, String fileName) throws IOException { File file = new File(fileName); - if (!file.isFile()) { - return null; + if (!(file.exists() && file.isFile())) { + return Optional.empty(); } - return FileUtils.openInputStream(file); + return Optional.of(FileUtils.openInputStream(file)); } @Override diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java index b8e78a5f4f..0287faab5c 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -108,6 +108,18 @@ public interface ProcessService { */ public void complete(Context context, Process process) throws SQLException; + /** + * The method will create a bitstream from the given inputstream with the given type as metadata and given name + * as name and attach it to the given process + * @param context The relevant DSpace context + * @param process The process for which the bitstream will be made + * @param is The inputstream for the bitstream + * @param type The type of the bitstream + * @param fileName The name of the bitstream + * @throws IOException If something goes wrong + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ public void appendFile(Context context, Process process, InputStream is, String type, String fileName) throws IOException, SQLException, AuthorizeException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index bc1709598d..a97a5d96fd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -143,7 +143,7 @@ public class ScriptRestRepository extends DSpaceRestRepository files, Context context, DSpaceRunnable scriptToExecute, RestDSpaceRunnableHandler restDSpaceRunnableHandler, List args) - throws IOException { + throws IOException, SQLException, AuthorizeException { try { scriptToExecute.initialize(args.toArray(new String[0]), restDSpaceRunnableHandler); checkFileNames(scriptToExecute, files); @@ -160,7 +160,7 @@ public class ScriptRestRepository extends DSpaceRestRepository files) - throws IOException { + throws IOException, SQLException, AuthorizeException { for (MultipartFile file : files) { restDSpaceRunnableHandler .writeFilestream(context, file.getOriginalFilename(), file.getInputStream(), "inputfile"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index 2922267e85..0ae8199866 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -13,6 +13,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; import java.util.List; +import java.util.Optional; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; @@ -186,11 +187,17 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { } @Override - public InputStream getFileStream(Context context, String fileName) throws IOException, AuthorizeException { + public Optional getFileStream(Context context, String fileName) throws IOException, + AuthorizeException { try { Process process = processService.find(context, processId); Bitstream bitstream = processService.getBitstreamByName(context, process, fileName); - return bitstreamService.retrieve(context, bitstream); + InputStream inputStream = bitstreamService.retrieve(context, bitstream); + if (inputStream == null) { + return Optional.empty(); + } else { + return Optional.of(inputStream); + } } catch (SQLException sqlException) { log.error("SQL exception while attempting to find process", sqlException); } @@ -199,13 +206,9 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { @Override public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) - throws IOException { - try { - Process process = processService.find(context, processId); - processService.appendFile(context, process, inputStream, type, fileName); - } catch (SQLException | AuthorizeException exception) { - log.error("Exception occurred while attempting to find process", exception); - } + throws IOException, SQLException, AuthorizeException { + Process process = processService.find(context, processId); + processService.appendFile(context, process, inputStream, type, fileName); } /** diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 82d596d192..c1778e64e2 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -44,7 +44,7 @@ - + From 74274ccafc864a33b7e62b939e584b7f3a0b2674 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 17 Jan 2020 13:33:34 +0100 Subject: [PATCH 018/749] 68273: Making types for which IRUS events should be sent configurable --- .../java/org/dspace/content/EntityType.java | 31 +++++++++-- .../export/ExportUsageEventListener.java | 52 ++++++++++++++++--- dspace/config/modules/stats.cfg | 4 ++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java index 15fe1739e5..cc80205162 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityType.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -15,6 +15,7 @@ import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.Table; +import org.apache.commons.lang3.StringUtils; import org.dspace.core.ReloadableEntity; /** @@ -45,7 +46,8 @@ public class EntityType implements ReloadableEntity { /** * The standard setter for the ID of this EntityType - * @param id The ID that this EntityType's ID will be set to + * + * @param id The ID that this EntityType's ID will be set to */ public void setId(Integer id) { this.id = id; @@ -53,7 +55,8 @@ public class EntityType implements ReloadableEntity { /** * The standard getter for the label of this EntityType - * @return The label for this EntityType + * + * @return The label for this EntityType */ public String getLabel() { return label; @@ -61,6 +64,7 @@ public class EntityType implements ReloadableEntity { /** * The standard setter for the label of this EntityType + * * @param label The label that this EntityType's label will be set to */ public void setLabel(String label) { @@ -69,9 +73,30 @@ public class EntityType implements ReloadableEntity { /** * The standard getter for the ID of this EntityType - * @return The ID for this EntityType + * + * @return The ID for this EntityType */ public Integer getID() { return id; } + + /** + * @param obj + * @return + */ + public boolean equals(Object obj) { + if (!(obj instanceof EntityType)) { + return false; + } + EntityType entityType = (EntityType) obj; + + if (this.getID() != entityType.getID()) { + return false; + } + + if (!StringUtils.equals(this.getLabel(), entityType.getLabel())) { + return false; + } + return true; + } } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java index 66c5414237..e5292cebc2 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java @@ -26,10 +26,14 @@ import org.dspace.app.util.Util; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.DCDate; +import org.dspace.content.Entity; +import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.dspace.core.LogManager; @@ -54,6 +58,10 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { /* The metadata field which is to be checked for */ private static MetadataField trackerType; + /* A list of entity types that will be processed */ + private static List entityTypes; + private static final String ENTITY_TYPE_DEFAULT = "Publication"; + /* A list of values the type might have */ private static List trackerValues; @@ -67,12 +75,21 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { private static ConfigurationService configurationService; + private static EntityTypeService entityTypeService; + private static EntityService entityService; + public void init(Context context) { try { if (configurationService == null) { configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); } + if (entityService == null) { + entityService = ContentServiceFactory.getInstance().getEntityService(); + } + if (entityTypeService == null) { + entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + } if (trackerType == null) { trackerType = resolveConfigPropertyToMetadataField(context, "tracker.type-field"); @@ -93,8 +110,16 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { } trackerUrlVersion = configurationService.getProperty("stats.tracker.urlversion"); - - + } + if (entityTypes == null) { + String[] entityTypeStrings = configurationService.getArrayProperty("stats.tracker.entity-types"); + entityTypes = new ArrayList<>(); + for (String type : entityTypeStrings) { + entityTypes.add(entityTypeService.findByEntityType(context, type)); + } + if (entityTypes.isEmpty()) { + entityTypes.add(entityTypeService.findByEntityType(context, ENTITY_TYPE_DEFAULT)); + } } } catch (Exception e) { log.error("Unknown error resolving configuration for the export usage event.", e); @@ -117,7 +142,7 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { .canEdit(context, item)) { init(context); - if (shouldProcessItem(item)) { + if (shouldProcessEntityType(context, item) && shouldProcessItemType(item)) { processItem(ue.getContext(), item, null, ue.getRequest(), ITEM_VIEW); } } @@ -140,7 +165,7 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { .canEdit(context, item)) { //Check if we have a valid type of item ! init(context); - if (shouldProcessItem(item)) { + if (shouldProcessEntityType(context, item) && shouldProcessItemType(item)) { processItem(ue.getContext(), item, bit, ue.getRequest(), BITSTREAM_DOWNLOAD); } } @@ -168,12 +193,23 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { } } - private boolean shouldProcessItem(Item item) { + private boolean shouldProcessEntityType(Context context, Item item) throws SQLException { + Entity entity = entityService.findByItemId(context, item.getID()); + EntityType type = entityService.getType(context, entity); + + if (type != null && entityTypes.contains(type)) { + return true; + } + return false; + } + + private boolean shouldProcessItemType(Item item) { if (trackerType != null && trackerValues != null) { List types = ContentServiceFactory.getInstance().getItemService() - .getMetadata(item, trackerType.getMetadataSchema().getName(), - trackerType.getElement(), - trackerType.getQualifier(), Item.ANY); + .getMetadata(item, + trackerType.getMetadataSchema().getName(), + trackerType.getElement(), + trackerType.getQualifier(), Item.ANY); if (!types.isEmpty()) { //Find out if we have a type that needs to be excluded diff --git a/dspace/config/modules/stats.cfg b/dspace/config/modules/stats.cfg index c7c978b9cb..aae0312873 100644 --- a/dspace/config/modules/stats.cfg +++ b/dspace/config/modules/stats.cfg @@ -12,6 +12,10 @@ # This lists a comma separated list of values that will be excluded for the given field. # stats.tracker.type-value = Article, Postprint +# This lists a comma separated list of entity that will be included +# When no list is provided, the default value "Publication" will be used +# stats.tracker.entity-types = Publication + # Set the tracker environment to "test" or "production". Defaults to "test" if empty. # The URL used by the test environment can be configured in property tracker.testurl # The URL used by the production environment can be configured in property tracker.produrl From 2d9e688d95ab065ed0c21a619a8cefb291123d1c Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 20 Jan 2020 11:43:14 +0100 Subject: [PATCH 019/749] [Task 68281] applied feedback to the metadata-import and metadata-export scripts --- .../dspace/app/bulkedit/MetadataExport.java | 11 ++++++++++ .../dspace/app/bulkedit/MetadataImport.java | 3 --- .../org/dspace/scripts/DSpaceRunnable.java | 18 +++++++++++++++++ .../rest/repository/ScriptRestRepository.java | 5 +++++ dspace/config/registries/process-types.xml | 20 +++++++++++++++++++ 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 dspace/config/registries/process-types.xml diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java index 47e9fcb6ab..706a84620e 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -8,11 +8,13 @@ package org.dspace.app.bulkedit; import java.io.OutputStream; +import java.sql.SQLException; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dspace.content.service.MetadataDSpaceCsvExportService; import org.dspace.core.Context; +import org.dspace.eperson.service.EPersonService; import org.dspace.scripts.DSpaceRunnable; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +35,9 @@ public class MetadataExport extends DSpaceRunnable { @Autowired private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService; + @Autowired + private EPersonService ePersonService; + private MetadataExport() { this.options = constructOptions(); } @@ -92,5 +97,11 @@ public class MetadataExport extends DSpaceRunnable { exportAllItems = true; } handle = commandLine.getOptionValue('i'); + + try { + context.setCurrentUser(ePersonService.find(context, getEpersonIdentifier())); + } catch (SQLException e) { + handler.handleException(e); + } } } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 759981655e..0c3f697bd8 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -1279,7 +1279,6 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { handler.logInfo(i.getID() + " (in workflow)"); } } - handler.logInfo(""); } changeCounter++; } @@ -1376,7 +1375,6 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { handler.logInfo(", authority = " + metadataValue.getAuthority()); handler.logInfo(", confidence = " + metadataValue.getConfidence()); } - handler.logInfo(""); } // Show removals @@ -1398,7 +1396,6 @@ public class MetadataImport extends DSpaceRunnable implements InitializingBean { handler.logInfo(", authority = " + metadataValue.getAuthority()); handler.logInfo(", confidence = " + metadataValue.getConfidence()); } - handler.logInfo(""); } } return changeCounter; diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index cad875a53b..ac8a67539d 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; +import java.util.UUID; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; @@ -31,6 +32,7 @@ import org.springframework.beans.factory.annotation.Required; */ public abstract class DSpaceRunnable implements Runnable, BeanNameAware { + private UUID epersonIdentifier; /** * The name of the script */ @@ -178,4 +180,20 @@ public abstract class DSpaceRunnable implements Runnable, BeanNameAware { public String getName() { return name; } + + /** + * Generic getter for the epersonIdentifier + * @return the epersonIdentifier value of this DSpaceRunnable + */ + public UUID getEpersonIdentifier() { + return epersonIdentifier; + } + + /** + * Generic setter for the epersonIdentifier + * @param epersonIdentifier The epersonIdentifier to be set on this DSpaceRunnable + */ + public void setEpersonIdentifier(UUID epersonIdentifier) { + this.epersonIdentifier = epersonIdentifier; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index a97a5d96fd..16b86f397b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -30,6 +30,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.scripts.handler.impl.RestDSpaceRunnableHandler; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.service.ScriptService; @@ -100,6 +101,10 @@ public class ScriptRestRepository extends DSpaceRestRepository args = constructArgs(dSpaceCommandLineParameters); diff --git a/dspace/config/registries/process-types.xml b/dspace/config/registries/process-types.xml new file mode 100644 index 0000000000..672f8f94bf --- /dev/null +++ b/dspace/config/registries/process-types.xml @@ -0,0 +1,20 @@ + + + + DSpace Process Types + + + + process + http://dspace.org/process + + + + process + type + + + + + + From 130db9531d871edc49af6412665babff082af7df Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 22 Jan 2020 14:14:01 +0100 Subject: [PATCH 020/749] [Task 68380] added files and file endpoints to the Process endpoints --- .../org/dspace/scripts/DSpaceRunnable.java | 4 +- .../dspace/scripts/ProcessServiceImpl.java | 8 +- .../scripts/service/ProcessService.java | 2 +- .../app/rest/ProcessRestController.java | 107 ++++++++++++++++++ .../link/process/ProcessHalLinkFactory.java | 14 +++ .../ProcessResourceHalLinkFactory.java | 15 ++- .../rest/model/ProcessFileWrapperRest.java | 35 ++++++ .../hateoas/ProcessFileWrapperResource.java | 42 +++++++ .../repository/ProcessRestRepository.java | 76 +++++++++++++ .../rest/repository/ScriptRestRepository.java | 6 + .../app/rest/ProcessRestRepositoryIT.java | 4 +- .../app/rest/ScriptRestRepositoryIT.java | 84 +++++++++++++- .../app/rest/matcher/ProcessMatcher.java | 5 +- .../app/rest/matcher/ScriptMatcher.java | 3 +- .../impl/MockDSpaceRunnableScript.java | 5 + dspace/config/dspace.cfg | 1 + 16 files changed, 391 insertions(+), 20 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ProcessRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessHalLinkFactory.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileWrapperRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileWrapperResource.java diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index ac8a67539d..8d3701a490 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -18,6 +18,7 @@ import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; import org.dspace.scripts.handler.DSpaceRunnableHandler; @@ -80,7 +81,8 @@ public abstract class DSpaceRunnable implements Runnable, BeanNameAware { List fileNames = new LinkedList<>(); for (Option option : options.getOptions()) { - if (option.getType() == InputStream.class) { + if (option.getType() == InputStream.class && + StringUtils.isNotBlank(commandLine.getOptionValue(option.getOpt()))) { fileNames.add(commandLine.getOptionValue(option.getOpt())); } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index 67fd151ac9..5fa8ec7699 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.List; import java.util.regex.Pattern; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; @@ -145,11 +146,14 @@ public class ProcessServiceImpl implements ProcessService { } @Override - public void delete(Context context, Process process) throws SQLException { + public void delete(Context context, Process process) throws SQLException, IOException, AuthorizeException { + + for (Bitstream bitstream : ListUtils.emptyIfNull(process.getBitstreams())) { + bitstreamService.delete(context, bitstream); + } processDAO.delete(context, process); log.info(LogManager.getHeader(context, "process_delete", "Process with ID " + process.getID() + " and name " + process.getName() + " has been deleted")); - } @Override diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java index 0287faab5c..28f302f9e3 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -129,7 +129,7 @@ public interface ProcessService { * @param process The Process object to be deleted * @throws SQLException If something goes wrong */ - public void delete(Context context, Process process) throws SQLException; + public void delete(Context context, Process process) throws SQLException, IOException, AuthorizeException; /** * This method will be used to update the given Process object in the database diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ProcessRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ProcessRestController.java new file mode 100644 index 0000000000..8c9cdd5200 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ProcessRestController.java @@ -0,0 +1,107 @@ +/** + * 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.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.link.HalLinkService; +import org.dspace.app.rest.link.process.ProcessResourceHalLinkFactory; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.model.hateoas.BitstreamResource; +import org.dspace.app.rest.model.hateoas.ProcessFileWrapperResource; +import org.dspace.app.rest.repository.ProcessRestRepository; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.PagedResources; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/" + ProcessRest.CATEGORY + "/" + ProcessRest.PLURAL_NAME) +public class ProcessRestController { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + HalLinkService linkService; + + @Autowired + private ProcessRestRepository processRestRepository; + + @Autowired + private Utils utils; + + @Autowired + private HalLinkService halLinkService; + + @Autowired + ProcessResourceHalLinkFactory processResourceHalLinkFactory; + + @RequestMapping(method = RequestMethod.GET, value = "/{processId}/files") + public ProcessFileWrapperResource listFilesFromProcess(@PathVariable(name = "processId") Integer processId) + throws SQLException, AuthorizeException { + + if (log.isTraceEnabled()) { + log.trace("Retrieving Files from Process with ID: " + processId); + } + + ProcessFileWrapperResource processFileWrapperResource = + new ProcessFileWrapperResource(processRestRepository.getProcessFileWrapperRest(processId), utils); + halLinkService.addLinks(processFileWrapperResource); + return processFileWrapperResource; + } + + @RequestMapping(method = RequestMethod.GET, value = "/{processId}/files/{fileType}") + public PagedResources listFilesWithTypeFromProcess( + @PathVariable(name = "processId") Integer processId, + @PathVariable(name = "fileType") String fileType, + Pageable pageable, PagedResourcesAssembler assembler) throws SQLException, AuthorizeException { + + if (log.isTraceEnabled()) { + log.trace("Retrieving Files with type " + fileType + " from Process with ID: " + processId); + } + + List bitstreamResources = processRestRepository + .getProcessBitstreamsByType(processId, fileType).stream() + .map(bitstreamRest -> new BitstreamResource(bitstreamRest, utils)) + .collect(Collectors.toList()); + + Page page = utils.getPage(bitstreamResources, pageable); + + Link link = linkTo( + methodOn(this.getClass()).listFilesWithTypeFromProcess(processId, fileType, pageable, assembler)) + .withSelfRel(); + PagedResources result = assembler.toResource(page, link); + + return result; + } + + @RequestMapping(method = RequestMethod.GET, value = "/{processId}/files/name/{fileName:.+}") + public BitstreamResource getBitstreamByName(@PathVariable(name = "processId") Integer processId, + @PathVariable(name = "fileName") String fileName) + throws SQLException, AuthorizeException { + + BitstreamRest bitstreamRest = processRestRepository.getProcessBitstreamByName(processId, fileName); + return new BitstreamResource(bitstreamRest, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessHalLinkFactory.java new file mode 100644 index 0000000000..11fcb1b71c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessHalLinkFactory.java @@ -0,0 +1,14 @@ +/** + * 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.link.process; + +import org.dspace.app.rest.ProcessRestController; +import org.dspace.app.rest.link.HalLinkFactory; + +public abstract class ProcessHalLinkFactory extends HalLinkFactory { +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java index 4bbf77ff97..44642ce310 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java @@ -9,8 +9,7 @@ package org.dspace.app.rest.link.process; import java.util.LinkedList; -import org.dspace.app.rest.RestResourceController; -import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.ProcessRestController; import org.dspace.app.rest.model.hateoas.ProcessResource; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -18,24 +17,24 @@ import org.springframework.data.domain.Pageable; import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; -/** - * This class will provide the ProcessResource with links - */ @Component -public class ProcessResourceHalLinkFactory extends HalLinkFactory { +public class ProcessResourceHalLinkFactory extends ProcessHalLinkFactory { @Autowired private ConfigurationService configurationService; protected void addLinks(ProcessResource halResource, Pageable pageable, LinkedList list) throws Exception { String dspaceRestUrl = configurationService.getProperty("dspace.restUrl"); +// list.add( +// buildLink("script", dspaceRestUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); + list.add(buildLink("files", getMethodOn().listFilesFromProcess(halResource.getContent().getProcessId()))); list.add( buildLink("script", dspaceRestUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); } - protected Class getControllerClass() { - return RestResourceController.class; + protected Class getControllerClass() { + return ProcessRestController.class; } protected Class getResourceClass() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileWrapperRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileWrapperRest.java new file mode 100644 index 0000000000..df9059393e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileWrapperRest.java @@ -0,0 +1,35 @@ +/** + * 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; + +public class ProcessFileWrapperRest { + private Integer processId; + + @JsonIgnore + private List bitstreams; + + public Integer getProcessId() { + return processId; + } + + public void setProcessId(Integer processId) { + this.processId = processId; + } + + public void setBitstreams(List bistreams) { + this.bitstreams = bistreams; + } + + public List getBitstreams() { + return bitstreams; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileWrapperResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileWrapperResource.java new file mode 100644 index 0000000000..e2ca726bb7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileWrapperResource.java @@ -0,0 +1,42 @@ +/** + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.ProcessFileWrapperRest; +import org.dspace.app.rest.utils.Utils; + + +public class ProcessFileWrapperResource extends HALResource { + + public ProcessFileWrapperResource(ProcessFileWrapperRest content, Utils utils) { + super(content); + + if (content != null) { + HashMap> bitstreamResourceMap = new HashMap<>(); + for (BitstreamRest bitstreamRest : content.getBitstreams()) { + List fileType = bitstreamRest.getMetadata().getMap().get("process.type"); + if (fileType != null && !fileType.isEmpty()) { + bitstreamResourceMap + .computeIfAbsent(fileType.get(0).getValue(), k -> new ArrayList<>()) + .add(new BitstreamResource(bitstreamRest, utils)); + } + } + + for (Map.Entry> entry : bitstreamResourceMap.entrySet()) { + embedResource(entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java index ebfdb4d2a6..03fe8f5bd9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java @@ -8,16 +8,26 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.apache.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.ProcessFileWrapperRest; import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; import org.dspace.core.Context; import org.dspace.scripts.Process; import org.dspace.scripts.service.ProcessService; 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; @@ -32,6 +42,14 @@ public class ProcessRestRepository extends DSpaceRestRepository getProcessBitstreams(Integer processId) throws SQLException, AuthorizeException { + return getProcessBitstreamsByType(processId, null); + } + + public ProcessFileWrapperRest getProcessFileWrapperRest(Integer processId) throws SQLException, AuthorizeException { + ProcessFileWrapperRest processFileWrapperRest = new ProcessFileWrapperRest(); + processFileWrapperRest.setBitstreams(getProcessBitstreams(processId)); + processFileWrapperRest.setProcessId(processId); + + return processFileWrapperRest; + } + + public List getProcessBitstreamsByType(Integer processId, String type) + throws SQLException, AuthorizeException { + Context context = obtainContext(); + Process process = processService.find(context, processId); + if (process == null) { + throw new ResourceNotFoundException("Process with id " + processId + " was not found"); + } + if ((context.getCurrentUser() == null) || (!context.getCurrentUser() + .equals(process.getEPerson()) && !authorizeService + .isAdmin(context))) { + throw new AuthorizeException("The current user is not eligible to view the process with id: " + processId); + } + List bitstreams = processService.getBitstreams(context, process, type); + + if (bitstreams == null) { + return Collections.emptyList(); + } + + return bitstreams.stream() + .map(bitstream -> (BitstreamRest) converterService.toRest(bitstream, Projection.DEFAULT)) + .collect(Collectors.toList()); + + } + + public BitstreamRest getProcessBitstreamByName(Integer processId, String name) + throws SQLException, AuthorizeException { + Context context = obtainContext(); + Process process = processService.find(context, processId); + if (process == null) { + throw new ResourceNotFoundException("Process with id " + processId + " was not found"); + } + if ((context.getCurrentUser() == null) || (!context.getCurrentUser() + .equals(process.getEPerson()) && !authorizeService + .isAdmin(context))) { + throw new AuthorizeException("The current user is not eligible to view the process with id: " + processId); + } + Bitstream bitstream = processService.getBitstreamByName(context, process, name); + + if (bitstream == null) { + throw new ResourceNotFoundException( + "Bitstream with name " + name + " and process id " + processId + " was not found"); + } + + return converterService.toRest(bitstream, Projection.DEFAULT); + } + @Override public Class getDomainClass() { return ProcessRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index 16b86f397b..2a1d63e482 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -172,6 +172,12 @@ public class ScriptRestRepository extends DSpaceRestRepository files) { List fileNames = new LinkedList<>(); for (MultipartFile file : files) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java index 92c37007b1..c90ad14b19 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java @@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.IOException; import java.sql.SQLException; import java.util.LinkedList; @@ -21,6 +22,7 @@ import org.dspace.app.rest.builder.ProcessBuilder; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.ProcessMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.ProcessStatus; import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.Process; @@ -206,7 +208,7 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { try { processService.delete(context, process); - } catch (SQLException e) { + } catch (SQLException | IOException | AuthorizeException e) { throw new RuntimeException(e); } }); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 96a6d3034d..2be9929e58 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -10,11 +10,13 @@ package org.dspace.app.rest; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; +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.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.IOException; import java.sql.SQLException; import java.util.Arrays; import java.util.LinkedList; @@ -23,6 +25,9 @@ import java.util.stream.Collectors; import com.google.gson.Gson; import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.ProcessMatcher; @@ -30,7 +35,12 @@ import org.dspace.app.rest.matcher.ScriptMatcher; import org.dspace.app.rest.model.ParameterValueRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; import org.dspace.content.ProcessStatus; +import org.dspace.content.service.BitstreamService; import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.service.ProcessService; @@ -38,12 +48,17 @@ import org.hamcrest.Matchers; import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private ProcessService processService; + @Autowired + private BitstreamService bitstreamService; + @Autowired private List dSpaceRunnableList; @@ -184,8 +199,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { parameters.add(new DSpaceCommandLineParameter("-q", null)); List list = parameters.stream() - .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter - .convert(dSpaceCommandLineParameter, Projection.DEFAULT)).collect(Collectors.toList()); + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); String token = getAuthToken(admin.getEmail(), password); @@ -216,8 +232,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { parameters.add(new DSpaceCommandLineParameter("-i", null)); List list = parameters.stream() - .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter - .convert(dSpaceCommandLineParameter, Projection.DEFAULT)).collect(Collectors.toList()); + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); String token = getAuthToken(admin.getEmail(), password); List acceptableProcessStatuses = new LinkedList<>(); @@ -245,12 +262,69 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isBadRequest()); } + @Test + public void postProcessAdminWithFileSuccess() throws Exception { + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-r", "test")); + parameters.add(new DSpaceCommandLineParameter("-i", null)); + + + 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(); + + //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(); + + String bitstreamContent = "Hello, World!"; + MockMultipartFile bitstreamFile = new MockMultipartFile("file", + "hello.txt", MediaType.TEXT_PLAIN_VALUE, + bitstreamContent.getBytes()); + parameters.add(new DSpaceCommandLineParameter("-f", "hello.txt")); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + String token = getAuthToken(admin.getEmail(), password); + List acceptableProcessStatuses = new LinkedList<>(); + acceptableProcessStatuses.addAll(Arrays.asList(ProcessStatus.SCHEDULED, + ProcessStatus.RUNNING, + ProcessStatus.COMPLETED)); + + getClient(token).perform(fileUpload("/api/system/scripts/mock-script/processes").file(bitstreamFile) + .param("properties", + new Gson().toJson(list))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("mock-script", + String.valueOf(admin.getID()), + parameters, + acceptableProcessStatuses)))); + + } + + @After public void destroy() throws Exception { CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { try { processService.delete(context, process); - } catch (SQLException e) { + } catch (SQLException | AuthorizeException | IOException e) { throw new RuntimeException(e); } }); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.java index 2ac00eb4ab..cc62c1e9ab 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.java @@ -65,7 +65,10 @@ public class ProcessMatcher { list.stream().map(dSpaceCommandLineParameter -> ParameterValueMatcher .matchParameterValue(dSpaceCommandLineParameter.getName(), dSpaceCommandLineParameter.getValue())) .collect(Collectors.toList()) - )) + )), + hasJsonPath("$._links.script.href", Matchers.containsString(name)), + hasJsonPath("$._links.files.href", Matchers.containsString("files")), + hasJsonPath("$._links.self.href", Matchers.containsString("api/system/processes")) ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.java index d348ee76c4..c919aadd86 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.java @@ -32,7 +32,8 @@ public class ScriptMatcher { matchScript("mock-script", "Mocking a script for testing purposes"), hasJsonPath("$.parameters", Matchers.containsInAnyOrder( ParameterMatcher.matchParameter(options.getOption("r")), - ParameterMatcher.matchParameter(options.getOption("i")) + ParameterMatcher.matchParameter(options.getOption("i")), + ParameterMatcher.matchParameter(options.getOption("f")) )) ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java b/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java index 5df5c8992b..0d48ad9c9d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java +++ b/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java @@ -7,6 +7,8 @@ */ package org.dspace.scripts.impl; +import java.io.InputStream; + import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dspace.scripts.DSpaceRunnable; @@ -37,6 +39,9 @@ public class MockDSpaceRunnableScript extends DSpaceRunnable { options.addOption("i", "index", false, "description i"); options.getOption("i").setType(boolean.class); options.getOption("i").setRequired(true); + options.addOption("f", "file", true, "source file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(false); return options; } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index bce46aaba6..38b0b0a7fb 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -789,6 +789,7 @@ registry.metadata.load = schema-organization-types.xml registry.metadata.load = schema-periodical-types.xml registry.metadata.load = schema-publicationIssue-types.xml registry.metadata.load = schema-publicationVolume-types.xml +registry.metadata.load = process-types.xml From 5265ebd382097d221b9b9bfc2df708b5819f1fa0 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Wed, 22 Jan 2020 14:53:42 +0100 Subject: [PATCH 021/749] Removing commented out code --- .../app/rest/link/process/ProcessResourceHalLinkFactory.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java index 44642ce310..c71c94adbd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java @@ -25,8 +25,6 @@ public class ProcessResourceHalLinkFactory extends ProcessHalLinkFactory list) throws Exception { String dspaceRestUrl = configurationService.getProperty("dspace.restUrl"); -// list.add( -// buildLink("script", dspaceRestUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); list.add(buildLink("files", getMethodOn().listFilesFromProcess(halResource.getContent().getProcessId()))); list.add( buildLink("script", dspaceRestUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); From 0ee7ec9bef0c32cd4aedd16b20a0f9098e5e0e38 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 31 Jan 2020 12:16:07 +0100 Subject: [PATCH 022/749] 67668: Refactoring code, adding tests and typedocs --- .../export/ExportUsageEventListener.java | 417 +----------------- .../statistics/export/OpenURLTracker.java | 34 +- .../OpenURLTrackerLoggerServiceImpl.java | 20 +- .../export/RetryOpenUrlTracker.java | 33 +- .../statistics/export/SpiderDetector.java | 289 ------------ .../OpenURLTrackerLoggerServiceFactory.java | 16 +- ...penURLTrackerLoggerServiceFactoryImpl.java | 19 +- .../processor/BitstreamEventProcessor.java | 141 ++++++ .../processor/ExportEventProcessor.java | 294 ++++++++++++ .../export/processor/ItemEventProcessor.java | 83 ++++ .../service/OpenURLTrackerLoggerService.java | 20 +- .../export/service/OpenUrlService.java | 44 ++ .../export/service/OpenUrlServiceImpl.java | 135 ++++++ .../config/spring/api/openurltracker.xml | 13 + .../config/spring/api/scripts.xml | 6 + .../impl/TestDSpaceRunnableHandler.java | 36 ++ .../export/ITExportUsageEventListener.java | 390 ++++++++++++++++ .../export/ITRetryOpenUrlTracker.java | 170 +++++++ .../OpenURLTrackerLoggerServiceImplTest.java | 85 ++++ .../BitstreamEventProcessorTest.java | 100 +++++ .../processor/ExportEventProcessorTest.java | 336 ++++++++++++++ .../processor/ItemEventProcessorTest.java | 57 +++ .../service/MockOpenUrlServiceImpl.java | 41 ++ .../service/OpenUrlServiceImplTest.java | 136 ++++++ .../app/rest/ScriptRestRepositoryIT.java | 4 +- .../service/MockOpenUrlServiceImpl.java | 41 ++ dspace/config/spring/api/core-services.xml | 2 - dspace/config/spring/api/openurltracker.xml | 10 + 28 files changed, 2267 insertions(+), 705 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlService.java create mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java create mode 100644 dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml create mode 100644 dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/processor/ItemEventProcessorTest.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java create mode 100644 dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java create mode 100644 dspace/config/spring/api/openurltracker.xml diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java index e5292cebc2..5065d0f54e 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java @@ -7,174 +7,46 @@ */ package org.dspace.statistics.export; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; -import org.dspace.app.util.Util; import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; -import org.dspace.content.DCDate; -import org.dspace.content.Entity; -import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.MetadataField; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.EntityService; -import org.dspace.content.service.EntityTypeService; -import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.dspace.core.LogManager; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.model.Event; -import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; -import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; -import org.dspace.statistics.util.SpiderDetector; +import org.dspace.statistics.export.processor.BitstreamEventProcessor; +import org.dspace.statistics.export.processor.ItemEventProcessor; import org.dspace.usage.AbstractUsageEventListener; import org.dspace.usage.UsageEvent; /** - * User: kevin (kevin at atmire.com) - * Date: 30-mrt-2010 - * Time: 16:37:56 + * Class to receive usage events and send corresponding data to IRUS */ public class ExportUsageEventListener extends AbstractUsageEventListener { /* Log4j logger*/ private static Logger log = Logger.getLogger(ExportUsageEventListener.class); - /* The metadata field which is to be checked for */ - private static MetadataField trackerType; - - /* A list of entity types that will be processed */ - private static List entityTypes; - private static final String ENTITY_TYPE_DEFAULT = "Publication"; - - /* A list of values the type might have */ - private static List trackerValues; - - /* The base url of the tracker */ - private static String baseUrl; - - private static String trackerUrlVersion; - - private static final String ITEM_VIEW = "Investigation"; - private static final String BITSTREAM_DOWNLOAD = "Request"; - - private static ConfigurationService configurationService; - - private static EntityTypeService entityTypeService; - private static EntityService entityService; - - - public void init(Context context) { - try { - if (configurationService == null) { - configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - } - if (entityService == null) { - entityService = ContentServiceFactory.getInstance().getEntityService(); - } - if (entityTypeService == null) { - entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); - } - if (trackerType == null) { - trackerType = resolveConfigPropertyToMetadataField(context, "tracker.type-field"); - - String[] metadataValues = configurationService.getArrayProperty("stats.tracker.type-value"); - if (metadataValues.length > 0) { - trackerValues = new ArrayList<>(); - for (String metadataValue : metadataValues) { - trackerValues.add(metadataValue.toLowerCase()); - } - } else { - trackerValues = null; - } - - if (StringUtils.equals(configurationService.getProperty("stats.tracker.environment"), "production")) { - baseUrl = configurationService.getProperty("stats.tracker.produrl"); - } else { - baseUrl = configurationService.getProperty("stats.tracker.testurl"); - } - - trackerUrlVersion = configurationService.getProperty("stats.tracker.urlversion"); - } - if (entityTypes == null) { - String[] entityTypeStrings = configurationService.getArrayProperty("stats.tracker.entity-types"); - entityTypes = new ArrayList<>(); - for (String type : entityTypeStrings) { - entityTypes.add(entityTypeService.findByEntityType(context, type)); - } - if (entityTypes.isEmpty()) { - entityTypes.add(entityTypeService.findByEntityType(context, ENTITY_TYPE_DEFAULT)); - } - } - } catch (Exception e) { - log.error("Unknown error resolving configuration for the export usage event.", e); - trackerType = null; - trackerValues = null; - baseUrl = null; - trackerUrlVersion = null; - } - } - + /** + * Receives an event and processes to create a URL to send to IRUS when certain conditions are met + * @param event includes all the information related to the event that occurred + */ public void receiveEvent(Event event) { if (event instanceof UsageEvent) { UsageEvent ue = (UsageEvent) event; Context context = ue.getContext(); + try { //Check for item investigation if (ue.getObject() instanceof Item) { - Item item = (Item) ue.getObject(); - if (item.isArchived() && !ContentServiceFactory.getInstance().getItemService() - .canEdit(context, item)) { - init(context); + ItemEventProcessor itemEventProcessor = new ItemEventProcessor(context, ue.getRequest(), + (Item) ue.getObject()); + itemEventProcessor.processEvent(); + } else if (ue.getObject() instanceof Bitstream) { - if (shouldProcessEntityType(context, item) && shouldProcessItemType(item)) { - processItem(ue.getContext(), item, null, ue.getRequest(), ITEM_VIEW); - } - } - } - //Check for bitstream download - if (ue.getObject() instanceof Bitstream) { - Bitstream bit = (Bitstream) ue.getObject(); - //Check for an item - if (0 < bit.getBundles().size()) { - if (!SpiderDetector.isSpider(ue.getRequest())) { - Bundle bundle = bit.getBundles().get(0); - if (bundle.getName() == null || !bundle.getName().equals("ORIGINAL")) { - return; - } - - if (0 < bundle.getItems().size()) { - Item item = bundle.getItems().get(0); - - if (item.isArchived() && !ContentServiceFactory.getInstance().getItemService() - .canEdit(context, item)) { - //Check if we have a valid type of item ! - init(context); - if (shouldProcessEntityType(context, item) && shouldProcessItemType(item)) { - processItem(ue.getContext(), item, bit, ue.getRequest(), BITSTREAM_DOWNLOAD); - } - } - } - } else { - log.info("Robot (" + ue.getRequest().getHeader("user-agent") + ") accessed " + bit - .getName() + "/" + bit.getSource()); - } - } + BitstreamEventProcessor bitstreamEventProcessor = + new BitstreamEventProcessor(context, ue.getRequest(), (Bitstream) ue.getObject()); + bitstreamEventProcessor.processEvent(); } } catch (Exception e) { UUID id; @@ -188,266 +60,7 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { } log.error(LogManager.getHeader(ue.getContext(), "Error while processing export of use event", "Id: " + id + " type: " + type), e); - e.printStackTrace(); } } } - - private boolean shouldProcessEntityType(Context context, Item item) throws SQLException { - Entity entity = entityService.findByItemId(context, item.getID()); - EntityType type = entityService.getType(context, entity); - - if (type != null && entityTypes.contains(type)) { - return true; - } - return false; - } - - private boolean shouldProcessItemType(Item item) { - if (trackerType != null && trackerValues != null) { - List types = ContentServiceFactory.getInstance().getItemService() - .getMetadata(item, - trackerType.getMetadataSchema().getName(), - trackerType.getElement(), - trackerType.getQualifier(), Item.ANY); - - if (!types.isEmpty()) { - //Find out if we have a type that needs to be excluded - for (MetadataValue type : types) { - if (trackerValues.contains(type.getValue().toLowerCase())) { - //We have found no type so process this item - return false; - } - } - return true; - } else { - // No types in this item, so not excluded - return true; - } - } else { - // No types to be excluded - return true; - } - } - - private void processItem(Context context, Item item, Bitstream bitstream, HttpServletRequest request, - String eventType) throws IOException, SQLException { - //We have a valid url collect the rest of the data - String clientIP = request.getRemoteAddr(); - if (configurationService.getBooleanProperty("useProxies", false) && request - .getHeader("X-Forwarded-For") != null) { - /* This header is a comma delimited list */ - for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { - /* proxy itself will sometime populate this header with the same value in - remote address. ordering in spec is vague, we'll just take the last - not equal to the proxy - */ - if (!request.getHeader("X-Forwarded-For").contains(clientIP)) { - clientIP = xfip.trim(); - } - } - } - String clientUA = StringUtils.defaultIfBlank(request.getHeader("USER-AGENT"), ""); - String referer = StringUtils.defaultIfBlank(request.getHeader("referer"), ""); - - //Start adding our data - StringBuilder data = new StringBuilder(); - data.append(URLEncoder.encode("url_ver", "UTF-8") + "=" + URLEncoder.encode(trackerUrlVersion, "UTF-8")); - data.append("&").append(URLEncoder.encode("req_id", "UTF-8")).append("=") - .append(URLEncoder.encode(clientIP, "UTF-8")); - data.append("&").append(URLEncoder.encode("req_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(clientUA, "UTF-8")); - data.append("&").append(URLEncoder.encode("rft.artnum", "UTF-8")).append("="). - append(URLEncoder.encode("oai:" + configurationService.getProperty("dspace.hostname") + ":" + item - .getHandle(), "UTF-8")); - data.append("&").append(URLEncoder.encode("rfr_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(referer, "UTF-8")); - data.append("&").append(URLEncoder.encode("rfr_id", "UTF-8")).append("=") - .append(URLEncoder.encode(configurationService.getProperty("dspace.hostname"), "UTF-8")); - data.append("&").append(URLEncoder.encode("url_tim", "UTF-8")).append("=") - .append(URLEncoder.encode(new DCDate(new Date()).toString(), "UTF-8")); - - if (BITSTREAM_DOWNLOAD.equals(eventType)) { - String bitstreamInfo = getBitstreamInfo(item, bitstream); - data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(bitstreamInfo, "UTF-8")); - data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(BITSTREAM_DOWNLOAD, "UTF-8")); - } else if (ITEM_VIEW.equals(eventType)) { - String itemInfo = getItemInfo(item); - data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(itemInfo, "UTF-8")); - data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(ITEM_VIEW, "UTF-8")); - } - - processUrl(context, baseUrl + "?" + data.toString()); - - } - - private String getBitstreamInfo(final Item item, final Bitstream bitstream) { - //only for jsp ui - // http://demo.dspace.org/jspui/handle/10673/2235 - // http://demo.dspace.org/jspui/bitstream/10673/2235/1/Captura.JPG - // - - - //only fror xmlui - // http://demo.dspace.org/xmlui/handle/10673/2235 - // http://demo.dspace.org/xmlui/bitstream/handle/10673/2235/Captura.JPG?sequence=1 - // - - String uiType = configurationService.getProperty("stats.dspace.type"); - StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); - if ("jspui".equals(uiType)) { - - sb.append("/bitstream/").append(item.getHandle()).append("/").append(bitstream.getSequenceID()); - - // If we can, append the pretty name of the bitstream to the URL - try { - if (bitstream.getName() != null) { - sb.append("/").append(Util.encodeBitstreamName(bitstream.getName(), "UTF-8")); - } - } catch (UnsupportedEncodingException uee) { - // just ignore it, we don't have to have a pretty - // name at the end of the URL because the sequence id will - // locate it. However it means that links in this file might - // not work.... - } - - - } else { //xmlui - - String identifier = null; - if (item != null && item.getHandle() != null) { - identifier = "handle/" + item.getHandle(); - } else if (item != null) { - identifier = "item/" + item.getID(); - } else { - identifier = "id/" + bitstream.getID(); - } - - - sb.append("/bitstream/").append(identifier).append("/"); - - // If we can, append the pretty name of the bitstream to the URL - try { - if (bitstream.getName() != null) { - sb.append(Util.encodeBitstreamName(bitstream.getName(), "UTF-8")); - } - } catch (UnsupportedEncodingException uee) { - // just ignore it, we don't have to have a pretty - // name at the end of the URL because the sequence id will - // locate it. However it means that links in this file might - // not work.... - } - - sb.append("?sequence=").append(bitstream.getSequenceID()); - } - return sb.toString(); - } - - private String getItemInfo(final Item item) { - StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); - sb.append("/handle/").append(item.getHandle()); - - return sb.toString(); - } - - - private static void processUrl(Context c, String urlStr) throws IOException, SQLException { - log.debug("Prepared to send url to tracker URL: " + urlStr); - System.out.println(urlStr); - URLConnection conn; - - try { - // Send data - URL url = new URL(urlStr); - conn = url.openConnection(); - - if (((HttpURLConnection) conn).getResponseCode() != 200) { - ExportUsageEventListener.logfailed(c, urlStr); - } else if (log.isDebugEnabled()) { - log.debug("Successfully posted " + urlStr + " on " + new Date()); - } - } catch (Exception e) { - log.error("Failed to send url to tracker URL: " + urlStr); - ExportUsageEventListener.logfailed(c, urlStr); - } - } - - private static void tryReprocessFailed(Context context, OpenURLTracker tracker) throws SQLException { - boolean success = false; - URLConnection conn; - try { - URL url = new URL(tracker.getUrl()); - conn = url.openConnection(); - - if (((HttpURLConnection) conn).getResponseCode() == HttpURLConnection.HTTP_OK) { - success = true; - } - } catch (Exception e) { - success = false; - } finally { - if (success) { - OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService() - .remove(context, tracker); - // If the tracker was able to post successfully, we remove it from the database - log.info("Successfully posted " + tracker.getUrl() + " from " + tracker.getUploadDate()); - } else { - // Still no luck - write an error msg but keep the entry in the table for future executions - log.error("Failed attempt from " + tracker.getUrl() + " originating from " + tracker.getUploadDate()); - } - } - } - - public static void reprocessFailedQueue(Context context) throws SQLException { - Context c = new Context(); - OpenURLTrackerLoggerServiceFactory instance = OpenURLTrackerLoggerServiceFactory.getInstance(); - if (instance == null) { - log.error("Error retrieving the \"OpenURLTrackerLoggerServiceFactory\" instance, aborting the processing"); - return; - } - OpenURLTrackerLoggerService openUrlTrackerLoggerService = instance.getOpenUrlTrackerLoggerService(); - if (openUrlTrackerLoggerService == null) { - log.error("Error retrieving the \"openUrlTrackerLoggerService\" instance, aborting the processing"); - return; - } - List openURLTrackers = openUrlTrackerLoggerService.findAll(c); - for (OpenURLTracker openURLTracker : openURLTrackers) { - ExportUsageEventListener.tryReprocessFailed(context, openURLTracker); - } - - try { - c.abort(); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - public static void logfailed(Context context, String url) throws SQLException { - Date now = new Date(); - if (url.equals("")) { - return; - } - OpenURLTrackerLoggerService service = OpenURLTrackerLoggerServiceFactory.getInstance() - .getOpenUrlTrackerLoggerService(); - OpenURLTracker tracker = service.create(context); - tracker.setUploadDate(now); - tracker.setUrl(url); - // TODO service tracker update - } - - private static MetadataField resolveConfigPropertyToMetadataField(Context context, String fieldName) - throws SQLException { - String metadataField = configurationService.getProperty("stats." + fieldName); - if (metadataField != null && 0 < metadataField.trim().length()) { - metadataField = metadataField.trim(); - MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - return metadataFieldService - .findByElement(context, metadataField.split("\\.")[0], metadataField.split("\\.")[1], - metadataField.split("\\.").length == 2 ? null : metadataField.split("\\.")[2]); - } - return null; - } } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java index ba23d9e1c6..6005b6a02f 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java @@ -22,7 +22,7 @@ import org.dspace.core.ReloadableEntity; import org.hibernate.proxy.HibernateProxyHelper; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * Class that represents an OpenURLTracker which tracks a failed transmission to IRUS */ @Entity @Table(name = "OpenUrlTracker") @@ -42,32 +42,54 @@ public class OpenURLTracker implements ReloadableEntity { private Date uploadDate; protected OpenURLTracker() { - } + /** + * Gets the OpenURLTracker id + * @return the id + */ @Override public Integer getID() { return id; } - + /** + * Gets the OpenURLTracker url + * @return the url + */ public String getUrl() { return url; } + /** + * Sets the OpenURLTracker url + * @param url + */ public void setUrl(String url) { this.url = url; } + /** + * Returns the upload date + * @return upload date + */ public Date getUploadDate() { return uploadDate; } + /** + * Set the upload date + * @param uploadDate + */ public void setUploadDate(Date uploadDate) { this.uploadDate = uploadDate; } - + /** + * Determines whether two objects of this class are equal by comparing the ID + * @param o - object to compare + * @return whether the objects are equal + */ @Override public boolean equals(Object o) { if (this == o) { @@ -86,6 +108,10 @@ public class OpenURLTracker implements ReloadableEntity { return true; } + /** + * Returns the hash code value for the object + * @return hash code + */ @Override public int hashCode() { int hash = 8; diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java index c957b4c7a0..ac930b1c16 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java @@ -16,23 +16,41 @@ import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; import org.springframework.beans.factory.annotation.Autowired; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * Implementation of the service that handles the OpenURLTracker database operations */ public class OpenURLTrackerLoggerServiceImpl implements OpenURLTrackerLoggerService { @Autowired(required = true) protected OpenURLTrackerDAO openURLTrackerDAO; + /** + * Removes an OpenURLTracker from the database + * @param context + * @param openURLTracker + * @throws SQLException + */ @Override public void remove(Context context, OpenURLTracker openURLTracker) throws SQLException { openURLTrackerDAO.delete(context, openURLTracker); } + /** + * Returns all OpenURLTrackers from the database + * @param context + * @return all OpenURLTrackers + * @throws SQLException + */ @Override public List findAll(Context context) throws SQLException { return openURLTrackerDAO.findAll(context, OpenURLTracker.class); } + /** + * Creates a new OpenURLTracker + * @param context + * @return the creatred OpenURLTracker + * @throws SQLException + */ @Override public OpenURLTracker create(Context context) throws SQLException { OpenURLTracker openURLTracker = openURLTrackerDAO.create(context, new OpenURLTracker()); diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java index 6427c27433..cc2030c446 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java @@ -13,8 +13,13 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; +import org.dspace.statistics.export.service.OpenUrlService; - +/** + * Script to retry the failed url transmissions to IRUS + * This script also has an option to add new failed urls for testing purposes + */ public class RetryOpenUrlTracker extends DSpaceRunnable { private static final Logger log = Logger.getLogger(RetryOpenUrlTracker.class); @@ -22,6 +27,14 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { private String lineToAdd = null; private boolean help = false; + private OpenUrlService openUrlService; + + /** + * Run the script + * When the -a option is used, a new "failed" url will be added to the database + * + * @throws Exception + */ public void internalRun() throws Exception { if (help) { printHelp(); @@ -30,10 +43,10 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { context.turnOffAuthorisationSystem(); if (StringUtils.isNotBlank(lineToAdd)) { - ExportUsageEventListener.logfailed(context, lineToAdd); + openUrlService.logfailed(context, lineToAdd); log.info("Created dummy entry in OpenUrlTracker with URL: " + lineToAdd); } else { - ExportUsageEventListener.reprocessFailedQueue(context); + openUrlService.reprocessFailedQueue(context); } context.restoreAuthSystemState(); try { @@ -43,8 +56,17 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { } } + /** + * Setups the parameters + * + * @throws ParseException + */ public void setup() throws ParseException { context = new Context(); + openUrlService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlService(); + + lineToAdd = null; + help = false; if (commandLine.hasOption('h')) { help = true; @@ -59,6 +81,11 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { this.options = options; } + /** + * Constructs the script options + * + * @return the constructed options + */ private Options constructOptions() { Options options = new Options(); diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java b/dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java deleted file mode 100644 index 1f1211470e..0000000000 --- a/dspace-api/src/main/java/org/dspace/statistics/export/SpiderDetector.java +++ /dev/null @@ -1,289 +0,0 @@ -/** - * 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.statistics.export; - -import java.io.BufferedReader; -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; - -import org.apache.log4j.Logger; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.statistics.factory.StatisticsServiceFactory; -import org.dspace.statistics.util.IPTable; -import org.dspace.statistics.util.SpiderDetectorService; - -/** - * SpiderDetector is used to find IP's that are spiders... - * In future someone may add UserAgents and Host Domains - * to the detection criteria here. - * - * @author kevinvandevelde at atmire.com - * @author ben at atmire.com - * @author Mark Diggory (mdiggory at atmire.com) - * @author Kevin Van Ransbeeck at atmire.com - */ -public class SpiderDetector { - - private static final Logger log = Logger.getLogger(SpiderDetector.class); - - //Service where all methods get delegated to, this is instantiated by a spring-bean defined in core-services.xml - private static SpiderDetectorService spiderDetectorService = StatisticsServiceFactory.getInstance() - .getSpiderDetectorService(); - - private SpiderDetector() { } - - /** - * Sparse HAshTable structure to hold IP Address Ranges. - */ - private static IPTable table = null; - private static Set spidersRegex = Collections.synchronizedSet(new HashSet()); - private static Set spidersMatched = null; - - /** - * Utility method which Reads the ip addresses out a file & returns them in a Set - * - * @param spiderIpFile the location of our spider file - * @return a vector full of ip's - * @throws IOException could not happen since we check the file be4 we use it - */ - public static Set readIpAddresses(File spiderIpFile) throws IOException { - Set ips = new HashSet<>(); - - if (!spiderIpFile.exists() || !spiderIpFile.isFile()) { - return ips; - } - - //Read our file & get all them ip's - try (BufferedReader in = new BufferedReader(new FileReader(spiderIpFile))) { - String line; - while ((line = in.readLine()) != null) { - if (!line.startsWith("#")) { - line = line.trim(); - - if (!line.equals("") && !Character.isDigit(line.charAt(0))) { - // is a hostname - // add this functionality later... - } else if (!line.equals("")) { - ips.add(line); - // is full v4 ip (too tired to deal with v6)... - } - } else { - // ua.add(line.replaceFirst("#","").replaceFirst("UA","").trim()); - // ... add this functionality later - } - } - } - return ips; - } - - /** - * Get an immutable Set representing all the Spider Addresses here - * - * @return Set setOfIpAddresses - */ - public static Set getSpiderIpAddresses() { - loadSpiderIpAddresses(); - return table.toSet(); - } - - /* - private loader to populate the table from files. - */ - - private static synchronized void loadSpiderIpAddresses() { - if (table == null) { - table = new IPTable(); - - String filePath = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir"); - try { - File spidersDir = new File(filePath, "config/spiders"); - - if (spidersDir.exists() && spidersDir.isDirectory()) { - for (File file : spidersDir.listFiles()) { - for (String ip : readIpAddresses(file)) { - table.add(ip); - } - log.info("Loaded Spider IP file: " + file); - } - } else { - log.info("No spider file loaded"); - } - } catch (Exception e) { - log.error("Error Loading Spiders:" + e.getMessage(), e); - } - } - } - - /** - * Static Service Method for testing spiders against existing spider files. - *

- * In the future this will be extended to support User Agent and - * domain Name detection. - *

- * In future spiders HashSet may be optimized as byte offset array to - * improve performance and memory footprint further. - * - * @param request - * @return true|false if the request was detected to be from a spider - */ - public static boolean isSpider(HttpServletRequest request) { - /* - * 1) If the IP address matches the spider IP addresses (this is the current implementation) - */ - boolean checkSpidersIP = DSpaceServicesFactory.getInstance().getConfigurationService() - .getPropertyAsType("stats.spider.ipmatch.enabled", true, true); - if (checkSpidersIP) { - if (StatisticsServiceFactory.getInstance().getSolrLoggerService().isUseProxies() && request - .getHeader("X-Forwarded-For") != null) { - /* This header is a comma delimited list */ - for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { - if (isSpider(xfip)) { - log.debug("spider.ipmatch"); - return true; - } - } - } else if (isSpider(request.getRemoteAddr())) { - log.debug("spider.ipmatch"); - return true; - } - } - /* - * 2) if the user-agent header is empty - DISABLED BY DEFAULT - - */ - boolean checkSpidersEmptyAgent = DSpaceServicesFactory.getInstance().getConfigurationService() - .getPropertyAsType("stats.spider.agentempty.enabled", - false, true); - if (checkSpidersEmptyAgent) { - if (request.getHeader("user-agent") == null || request.getHeader("user-agent").length() == 0) { - log.debug("spider.agentempty"); - return true; - } - } - /* - * 3) if the user-agent corresponds to one of the regexes at http://www.projectcounter - * .org/r4/COUNTER_robot_txt_list_Jan_2011.txt - */ - boolean checkSpidersTxt = DSpaceServicesFactory.getInstance().getConfigurationService() - .getPropertyAsType("stats.spider.agentregex.enabled", true, - true); - if (checkSpidersTxt) { - String userAgent = request.getHeader("user-agent"); - - if (userAgent != null && !userAgent.equals("")) { - return isSpiderRegex(userAgent); - } - } - return false; - } - - /** - * Check individual IP is a spider. - * - * @param ip - * @return if is spider IP - */ - public static boolean isSpider(String ip) { - if (table == null) { - spiderDetectorService.loadSpiderIpAddresses(); - } - - try { - if (table.contains(ip)) { - return true; - } - } catch (Exception e) { - return false; - } - - return false; - } - - /** - * Checks the user-agent string vs a set of known regexes from spiders - * A second Set is kept for fast-matching. - * If a user-agent is matched once, it is added to this set with "known agents". - * If this user-agent comes back later, we can do a quick lookup in this set, - * instead of having to loop over the entire set with regexes again. - * - * @param userAgent String - * @return true if the user-agent matches a regex - */ - public static boolean isSpiderRegex(String userAgent) { - if (spidersMatched != null && spidersMatched.contains(userAgent)) { - log.debug("spider.agentregex"); - return true; - } else { - synchronized (spidersRegex) { - if (spidersRegex.isEmpty()) { - loadSpiderRegexFromFile(); - } - } - - if (spidersRegex != null) { - for (Object regex : spidersRegex.toArray()) { - Matcher matcher = ((Pattern) regex).matcher(userAgent); - if (matcher.find()) { - if (spidersMatched == null) { - spidersMatched = new HashSet<>(); - } - if (spidersMatched.size() >= 100) { - spidersMatched.clear(); - } - spidersMatched.add(userAgent); - log.debug("spider.agentregex"); - return true; - } - } - } - return false; - } - } - - /** - * Populate static Set spidersRegex from local txt file. - * Original file downloaded from http://www.projectcounter.org/r4/COUNTER_robot_txt_list_Jan_2011.txt during build - */ - public static void loadSpiderRegexFromFile() { - String spidersTxt = DSpaceServicesFactory.getInstance().getConfigurationService() - .getPropertyAsType("stats.spider.agentregex.regexfile", String.class); - DataInputStream in = null; - try { - FileInputStream fstream = new FileInputStream(spidersTxt); - in = new DataInputStream(fstream); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - String strLine; - while ((strLine = br.readLine()) != null) { - spidersRegex.add(Pattern.compile(strLine, Pattern.CASE_INSENSITIVE)); - } - log.info("Loaded Spider Regex file: " + spidersTxt); - } catch (FileNotFoundException e) { - log.error("File with spiders regex not found @ " + spidersTxt); - } catch (IOException e) { - log.error("Could not read from file " + spidersTxt); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - log.error("Could not close file " + spidersTxt); - } - } - } -} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java index 6668b8ee48..884d1b2f89 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java @@ -9,14 +9,23 @@ package org.dspace.statistics.export.factory; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.OpenUrlService; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * The service factory for the OpenUrlTracker related services */ public abstract class OpenURLTrackerLoggerServiceFactory { + /** + * Returns the OpenURLTrackerLoggerService + * @return OpenURLTrackerLoggerService instance + */ public abstract OpenURLTrackerLoggerService getOpenUrlTrackerLoggerService(); + /** + * Retrieve the OpenURLTrackerLoggerServiceFactory + * @return OpenURLTrackerLoggerServiceFactory instance + */ public static OpenURLTrackerLoggerServiceFactory getInstance() { return DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("openURLTrackerLoggerServiceFactory", @@ -24,4 +33,9 @@ public abstract class OpenURLTrackerLoggerServiceFactory { } + /** + * Returns the OpenUrlService + * @return OpenUrlService instance + */ + public abstract OpenUrlService getOpenUrlService(); } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java index 9a04bf20d3..1b3efd85c0 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java @@ -8,18 +8,35 @@ package org.dspace.statistics.export.factory; import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.OpenUrlService; import org.springframework.beans.factory.annotation.Autowired; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * The service factory implementation for the OpenUrlTracker related services */ public class OpenURLTrackerLoggerServiceFactoryImpl extends OpenURLTrackerLoggerServiceFactory { @Autowired(required = true) private OpenURLTrackerLoggerService openURLTrackerLoggerService; + @Autowired(required = true) + private OpenUrlService openUrlService; + + /** + * Returns the OpenURLTrackerLoggerService + * @return OpenURLTrackerLoggerService instance + */ @Override public OpenURLTrackerLoggerService getOpenUrlTrackerLoggerService() { return openURLTrackerLoggerService; } + + /** + * Returns the OpenUrlService + * @return OpenUrlService instance + */ + @Override + public OpenUrlService getOpenUrlService() { + return openUrlService; + } } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java new file mode 100644 index 0000000000..5f8447bed0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java @@ -0,0 +1,141 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.export.processor; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.sql.SQLException; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.util.Util; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.statistics.util.SpiderDetector; + +/** + * Processor that handles Bitstream events from the ExportUsageEventListener + */ +public class BitstreamEventProcessor extends ExportEventProcessor { + + private Item item; + private Bitstream bitstream; + + /** + * Creates a new BitstreamEventProcessor that will set the params and obtain the parent item of the bitstream + * @param context + * @param request + * @param bitstream + * @throws SQLException + */ + public BitstreamEventProcessor(Context context, HttpServletRequest request, Bitstream bitstream) + throws SQLException { + super(context, request); + this.bitstream = bitstream; + this.item = getItem(); + } + + /** + * Returns the parent item of the bitsream + * @return parent item of the bitstream + * @throws SQLException + */ + private Item getItem() throws SQLException { + if (0 < bitstream.getBundles().size()) { + if (!SpiderDetector.isSpider(request)) { + Bundle bundle = bitstream.getBundles().get(0); + if (bundle.getName() == null || !bundle.getName().equals("ORIGINAL")) { + return null; + } + + if (0 < bundle.getItems().size()) { + Item item = bundle.getItems().get(0); + return item; + } + } + } + return null; + } + + /** + * Process the event + * Check if the item should be processed + * Create the url to be transmitted based on item and bitstream data + * @throws SQLException + * @throws IOException + */ + public void processEvent() throws SQLException, IOException { + if (shouldProcessItem(item)) { + String baseParam = getBaseParamaters(item); + String fullParam = addObjectSpecificData(baseParam, item, bitstream); + processObject(fullParam); + } + } + + /** + * Adds additional item and bitstream data to the url + * @param string to which the additional data needs to be added + * @param item + * @param bitstream + * @return the string with additional data + * @throws UnsupportedEncodingException + */ + protected String addObjectSpecificData(final String string, Item item, Bitstream bitstream) + throws UnsupportedEncodingException { + StringBuilder data = new StringBuilder(string); + + String bitstreamInfo = getBitstreamInfo(item, bitstream); + data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(bitstreamInfo, "UTF-8")); + data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(BITSTREAM_DOWNLOAD, "UTF-8")); + + return data.toString(); + } + + /** + * Get Bitstream info used for the url + * @param item + * @param bitstream + * @return bitstream info + */ + private String getBitstreamInfo(final Item item, final Bitstream bitstream) { + + StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); + + String identifier; + if (item != null && item.getHandle() != null) { + identifier = "handle/" + item.getHandle(); + } else if (item != null) { + identifier = "item/" + item.getID(); + } else { + identifier = "id/" + bitstream.getID(); + } + + + sb.append("/bitstream/").append(identifier).append("/"); + + // If we can, append the pretty name of the bitstream to the URL + try { + if (bitstream.getName() != null) { + sb.append(Util.encodeBitstreamName(bitstream.getName(), "UTF-8")); + } + } catch (UnsupportedEncodingException uee) { + // just ignore it, we don't have to have a pretty + // name at the end of the URL because the sequence id will + // locate it. However it means that links in this file might + // not work.... + } + + sb.append("?sequence=").append(bitstream.getSequenceID()); + + return sb.toString(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java new file mode 100644 index 0000000000..508b512343 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java @@ -0,0 +1,294 @@ +/** + * 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.statistics.export.processor; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.content.DCDate; +import org.dspace.content.Entity; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; +import org.dspace.statistics.export.service.OpenUrlService; + +/** + * Abstract export event processor that contains all shared logic to handle both Items and Bitstreams + * from the ExportUsageEventListener + */ +public abstract class ExportEventProcessor { + + private static Logger log = Logger.getLogger(ExportEventProcessor.class); + + /* The metadata field which is to be checked for */ + protected String trackerType; + + /* A list of entity types that will be processed */ + protected List entityTypes; + protected static final String ENTITY_TYPE_DEFAULT = "Publication"; + + /* A list of values the type might have */ + protected List trackerValues; + + /* The base url of the tracker */ + protected String baseUrl; + + protected String trackerUrlVersion; + + protected static final String ITEM_VIEW = "Investigation"; + protected static final String BITSTREAM_DOWNLOAD = "Request"; + + protected static ConfigurationService configurationService; + + protected static EntityTypeService entityTypeService; + protected static EntityService entityService; + + protected static OpenUrlService openUrlService; + + + protected Context context; + protected HttpServletRequest request; + protected ItemService itemService; + + /** + * Creates a new ExportEventProcessor based on the params and initializes the services + * + * @param context + * @param request + */ + ExportEventProcessor(Context context, HttpServletRequest request) { + this.context = context; + this.request = request; + initServices(); + } + + /** + * Processes the event + * + * @throws SQLException + * @throws IOException + */ + public abstract void processEvent() throws SQLException, IOException; + + /** + * Process the url obtained from the object to be transmitted + * + * @param urlParameters + * @throws IOException + * @throws SQLException + */ + protected void processObject(String urlParameters) throws IOException, SQLException { + + openUrlService.processUrl(context, baseUrl + "?" + urlParameters); + } + + /** + * Get the base parameters for the url to be transmitted + * + * @param item + * @return the parameter string to be used in the url + * @throws UnsupportedEncodingException + */ + protected String getBaseParamaters(Item item) + throws UnsupportedEncodingException { + + //We have a valid url collect the rest of the data + String clientIP = request.getRemoteAddr(); + if (configurationService.getBooleanProperty("useProxies", false) && request + .getHeader("X-Forwarded-For") != null) { + /* This header is a comma delimited list */ + for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { + /* proxy itself will sometime populate this header with the same value in + remote address. ordering in spec is vague, we'll just take the last + not equal to the proxy + */ + if (!request.getHeader("X-Forwarded-For").contains(clientIP)) { + clientIP = xfip.trim(); + } + } + } + String clientUA = StringUtils.defaultIfBlank(request.getHeader("USER-AGENT"), ""); + String referer = StringUtils.defaultIfBlank(request.getHeader("referer"), ""); + + //Start adding our data + StringBuilder data = new StringBuilder(); + data.append(URLEncoder.encode("url_ver", "UTF-8") + "=" + URLEncoder.encode(trackerUrlVersion, "UTF-8")); + data.append("&").append(URLEncoder.encode("req_id", "UTF-8")).append("=") + .append(URLEncoder.encode(clientIP, "UTF-8")); + data.append("&").append(URLEncoder.encode("req_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(clientUA, "UTF-8")); + data.append("&").append(URLEncoder.encode("rft.artnum", "UTF-8")).append("="). + append(URLEncoder.encode("oai:" + configurationService.getProperty("dspace.hostname") + ":" + item + .getHandle(), "UTF-8")); + data.append("&").append(URLEncoder.encode("rfr_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(referer, "UTF-8")); + data.append("&").append(URLEncoder.encode("rfr_id", "UTF-8")).append("=") + .append(URLEncoder.encode(configurationService.getProperty("dspace.hostname"), "UTF-8")); + data.append("&").append(URLEncoder.encode("url_tim", "UTF-8")).append("=") + .append(URLEncoder.encode(getCurrentDateString(), "UTF-8")); + + return data.toString(); + } + + /** + * Get the current date + * + * @return the current date as a string + */ + protected String getCurrentDateString() { + return new DCDate(new Date()).toString(); + } + + /** + * Checks if an item should be processed + * + * @param item to be checked + * @return whether the item should be processed + * @throws SQLException + */ + protected boolean shouldProcessItem(Item item) throws SQLException { + if (item == null) { + return false; + } + if (!item.isArchived()) { + return false; + } + if (itemService.canEdit(context, item)) { + return false; + } + if (!shouldProcessItemType(item)) { + return false; + } + if (!shouldProcessEntityType(item)) { + return false; + } + return true; + } + + /** + * Checks if the item's entity type should be processed + * + * @param item to be checked + * @return whether the item should be processed + * @throws SQLException + */ + protected boolean shouldProcessEntityType(Item item) throws SQLException { + Entity entity = entityService.findByItemId(context, item.getID()); + EntityType type = entityService.getType(context, entity); + + if (type != null && entityTypes.contains(type.getLabel())) { + return true; + } + return false; + } + + /** + * Checks if the item should be excluded based on the its type + * + * @param item to be checked + * @return whether the item should be processed + */ + protected boolean shouldProcessItemType(Item item) { + if (trackerType != null && trackerValues != null) { + List types = itemService + .getMetadata(item, trackerType.split("\\.")[0], trackerType.split("\\.")[1], + trackerType.split("\\.").length == 2 ? null : trackerType.split("\\.")[2], Item.ANY); + + if (!types.isEmpty()) { + //Find out if we have a type that needs to be excluded + for (MetadataValue type : types) { + if (trackerValues.contains(type.getValue().toLowerCase())) { + //We have found no type so process this item + return false; + } + } + return true; + } else { + // No types in this item, so not excluded + return true; + } + } else { + // No types to be excluded + return true; + } + } + + /** + * Initializes services and params obtained from DSpace config + */ + private void initServices() { + try { + if (configurationService == null) { + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + } + if (entityService == null) { + entityService = ContentServiceFactory.getInstance().getEntityService(); + } + if (itemService == null) { + itemService = ContentServiceFactory.getInstance().getItemService(); + } + if (entityTypeService == null) { + entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + } + if (openUrlService == null) { + openUrlService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlService(); + } + if (trackerType == null) { + trackerType = configurationService.getProperty("stats.tracker.type-field"); + + String[] metadataValues = configurationService.getArrayProperty("stats.tracker.type-value"); + if (metadataValues.length > 0) { + trackerValues = new ArrayList<>(); + for (String metadataValue : metadataValues) { + trackerValues.add(metadataValue.toLowerCase()); + } + } else { + trackerValues = null; + } + + if (StringUtils.equals(configurationService.getProperty("stats.tracker.environment"), "production")) { + baseUrl = configurationService.getProperty("stats.tracker.produrl"); + } else { + baseUrl = configurationService.getProperty("stats.tracker.testurl"); + } + + trackerUrlVersion = configurationService.getProperty("stats.tracker.urlversion"); + String[] entityTypeStrings = configurationService.getArrayProperty("stats.tracker.entity-types"); + entityTypes = new ArrayList<>(); + if (entityTypeStrings.length != 0) { + entityTypes.addAll(Arrays.asList(entityTypeStrings)); + } else { + entityTypes.add(ENTITY_TYPE_DEFAULT); + } + } + } catch (Exception e) { + log.error("Unknown error resolving configuration for the export usage event.", e); + trackerType = null; + trackerValues = null; + baseUrl = null; + trackerUrlVersion = null; + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java new file mode 100644 index 0000000000..8ef809da05 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java @@ -0,0 +1,83 @@ +/** + * 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.statistics.export.processor; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.sql.SQLException; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.content.Item; +import org.dspace.core.Context; + + +/** + * Processor that handles Item events from the ExportUsageEventListener + */ +public class ItemEventProcessor extends ExportEventProcessor { + + private Item item; + + /** + * Creates a new ItemEventProcessor that will set the params + * @param context + * @param request + * @param item + */ + public ItemEventProcessor(Context context, HttpServletRequest request, Item item) { + super(context, request); + this.item = item; + } + + /** + * Process the event + * Check if the item should be processed + * Create the url to be transmitted based on item data + * @throws SQLException + * @throws IOException + */ + public void processEvent() throws SQLException, IOException { + if (shouldProcessItem(item)) { + String baseParam = getBaseParamaters(item); + String fullParam = addObjectSpecificData(baseParam, item); + processObject(fullParam); + } + } + + /** + * Adds additional item data to the url + * @param string to which the additional data needs to be added + * @param item + * @return the string with additional data + * @throws UnsupportedEncodingException + */ + protected String addObjectSpecificData(final String string, Item item) throws UnsupportedEncodingException { + StringBuilder data = new StringBuilder(string); + String itemInfo = getItemInfo(item); + data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(itemInfo, "UTF-8")); + data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") + .append(URLEncoder.encode(ITEM_VIEW, "UTF-8")); + return data.toString(); + } + + /** + * Get Item info used for the url + * @param item + * @return item info + */ + private String getItemInfo(final Item item) { + StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); + sb.append("/handle/").append(item.getHandle()); + + return sb.toString(); + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java index 6b910f7120..46bc0b040b 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java @@ -14,13 +14,31 @@ import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * Interface of the service that handles the OpenURLTracker database operations */ public interface OpenURLTrackerLoggerService { + /** + * Removes an OpenURLTracker from the database + * @param context + * @param openURLTracker + * @throws SQLException + */ void remove(Context context, OpenURLTracker openURLTracker) throws SQLException; + /** + * Returns all OpenURLTrackers from the database + * @param context + * @return all OpenURLTrackers + * @throws SQLException + */ List findAll(Context context) throws SQLException; + /** + * Creates a new OpenURLTracker + * @param context + * @return the creatred OpenURLTracker + * @throws SQLException + */ OpenURLTracker create(Context context) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlService.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlService.java new file mode 100644 index 0000000000..0f8376edbf --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlService.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.export.service; + +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.core.Context; + +/** + * The Service responsible for processing urls to be send to IRUS + */ +public interface OpenUrlService { + /** + * Process the url + * @param c - the context + * @param urlStr - the url to be processed + * @throws IOException + * @throws SQLException + */ + void processUrl(Context c, String urlStr) throws SQLException; + + /** + * Will process all urls stored in the database and try contacting IRUS again + * @param context + * @throws SQLException + */ + void reprocessFailedQueue(Context context) throws SQLException; + + /** + * Will log the failed url in the database + * @param context + * @param url + * @throws SQLException + */ + void logfailed(Context context, String url) throws SQLException; + + +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java new file mode 100644 index 0000000000..54b72f169e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -0,0 +1,135 @@ +/** + * 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.statistics.export.service; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.core.Context; +import org.dspace.statistics.export.OpenURLTracker; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of the OpenUrlService interface + */ +public class OpenUrlServiceImpl implements OpenUrlService { + + private Logger log = Logger.getLogger(OpenUrlService.class); + + @Autowired + protected OpenURLTrackerLoggerService openUrlTrackerLoggerService; + + /** + * Processes the url + * When the contacting the url fails, the url will be logged in a db table + * @param c - the context + * @param urlStr - the url to be processed + * @throws SQLException + */ + public void processUrl(Context c, String urlStr) throws SQLException { + log.debug("Prepared to send url to tracker URL: " + urlStr); + + try { + int responseCode = getResponseCodeFromUrl(urlStr); + if (responseCode != HttpURLConnection.HTTP_OK) { + logfailed(c, urlStr); + } else if (log.isDebugEnabled()) { + log.debug("Successfully posted " + urlStr + " on " + new Date()); + } + } catch (Exception e) { + log.error("Failed to send url to tracker URL: " + urlStr); + logfailed(c, urlStr); + } + } + + /** + * Returns the response code from accessing the url + * @param urlStr + * @return response code from the url + * @throws IOException + */ + protected int getResponseCodeFromUrl(final String urlStr) throws IOException { + URLConnection conn; + URL url = new URL(urlStr); + conn = url.openConnection(); + + return ((HttpURLConnection) conn).getResponseCode(); + } + + /** + * Retry to send a failed url + * @param context + * @param tracker - db object containing the failed url + * @throws SQLException + */ + protected void tryReprocessFailed(Context context, OpenURLTracker tracker) throws SQLException { + boolean success = false; + try { + + int responseCode = getResponseCodeFromUrl(tracker.getUrl()); + + if (responseCode == HttpURLConnection.HTTP_OK) { + success = true; + } + } catch (Exception e) { + success = false; + } finally { + if (success) { + openUrlTrackerLoggerService + .remove(context, tracker); + // If the tracker was able to post successfully, we remove it from the database + log.info("Successfully posted " + tracker.getUrl() + " from " + tracker.getUploadDate()); + } else { + // Still no luck - write an error msg but keep the entry in the table for future executions + log.error("Failed attempt from " + tracker.getUrl() + " originating from " + tracker.getUploadDate()); + } + } + } + + /** + * Reprocess all url trackers present in the database + * @param context + * @throws SQLException + */ + public void reprocessFailedQueue(Context context) throws SQLException { + if (openUrlTrackerLoggerService == null) { + log.error("Error retrieving the \"openUrlTrackerLoggerService\" instance, aborting the processing"); + return; + } + List openURLTrackers = openUrlTrackerLoggerService.findAll(context); + for (OpenURLTracker openURLTracker : openURLTrackers) { + tryReprocessFailed(context, openURLTracker); + } + } + + /** + * Log a failed url in the database + * @param context + * @param url + * @throws SQLException + */ + public void logfailed(Context context, String url) throws SQLException { + Date now = new Date(); + if (StringUtils.isBlank(url)) { + return; + } + + OpenURLTracker tracker = openUrlTrackerLoggerService.create(context); + tracker.setUploadDate(now); + tracker.setUrl(url); + } + + +} diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml new file mode 100644 index 0000000000..d6e49b5655 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index b28d45ec18..456b1b3f57 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -13,4 +13,10 @@ + + + + + + diff --git a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java new file mode 100644 index 0000000000..74e2a56668 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java @@ -0,0 +1,36 @@ +/** + * 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.scripts.handler.impl; + +import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; + +/** + * This class will be used as a DSpaceRunnableHandler for the Tests so that we can stop the handler + * from calling System.exit() when a script would throw an exception + */ +public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler { + + private Exception exception = null; + + /** + * We're overriding this method so that we can stop the script from doing the System.exit() if + * an exception within the script is thrown + */ + @Override + public void handleException(String message, Exception e) { + exception = e; + } + + /** + * Generic getter for the exception + * @return the exception value of this TestDSpaceRunnableHandler + */ + public Exception getException() { + return exception; + } +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java new file mode 100644 index 0000000000..ced76d9299 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java @@ -0,0 +1,390 @@ +/** + * 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.statistics.export; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.AbstractIntegrationTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; +import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.usage.UsageEvent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +/** + * Test class for the ExportUsageEventListener + */ +public class ITExportUsageEventListener extends AbstractIntegrationTest { + + private static Logger log = Logger.getLogger(ITExportUsageEventListener.class); + + + protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); + protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + protected OpenURLTrackerLoggerService openURLTrackerLoggerService = + OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService(); + + protected ArrayList testProcessedUrls = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("testProcessedUrls", + ArrayList.class); + + @Mock + ExportUsageEventListener exportUsageEventListener = mock(ExportUsageEventListener.class, CALLS_REAL_METHODS); + + private Item item; + private Bitstream bitstream; + private EntityType entityType; + private Community community; + private Collection collection; + + + /** + * Initializes the test by setting up all objects needed to create a test item + */ + @Before() + public void init() { + super.init(); + context.turnOffAuthorisationSystem(); + try { + entityType = entityTypeService.create(context, "Publication"); + community = communityService.create(null, context); + collection = collectionService.create(context, community); + item = installItemService.installItem(context, workspaceItemService.create(context, collection, false)); + itemService.addMetadata(context, item, "relationship", "type", null, null, "Publication"); + File f = new File(testProps.get("test.bitstream").toString()); + bitstream = itemService.createSingleBitstream(context, new FileInputStream(f), item); + itemService.update(context, item); + + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + context.restoreAuthSystemState(); + } + } + + /** + * Clean up the created objects + * Empty the testProcessedUrls used to store succeeded urls + * Empty the database table where the failed urls are logged + */ + @After + public void destroy() { + try { + context.turnOffAuthorisationSystem(); + + itemService.delete(context, item); + collectionService.delete(context, collection); + communityService.delete(context, community); + entityTypeService.delete(context, entityType); + + + List all = openURLTrackerLoggerService.findAll(context); + for (OpenURLTracker tracker : all) { + openURLTrackerLoggerService.remove(context, tracker); + } + + entityType = null; + community = null; + collection = null; + item = null; + + // Clear the list of processedUrls + testProcessedUrls.clear(); + + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + context.restoreAuthSystemState(); + } + super.destroy(); + } + + /** + * Test whether the usage event of an item meeting all conditions is processed and succeeds + */ + @Test + public void testReceiveEventOnItemThatShouldBeProcessed() throws UnsupportedEncodingException, SQLException { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(item); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + exportUsageEventListener.receiveEvent(usageEvent); + + + List all = openURLTrackerLoggerService.findAll(context); + + + String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fhandle%2F" + URLEncoder + .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; + + boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); + + assertThat(testProcessedUrls.size(), is(1)); + assertThat(isMatch, is(true)); + assertThat(all.size(), is(0)); + + + } + + /** + * Test whether the usage event of an item meeting all conditions is processed but fails + */ + @Test + public void testReceiveEventOnItemThatShouldBeProcessedFailed() throws SQLException, UnsupportedEncodingException { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip-fail"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(item); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + exportUsageEventListener.receiveEvent(usageEvent); + + List all = openURLTrackerLoggerService.findAll(context); + + String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fhandle%2F" + URLEncoder + .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; + + boolean isMatch = matchesString(all.get(0).getUrl(), regex); + + assertThat(testProcessedUrls.size(), is(0)); + + assertThat(all.size(), is(1)); + assertThat(isMatch, is(true)); + } + + /** + * Test whether the usage event of an item that does not meet all conditions is not processed + */ + @Test + public void testReceiveEventOnItemThatShouldNotBeProcessed() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip-fail"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(item); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + itemService.clearMetadata(context, item, "relationship", "type", null, Item.ANY); + itemService.addMetadata(context, item, "relationship", "type", null, null, "OrgUnit"); + itemService.update(context, item); + + context.restoreAuthSystemState(); + + exportUsageEventListener.receiveEvent(usageEvent); + + List all = openURLTrackerLoggerService.findAll(context); + + + assertThat(testProcessedUrls.size(), is(0)); + assertThat(all.size(), is(0)); + } + + /** + * Test whether the usage event of a bitstream meeting all conditions is processed and succeeds + */ + @Test + public void testReceiveEventOnBitstreamThatShouldBeProcessed() throws SQLException, UnsupportedEncodingException { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(bitstream); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + exportUsageEventListener.receiveEvent(usageEvent); + + String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fbitstream%2Fhandle%2F" + + URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; + + boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); + + assertThat(testProcessedUrls.size(), is(1)); + assertThat(isMatch, is(true)); + + List all = openURLTrackerLoggerService.findAll(context); + assertThat(all.size(), is(0)); + } + + /** + * Test whether the usage event of a bitstream meeting all conditions is processed but fails + */ + @Test + public void testReceiveEventOnBitstreamThatShouldBeProcessedFail() throws UnsupportedEncodingException, + SQLException { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip-fail"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(bitstream); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + exportUsageEventListener.receiveEvent(usageEvent); + + List all = openURLTrackerLoggerService.findAll(context); + + String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fbitstream%2Fhandle%2F" + + URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; + + + boolean isMatch = matchesString(all.get(0).getUrl(), regex); + + assertThat(all.size(), is(1)); + assertThat(isMatch, is(true)); + assertThat(testProcessedUrls.size(), is(0)); + + } + + /** + * Test whether the usage event of a bitstream that does not meet all conditions is not processed + */ + @Test + public void testReceiveEventOnBitstreamThatShouldNotBeProcessed() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip-fail"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(bitstream); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + itemService.clearMetadata(context, item, "relationship", "type", null, Item.ANY); + itemService.addMetadata(context, item, "relationship", "type", null, null, "OrgUnit"); + itemService.update(context, item); + + context.restoreAuthSystemState(); + + exportUsageEventListener.receiveEvent(usageEvent); + + List all = openURLTrackerLoggerService.findAll(context); + + + assertThat(all.size(), is(0)); + assertThat(testProcessedUrls.size(), is(0)); + + } + + /** + * Test that an object that is not an Item or Bitstream is not processed + */ + @Test + public void testReceiveEventOnNonRelevantObject() throws SQLException { + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("client-ip-fail"); + when(request.getHeader(anyString())).thenReturn(null); + + UsageEvent usageEvent = mock(UsageEvent.class); + when(usageEvent.getObject()).thenReturn(community); + when(usageEvent.getRequest()).thenReturn(request); + when(usageEvent.getContext()).thenReturn(new Context()); + + exportUsageEventListener.receiveEvent(usageEvent); + + List all = openURLTrackerLoggerService.findAll(context); + + + assertThat(all.size(), is(0)); + assertThat(testProcessedUrls.size(), is(0)); + + } + + /** + * Method to test if a string matches a regex + * + * @param string + * @param regex + * @return whether the regex matches the string + */ + private boolean matchesString(String string, String regex) { + + Pattern p = Pattern.compile(regex); + + if (p.matcher(string).matches()) { + return true; + } + return false; + } + + +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java new file mode 100644 index 0000000000..f3f9fe294f --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java @@ -0,0 +1,170 @@ +/** + * 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.statistics.export; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.AbstractIntegrationTest; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.factory.ScriptServiceFactory; +import org.dspace.scripts.service.ScriptService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; +import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Class to test the RetryOpenUrlTracker + */ +@RunWith(MockitoJUnitRunner.class) +public class ITRetryOpenUrlTracker extends AbstractIntegrationTest { + + private static Logger log = Logger.getLogger(ITRetryOpenUrlTracker.class); + + + protected OpenURLTrackerLoggerService openURLTrackerLoggerService = + OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService(); + + protected ArrayList testProcessedUrls = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("testProcessedUrls", ArrayList.class); + + private ScriptService scriptService = ScriptServiceFactory.getInstance().getScriptService(); + + + /** + * Clean up the logged entries from the db after each test + */ + @After + @Override + public void destroy() { + try { + context.turnOffAuthorisationSystem(); + + List all = openURLTrackerLoggerService.findAll(context); + for (OpenURLTracker tracker : all) { + openURLTrackerLoggerService.remove(context, tracker); + } + + // Clear the list of processedUrls + testProcessedUrls.clear(); + + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + context.restoreAuthSystemState(); + } + super.destroy(); + } + + /** + * Test the mode of the script that allows the user to add a failed url to the database + * @throws Exception + */ + @Test + public void testAddNewFailedUrl() throws Exception { + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); + String urlToAdd = "test-failed-url"; + String[] args = {"-a", urlToAdd}; + + retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); + retryOpenUrlTracker.internalRun(); + + List all = openURLTrackerLoggerService.findAll(context); + + assertThat(testProcessedUrls.size(), is(0)); + assertThat(all.size(), is(1)); + assertThat(all.get(0).getUrl(), is(urlToAdd)); + } + + /** + * Test to check that all logged failed urls are reprocessed succesfully and removed from the db + * @throws Exception + */ + @Test + public void testReprocessAllUrls() throws Exception { + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); + String[] args = {}; + + OpenURLTracker tracker1 = openURLTrackerLoggerService.create(context); + tracker1.setUrl("test-url-1"); + OpenURLTracker tracker2 = openURLTrackerLoggerService.create(context); + tracker2.setUrl("test-url-2"); + OpenURLTracker tracker3 = openURLTrackerLoggerService.create(context); + tracker3.setUrl("test-url-3"); + + + retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); + retryOpenUrlTracker.internalRun(); + + List all = openURLTrackerLoggerService.findAll(context); + + assertThat(testProcessedUrls.size(), is(3)); + assertThat(testProcessedUrls.contains("test-url-1"), is(true)); + assertThat(testProcessedUrls.contains("test-url-2"), is(true)); + assertThat(testProcessedUrls.contains("test-url-3"), is(true)); + + assertThat(all.size(), is(0)); + } + + /** + * Test to check that the successful retries are removed, but the failed retries remain in the db + * @throws Exception + */ + @Test + public void testReprocessPartOfUrls() throws Exception { + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); + String[] args = {}; + + OpenURLTracker tracker1 = openURLTrackerLoggerService.create(context); + tracker1.setUrl("test-url-1"); + OpenURLTracker tracker2 = openURLTrackerLoggerService.create(context); + tracker2.setUrl("test-url-2-fail"); + OpenURLTracker tracker3 = openURLTrackerLoggerService.create(context); + tracker3.setUrl("test-url-3-fail"); + OpenURLTracker tracker4 = openURLTrackerLoggerService.create(context); + tracker4.setUrl("test-url-4-fail"); + OpenURLTracker tracker5 = openURLTrackerLoggerService.create(context); + tracker5.setUrl("test-url-5"); + + + retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); + retryOpenUrlTracker.internalRun(); + + List all = openURLTrackerLoggerService.findAll(context); + List storedTrackerUrls = new ArrayList<>(); + for (OpenURLTracker tracker : all) { + storedTrackerUrls.add(tracker.getUrl()); + } + + assertThat(testProcessedUrls.size(), is(2)); + assertThat(testProcessedUrls.contains("test-url-1"), is(true)); + assertThat(testProcessedUrls.contains("test-url-5"), is(true)); + + assertThat(all.size(), is(3)); + assertThat(storedTrackerUrls.contains("test-url-2-fail"), is(true)); + assertThat(storedTrackerUrls.contains("test-url-3-fail"), is(true)); + assertThat(storedTrackerUrls.contains("test-url-4-fail"), is(true)); + } + + +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java new file mode 100644 index 0000000000..afb72ee115 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java @@ -0,0 +1,85 @@ +/** + * 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.statistics.export; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.statistics.export.dao.OpenURLTrackerDAO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Class to test the OpenURLTrackerLoggerServiceImpl + */ +@RunWith(MockitoJUnitRunner.class) +public class OpenURLTrackerLoggerServiceImplTest { + + @InjectMocks + private OpenURLTrackerLoggerServiceImpl openURLTrackerLoggerService; + + @Mock + private Context context; + + @Mock + private OpenURLTracker openURLTracker; + + @Mock + private OpenURLTrackerDAO openURLTrackerDAO; + + /** + * Tests the remove method + * @throws SQLException + */ + @Test + public void testRemove() throws SQLException { + openURLTrackerLoggerService.remove(context, openURLTracker); + + Mockito.verify(openURLTrackerDAO, times(1)).delete(context, openURLTracker); + + } + + /** + * Tests the findAll method + * @throws SQLException + */ + @Test + public void testFindAll() throws SQLException { + List trackers = new ArrayList<>(); + + when(openURLTrackerDAO.findAll(context, OpenURLTracker.class)).thenReturn(trackers); + + assertEquals("TestFindAll 0", trackers, openURLTrackerLoggerService.findAll(context)); + } + + /** + * Tests the create method + * @throws SQLException + */ + @Test + public void testCreate() throws SQLException { + OpenURLTracker tracker = new OpenURLTracker(); + + when(openURLTrackerDAO.create(any(), any())).thenReturn(tracker); + + assertEquals("TestCreate 0", tracker, openURLTrackerLoggerService.create(context)); + } + + +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java new file mode 100644 index 0000000000..12a693b3d8 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java @@ -0,0 +1,100 @@ +/** + * 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.statistics.export.processor; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** + * Test class for the BitstreamEventProcessor + */ +public class BitstreamEventProcessorTest extends AbstractDSpaceTest { + + @Mock + private Item item = mock(Item.class); + + @Mock + private Bitstream bitstream = mock(Bitstream.class); + + @Mock + private ConfigurationService configurationService = mock(ConfigurationService.class); + + @InjectMocks + BitstreamEventProcessor bitstreamEventProcessor = mock(BitstreamEventProcessor.class, CALLS_REAL_METHODS); + + @Test + /** + * Test the method that adds data based on the object types + */ + public void testAddObectSpecificData() throws UnsupportedEncodingException { + bitstreamEventProcessor.configurationService = configurationService; + when(configurationService.getProperty(any(String.class))).thenReturn("demo.dspace.org"); + + when(item.getHandle()).thenReturn("123456789/1"); + + String result = bitstreamEventProcessor.addObjectSpecificData("existing-string", item, bitstream); + + assertThat(result, + is("existing-string&svc_dat=demo.dspace.org%2Fbitstream%2Fhandle%2F123456789%2F1%2F%3Fsequence%3D0" + + "&rft_dat=Request")); + + } + + @Test + /** + * Test the method that adds data based on the object types when no handle can be found for the item + */ + public void testAddObectSpecificDataWhenNoHandle() throws UnsupportedEncodingException { + bitstreamEventProcessor.configurationService = configurationService; + when(configurationService.getProperty(any(String.class))).thenReturn("demo.dspace.org"); + + when(item.getHandle()).thenReturn(null); + when(item.getID()).thenReturn(UUID.fromString("d84c8fa8-50e2-4267-98f4-00954ea89c94")); + + String result = bitstreamEventProcessor.addObjectSpecificData("existing-string", item, bitstream); + + assertThat(result, + is("existing-string&svc_dat=demo.dspace" + + ".org%2Fbitstream%2Fitem%2Fd84c8fa8-50e2-4267-98f4-00954ea89c94%2F%3Fsequence%3D0" + + "&rft_dat=Request")); + + } + + @Test + /** + * Test the method that adds data based on the object types when no item is present + */ + public void testAddObectSpecificDataWhenNoItem() throws UnsupportedEncodingException { + bitstreamEventProcessor.configurationService = configurationService; + when(configurationService.getProperty(any(String.class))).thenReturn("demo.dspace.org"); + + when(bitstream.getID()).thenReturn(UUID.fromString("1a0c4e40-969d-467b-8f01-9b7edab8fd1a")); + String result = bitstreamEventProcessor.addObjectSpecificData("existing-string", null, bitstream); + + assertThat(result, is("existing-string&svc_dat=demo.dspace.org%2Fbitstream%2Fid%2F" + + "1a0c4e40-969d-467b-8f01-9b7edab8fd1a%2F%3Fsequence%3D0&rft_dat=Request")); + + } + + +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java new file mode 100644 index 0000000000..2f5699beb0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java @@ -0,0 +1,336 @@ +/** + * 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.statistics.export.processor; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.content.Entity; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** + * Test for the ExportEventProcessor class + */ +public class ExportEventProcessorTest extends AbstractDSpaceTest { + + @Mock + private Context context = mock(Context.class); + @Mock + private HttpServletRequest request = mock(HttpServletRequest.class); + @Mock + private Item item = mock(Item.class); + @Mock + private ConfigurationService configurationService = mock(ConfigurationService.class); + @Mock + private ItemService itemService = mock(ItemService.class); + @Mock + private EntityService entityService = mock(EntityService.class); + + + @InjectMocks + ExportEventProcessor exportEventProcessor = mock(ExportEventProcessor.class); + + + @Test + /** + * Test the getBaseParameters method + */ + public void testGetBaseParameters() throws UnsupportedEncodingException { + exportEventProcessor.context = context; + exportEventProcessor.request = request; + exportEventProcessor.configurationService = configurationService; + exportEventProcessor.trackerUrlVersion = "Z39.88-2004"; + + when(exportEventProcessor.getCurrentDateString()).thenReturn("2020-01-24T13:24:33Z"); + + when(request.getRemoteAddr()).thenReturn("test-client-ip"); + when(request.getHeader("USER-AGENT")).thenReturn("test-user-agent"); + when(request.getHeader("referer")).thenReturn("test-referer"); + when(configurationService.getBooleanProperty("useProxies", false)).thenReturn(false); + when(configurationService.getProperty("dspace.hostname")).thenReturn("localhost"); + + when(item.getHandle()).thenReturn("123456/1"); + + when(exportEventProcessor.getBaseParamaters(item)).thenCallRealMethod(); + String result = exportEventProcessor.getBaseParamaters(item); + String expected = "url_ver=Z39.88-2004&req_id=test-client-ip&req_dat=test-user-agent&rft.artnum=" + + "oai%3Alocalhost%3A123456%2F1&rfr_dat=test-referer&rfr_id=localhost&url_tim=2020-01-24T13%3A24%3A33Z"; + + assertThat(result, is(expected)); + + + } + + @Test + /** + * Test the ShouldProcessItem method where the item is null + */ + public void testShouldProcessItemWhenNull() throws SQLException { + exportEventProcessor.itemService = itemService; + exportEventProcessor.context = context; + + when(item.isArchived()).thenReturn(true); + when(itemService.canEdit(context, item)).thenReturn(false); + when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); + when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); + + when(exportEventProcessor.shouldProcessItem(null)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItem(null); + assertThat(result, is(false)); + } + + @Test + /** + * Test the ShouldProcessItem method where the item is not archived + */ + public void testShouldProcessItemWhenNotArchived() throws SQLException { + exportEventProcessor.itemService = itemService; + exportEventProcessor.context = context; + + when(itemService.canEdit(context, item)).thenReturn(false); + when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); + when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); + + when(item.isArchived()).thenReturn(false); + + when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItem(item); + assertThat(result, is(false)); + } + + @Test + /** + * Test the ShouldProcessItem method where the item can be edit by the current user + */ + public void testShouldProcessItemWhenCanEdit() throws SQLException { + exportEventProcessor.itemService = itemService; + exportEventProcessor.context = context; + + when(item.isArchived()).thenReturn(true); + when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); + when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); + + when(itemService.canEdit(context, item)).thenReturn(true); + + when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItem(item); + assertThat(result, is(false)); + + } + + @Test + /** + * Test the ShouldProcessItem method where the item type should be excluded + */ + public void testShouldProcessItemWhenShouldNotProcessType() throws SQLException { + exportEventProcessor.itemService = itemService; + exportEventProcessor.context = context; + + when(item.isArchived()).thenReturn(true); + when(itemService.canEdit(context, item)).thenReturn(false); + when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); + + when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(false); + + when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItem(item); + assertThat(result, is(false)); + + } + + @Test + /** + * Test the ShouldProcessItem method where the item entity type should not be processed + */ + public void testShouldProcessItemWhenShouldNotProcessEntity() throws SQLException { + exportEventProcessor.itemService = itemService; + exportEventProcessor.context = context; + + when(item.isArchived()).thenReturn(true); + when(itemService.canEdit(context, item)).thenReturn(false); + when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); + + when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(false); + + when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItem(item); + assertThat(result, is(false)); + + } + + @Test + /** + * Test the ShouldProcessItem method where all conditions are met + */ + public void testShouldProcessItem() throws SQLException { + exportEventProcessor.itemService = itemService; + exportEventProcessor.context = context; + + when(item.isArchived()).thenReturn(true); + when(itemService.canEdit(context, item)).thenReturn(false); + when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); + when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); + + + when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItem(item); + assertThat(result, is(true)); + + } + + + @Test + /** + * Test the ShouldProcessEntityType method where all conditions are met + */ + public void testShouldProcessEntityType() throws SQLException { + exportEventProcessor.entityService = entityService; + + + String entityType1 = "entityType1"; + String entityType2 = "entityType2"; + + Entity entity = mock(Entity.class); + EntityType itemEntityType = mock(EntityType.class); + + List entityTypeList = new ArrayList<>(); + entityTypeList.add(entityType1); + entityTypeList.add(entityType2); + + exportEventProcessor.entityTypes = entityTypeList; + + when(item.getID()).thenReturn(UUID.fromString("e22a97f0-f320-4277-aff6-fdb254a751ce")); + when(entityService.findByItemId(any(Context.class), any(UUID.class))) + .thenReturn(entity); + when(entityService.getType(any(Context.class), any(Entity.class))).thenReturn(itemEntityType); + when(itemEntityType.getLabel()).thenReturn(entityType1); + + when(exportEventProcessor.shouldProcessEntityType(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessEntityType(item); + + assertThat(result, is(true)); + } + + @Test + /** + * Test the ShouldProcessEntityType method where the item entity type is not present in the configured list + */ + public void testShouldProcessEntityTypeWhenNotInList() throws SQLException { + exportEventProcessor.entityService = entityService; + + + String entityType1 = "entityType1"; + String entityType2 = "entityType2"; + String entityType3 = "entityType3"; + + Entity entity = mock(Entity.class); + EntityType itemEntityType = mock(EntityType.class); + + List entityTypeList = new ArrayList<>(); + entityTypeList.add(entityType1); + entityTypeList.add(entityType2); + + exportEventProcessor.entityTypes = entityTypeList; + + when(item.getID()).thenReturn(UUID.fromString("e22a97f0-f320-4277-aff6-fdb254a751ce")); + when(entityService.findByItemId(any(Context.class), any(UUID.class))) + .thenReturn(entity); + when(entityService.getType(any(Context.class), any(Entity.class))).thenReturn(itemEntityType); + when(itemEntityType.getLabel()).thenReturn(entityType3); + + when(exportEventProcessor.shouldProcessEntityType(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessEntityType(item); + + assertThat(result, is(false)); + + } + + + @Test + /** + * Test the shouldProcessItemType method where the item type is present in the list of excluded types + */ + public void testShouldProcessItemTypeInExcludeTrackerTypeList() { + exportEventProcessor.itemService = itemService; + + String itemField = "dc.type"; + + exportEventProcessor.trackerType = itemField; + + List typeList = new ArrayList<>(); + typeList.add("type1"); + typeList.add("type2"); + exportEventProcessor.trackerValues = typeList; + + MetadataValue metadataValue = mock(MetadataValue.class); + when(metadataValue.getValue()).thenReturn("type2"); + List values = new ArrayList<>(); + values.add(metadataValue); + + when(itemService.getMetadata(any(Item.class), any(String.class), any(String.class), any(String.class), + any(String.class))).thenReturn(values); + + + when(exportEventProcessor.shouldProcessItemType(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItemType(item); + assertThat(result, is(false)); + + } + + @Test + /** + * Test the shouldProcessItemType method where the item type is not present in the list of excluded types + */ + public void testShouldProcessItemTypeNotInExcludeTrackerTypeList() { + exportEventProcessor.itemService = itemService; + + String itemField = "dc.type"; + + exportEventProcessor.trackerType = itemField; + + List typeList = new ArrayList<>(); + typeList.add("type1"); + typeList.add("type2"); + exportEventProcessor.trackerValues = typeList; + + MetadataValue metadataValue = mock(MetadataValue.class); + when(metadataValue.getValue()).thenReturn("type3"); + List values = new ArrayList<>(); + values.add(metadataValue); + + when(itemService.getMetadata(any(Item.class), any(String.class), any(String.class), any(String.class), + any(String.class))).thenReturn(values); + + + when(exportEventProcessor.shouldProcessItemType(item)).thenCallRealMethod(); + boolean result = exportEventProcessor.shouldProcessItemType(item); + assertThat(result, is(true)); + + } + +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ItemEventProcessorTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ItemEventProcessorTest.java new file mode 100644 index 0000000000..df572de69e --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ItemEventProcessorTest.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.statistics.export.processor; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.UnsupportedEncodingException; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** + * Test class for the ItemEventProcessor + */ +public class ItemEventProcessorTest extends AbstractDSpaceTest { + + @Mock + private Item item = mock(Item.class); + @Mock + private ConfigurationService configurationService = mock(ConfigurationService.class); + + @InjectMocks + ItemEventProcessor itemEventProcessor = mock(ItemEventProcessor.class, CALLS_REAL_METHODS); + + @Test + /** + * Test the method that adds data based on the object types + */ + public void testAddObectSpecificData() throws UnsupportedEncodingException { + itemEventProcessor.configurationService = configurationService; + when(configurationService.getProperty(any(String.class))).thenReturn("demo.dspace.org"); + + when(item.getHandle()).thenReturn("123456789/1"); + + String result = itemEventProcessor.addObjectSpecificData("existing-string", item); + + assertThat(result, + is("existing-string&svc_dat=demo.dspace.org%2Fhandle%2F123456789%2F1&rft_dat=Investigation")); + + } + + +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java new file mode 100644 index 0000000000..14ac9d36d5 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java @@ -0,0 +1,41 @@ +/** + * 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.statistics.export.service; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.ArrayList; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Mock OpenUrlService that will ensure that IRUS tracker does need to be contacted in order to test the functionality + */ +public class MockOpenUrlServiceImpl extends OpenUrlServiceImpl { + + @Autowired + ArrayList testProcessedUrls; + + /** + * Returns a response code to simulate contact to the external url + * When the url contains "fail", a fail code 500 will be returned + * Otherwise the success code 200 will be returned + * @param urlStr + * @return 200 or 500 depending on whether the "fail" keyword is present in the url + * @throws IOException + */ + protected int getResponseCodeFromUrl(final String urlStr) throws IOException { + if (StringUtils.contains(urlStr, "fail")) { + return HttpURLConnection.HTTP_INTERNAL_ERROR; + } else { + testProcessedUrls.add(urlStr); + return HttpURLConnection.HTTP_OK; + } + } +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java new file mode 100644 index 0000000000..5f1a648ba6 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -0,0 +1,136 @@ +/** + * 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.statistics.export.service; + +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.statistics.export.OpenURLTracker; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Test class for the OpenUrlServiceImpl + */ +@RunWith(MockitoJUnitRunner.class) +public class OpenUrlServiceImplTest { + + @InjectMocks + @Spy + private OpenUrlServiceImpl openUrlService; + + @Mock + private OpenURLTrackerLoggerService openURLTrackerLoggerService; + + /** + * Test the processUrl method + * @throws IOException + * @throws SQLException + */ + @Test + public void testProcessUrl() throws IOException, SQLException { + Context context = mock(Context.class); + + doReturn(HttpURLConnection.HTTP_OK).when(openUrlService) + .getResponseCodeFromUrl(anyString()); + doNothing().when(openUrlService).logfailed(any(Context.class), anyString()); + + openUrlService.processUrl(context, "test-url"); + + verify(openUrlService, times(0)).logfailed(context, "test-url"); + + + } + + /** + * Test the processUrl method when the url connection fails + * @throws IOException + * @throws SQLException + */ + @Test + public void testProcessUrlOnFail() throws IOException, SQLException { + Context context = mock(Context.class); + + doReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).when(openUrlService) + .getResponseCodeFromUrl(anyString()); + doNothing().when(openUrlService).logfailed(any(Context.class), anyString()); + + openUrlService.processUrl(context, "test-url"); + + verify(openUrlService, times(1)).logfailed(context, "test-url"); + + + } + + /** + * Test the ReprocessFailedQueue method + * @throws SQLException + */ + @Test + public void testReprocessFailedQueue() throws SQLException { + Context context = mock(Context.class); + + List trackers = new ArrayList<>(); + OpenURLTracker tracker1 = mock(OpenURLTracker.class); + OpenURLTracker tracker2 = mock(OpenURLTracker.class); + OpenURLTracker tracker3 = mock(OpenURLTracker.class); + + trackers.add(tracker1); + trackers.add(tracker2); + trackers.add(tracker3); + + when(openURLTrackerLoggerService.findAll(any(Context.class))).thenReturn(trackers); + doNothing().when(openUrlService).tryReprocessFailed(any(Context.class), any(OpenURLTracker.class)); + + openUrlService.reprocessFailedQueue(context); + + verify(openUrlService, times(3)).tryReprocessFailed(any(Context.class), any(OpenURLTracker.class)); + + } + + /** + * Test the method that logs the failed urls in the db + * @throws SQLException + */ + @Test + public void testLogfailed() throws SQLException { + Context context = mock(Context.class); + OpenURLTracker tracker1 = mock(OpenURLTracker.class); + + doCallRealMethod().when(tracker1).setUrl(anyString()); + when(tracker1.getUrl()).thenCallRealMethod(); + + when(openURLTrackerLoggerService.create(any(Context.class))).thenReturn(tracker1); + + String failedUrl = "failed-url"; + openUrlService.logfailed(context, failedUrl); + + Assert.assertThat(tracker1.getUrl(), is(failedUrl)); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 930f3ab4d7..4877998d80 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -60,7 +60,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), dSpaceRunnableList.get(0).getDescription()), ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), - dSpaceRunnableList.get(1).getDescription()) + dSpaceRunnableList.get(1).getDescription()), + ScriptMatcher.matchScript(dSpaceRunnableList.get(2).getName(), + dSpaceRunnableList.get(2).getDescription()) ))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java new file mode 100644 index 0000000000..14ac9d36d5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java @@ -0,0 +1,41 @@ +/** + * 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.statistics.export.service; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.ArrayList; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Mock OpenUrlService that will ensure that IRUS tracker does need to be contacted in order to test the functionality + */ +public class MockOpenUrlServiceImpl extends OpenUrlServiceImpl { + + @Autowired + ArrayList testProcessedUrls; + + /** + * Returns a response code to simulate contact to the external url + * When the url contains "fail", a fail code 500 will be returned + * Otherwise the success code 200 will be returned + * @param urlStr + * @return 200 or 500 depending on whether the "fail" keyword is present in the url + * @throws IOException + */ + protected int getResponseCodeFromUrl(final String urlStr) throws IOException { + if (StringUtils.contains(urlStr, "fail")) { + return HttpURLConnection.HTTP_INTERNAL_ERROR; + } else { + testProcessedUrls.add(urlStr); + return HttpURLConnection.HTTP_OK; + } + } +} diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index c48ed1db92..81860d5bef 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -117,7 +117,5 @@ - - diff --git a/dspace/config/spring/api/openurltracker.xml b/dspace/config/spring/api/openurltracker.xml new file mode 100644 index 0000000000..65d0785779 --- /dev/null +++ b/dspace/config/spring/api/openurltracker.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file From 2365ef521d76feef3e291f472f654c20d71f8341 Mon Sep 17 00:00:00 2001 From: Philip Vissenaekens Date: Fri, 31 Jan 2020 17:44:02 +0100 Subject: [PATCH 023/749] 68214: cleaned up stats.cfg, provided configuration for the COUNTER robots list and disabled the IRUS integration by default --- dspace/config/log4j2.xml | 2 -- dspace/config/modules/stats.cfg | 19 +++++++++---------- .../spring/rest/event-service-listeners.xml | 6 +++--- dspace/src/main/config/build.xml | 15 ++++++++++----- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/dspace/config/log4j2.xml b/dspace/config/log4j2.xml index eb2f5d2cd8..98a0300125 100644 --- a/dspace/config/log4j2.xml +++ b/dspace/config/log4j2.xml @@ -70,8 +70,6 @@ additivity='false'> - - # Block services logging except on exceptions - - - + + + \ No newline at end of file diff --git a/dspace/src/main/config/build.xml b/dspace/src/main/config/build.xml index ba12cf85c5..596b66f3df 100644 --- a/dspace/src/main/config/build.xml +++ b/dspace/src/main/config/build.xml @@ -926,15 +926,20 @@ You may manually install this file by following these steps: - Downloading: https://raw.githubusercontent.com/atmire/COUNTER-Robots/master/generated/COUNTER_Robots_list.txt - + Downloading: ${stats.spider.agentregex.url} + - - - + + + + + + + + From b473cc1b407c68b4b9a158cf14da065f16cc48b0 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 5 Feb 2020 10:21:24 +0100 Subject: [PATCH 024/749] Update to the script service to ensure scripts are reloaded every time --- .../java/org/dspace/scripts/ScriptServiceImpl.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index e2a6acf3a8..4bfb9be0fa 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -10,8 +10,8 @@ package org.dspace.scripts; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; +import org.dspace.kernel.ServiceManager; import org.dspace.scripts.service.ScriptService; import org.springframework.beans.factory.annotation.Autowired; @@ -19,21 +19,17 @@ import org.springframework.beans.factory.annotation.Autowired; * The implementation for the {@link ScriptService} */ public class ScriptServiceImpl implements ScriptService { - @Autowired - private List dSpaceRunnables; + private ServiceManager serviceManager; @Override public DSpaceRunnable getScriptForName(String name) { - return dSpaceRunnables.stream() - .filter(dSpaceRunnable -> StringUtils.equalsIgnoreCase(dSpaceRunnable.getName(), name)) - .findFirst() - .orElse(null); + return serviceManager.getServiceByName(name, DSpaceRunnable.class); } @Override public List getDSpaceRunnables(Context context) { - return dSpaceRunnables.stream().filter( - dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); + return serviceManager.getServicesByType(DSpaceRunnable.class).stream().filter( + dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); } } From 7a37080c982d587be738c851307cc94a9e1dd057 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 5 Feb 2020 12:01:17 +0100 Subject: [PATCH 025/749] Fixing RetryOpenUrl and tests due to scriptservice change --- .../src/main/java/org/dspace/scripts/ScriptServiceImpl.java | 2 +- .../org/dspace/statistics/export/RetryOpenUrlTracker.java | 3 --- .../test/data/dspaceFolder/config/spring/api/scripts.xml | 6 +++--- dspace/config/spring/api/scripts.xml | 4 ++-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index 4bfb9be0fa..7f1f0b83e6 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -30,6 +30,6 @@ public class ScriptServiceImpl implements ScriptService { @Override public List getDSpaceRunnables(Context context) { return serviceManager.getServicesByType(DSpaceRunnable.class).stream().filter( - dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); + dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); } } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java index cc2030c446..f8320eab91 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java @@ -65,9 +65,6 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { context = new Context(); openUrlService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlService(); - lineToAdd = null; - help = false; - if (commandLine.hasOption('h')) { help = true; } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 456b1b3f57..4f7fdb6a5c 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -4,17 +4,17 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + - + - + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index bbc9c346e0..b358267cbe 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -4,12 +4,12 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + - + From c665ecf2c61dfe2905cec1981e35a506ae0e527f Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 10 Feb 2020 11:53:40 +0100 Subject: [PATCH 026/749] 68716: Implement feedback --- .../export/AbstractUsageEventListener.java | 66 ------------------- ...a => FailedOpenURLTrackerServiceImpl.java} | 4 +- ...er.java => RetryfailedOpenUrlTracker.java} | 27 ++++++-- .../export/dao/OpenURLTrackerDAO.java | 6 +- .../dao/impl/OpenURLTrackerDAOImpl.java | 6 +- .../OpenURLTrackerLoggerServiceFactory.java | 8 +-- ...penURLTrackerLoggerServiceFactoryImpl.java | 12 ++-- .../processor/BitstreamEventProcessor.java | 12 ++-- .../processor/ExportEventProcessor.java | 31 +++++---- .../export/processor/ItemEventProcessor.java | 10 +-- ....java => FailedOpenURLTrackerService.java} | 2 +- .../export/service/OpenUrlServiceImpl.java | 12 ++-- .../config/spring/api/openurltracker.xml | 2 +- .../config/spring/api/scripts.xml | 2 +- ... FailedOpenURLTrackerServiceImplTest.java} | 6 +- .../export/ITExportUsageEventListener.java | 22 +++---- ....java => ITRetryfailedOpenUrlTracker.java} | 40 +++++------ .../processor/ExportEventProcessorTest.java | 4 +- .../service/OpenUrlServiceImplTest.java | 6 +- dspace/config/launcher.xml | 2 +- dspace/config/spring/api/openurltracker.xml | 2 +- dspace/config/spring/api/scripts.xml | 2 +- 22 files changed, 119 insertions(+), 165 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java rename dspace-api/src/main/java/org/dspace/statistics/export/{OpenURLTrackerLoggerServiceImpl.java => FailedOpenURLTrackerServiceImpl.java} (91%) rename dspace-api/src/main/java/org/dspace/statistics/export/{RetryOpenUrlTracker.java => RetryfailedOpenUrlTracker.java} (72%) rename dspace-api/src/main/java/org/dspace/statistics/export/service/{OpenURLTrackerLoggerService.java => FailedOpenURLTrackerService.java} (96%) rename dspace-api/src/test/java/org/dspace/statistics/export/{OpenURLTrackerLoggerServiceImplTest.java => FailedOpenURLTrackerServiceImplTest.java} (92%) rename dspace-api/src/test/java/org/dspace/statistics/export/{ITRetryOpenUrlTracker.java => ITRetryfailedOpenUrlTracker.java} (80%) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java deleted file mode 100644 index 798514e447..0000000000 --- a/dspace-api/src/main/java/org/dspace/statistics/export/AbstractUsageEventListener.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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.statistics.export; - -import org.dspace.services.EventService; -import org.dspace.services.model.EventListener; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; - -/** - * AbstractUsageEventListener is used as the base class for listening events running - * in the EventService. - * - * @author Mark Diggory (mdiggory at atmire.com) - * @version $Revision: $ - */ -public abstract class AbstractUsageEventListener implements EventListener, BeanPostProcessor { - - public AbstractUsageEventListener() { - super(); - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (beanName.equals("org.dspace.services.EventService")) { - setEventService((EventService) bean); - } - return bean; - } - - /** - * Empty String[] flags to have Listener - * consume any event name prefixes. - */ - public String[] getEventNamePrefixes() { - return new String[0]; - } - - /** - * Currently consumes events generated for - * all resources. - */ - public String getResourcePrefix() { - return null; - } - - public void setEventService(EventService service) { - if (service != null) { - service.registerEventListener(this); - } else { - throw new IllegalStateException("EventService handed to Listener cannot be null"); - } - - } - -} diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java similarity index 91% rename from dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java rename to dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java index ac930b1c16..cb8e64cc65 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java @@ -12,13 +12,13 @@ import java.util.List; import org.dspace.core.Context; import org.dspace.statistics.export.dao.OpenURLTrackerDAO; -import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.FailedOpenURLTrackerService; import org.springframework.beans.factory.annotation.Autowired; /** * Implementation of the service that handles the OpenURLTracker database operations */ -public class OpenURLTrackerLoggerServiceImpl implements OpenURLTrackerLoggerService { +public class FailedOpenURLTrackerServiceImpl implements FailedOpenURLTrackerService { @Autowired(required = true) protected OpenURLTrackerDAO openURLTrackerDAO; diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryfailedOpenUrlTracker.java similarity index 72% rename from dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java rename to dspace-api/src/main/java/org/dspace/statistics/export/RetryfailedOpenUrlTracker.java index f8320eab91..967a414e4c 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/RetryOpenUrlTracker.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryfailedOpenUrlTracker.java @@ -10,7 +10,6 @@ package org.dspace.statistics.export; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; @@ -20,12 +19,12 @@ import org.dspace.statistics.export.service.OpenUrlService; * Script to retry the failed url transmissions to IRUS * This script also has an option to add new failed urls for testing purposes */ -public class RetryOpenUrlTracker extends DSpaceRunnable { - private static final Logger log = Logger.getLogger(RetryOpenUrlTracker.class); +public class RetryfailedOpenUrlTracker extends DSpaceRunnable { private Context context = null; private String lineToAdd = null; private boolean help = false; + private boolean retryFailed = false; private OpenUrlService openUrlService; @@ -44,15 +43,17 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { if (StringUtils.isNotBlank(lineToAdd)) { openUrlService.logfailed(context, lineToAdd); - log.info("Created dummy entry in OpenUrlTracker with URL: " + lineToAdd); - } else { + handler.logInfo("Created dummy entry in OpenUrlTracker with URL: " + lineToAdd); + } + if (retryFailed) { + handler.logInfo("Reprocessing failed URLs stored in the db"); openUrlService.reprocessFailedQueue(context); } context.restoreAuthSystemState(); try { context.complete(); } catch (Exception e) { - log.error(e.getMessage(), e); + handler.logError(e.getMessage()); } } @@ -65,15 +66,23 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { context = new Context(); openUrlService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlService(); + if (!(commandLine.hasOption('a') || commandLine.hasOption('r') || commandLine.hasOption('h'))) { + throw new ParseException("At least one of the parameters (-a, -r, -h) is required!"); + } + + if (commandLine.hasOption('h')) { help = true; } if (commandLine.hasOption('a')) { lineToAdd = commandLine.getOptionValue('a'); } + if (commandLine.hasOption('r')) { + retryFailed = true; + } } - private RetryOpenUrlTracker() { + private RetryfailedOpenUrlTracker() { Options options = constructOptions(); this.options = options; } @@ -89,6 +98,10 @@ public class RetryOpenUrlTracker extends DSpaceRunnable { options.addOption("a", true, "Add a new \"failed\" row to the table with a url (test purposes only)"); options.getOption("a").setType(String.class); + options.addOption("r", false, "Retry sending requests to all urls stored in the table with failed requests. " + + "This includes the url that can be added through the -a option."); + options.getOption("r").setType(boolean.class); + options.addOption("h", "help", false, "print this help message"); options.getOption("h").setType(boolean.class); diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java b/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java index 0aa05065be..e3b957db1d 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/dao/OpenURLTrackerDAO.java @@ -11,9 +11,11 @@ import org.dspace.core.GenericDAO; import org.dspace.statistics.export.OpenURLTracker; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * Database Access Object interface class for the OpenURLTracker object. + * The implementation of this class is responsible for all database calls for the OpenURLTracker object and is + * autowired by spring + * This class should only be accessed from a single service and should never be exposed outside of the API */ public interface OpenURLTrackerDAO extends GenericDAO { - } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java index fb08d572f0..d057f45bac 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/dao/impl/OpenURLTrackerDAOImpl.java @@ -12,11 +12,13 @@ import org.dspace.statistics.export.OpenURLTracker; import org.dspace.statistics.export.dao.OpenURLTrackerDAO; /** - * Created by jonas - jonas@atmire.com on 09/02/17. + * Hibernate implementation of the Database Access Object interface class for the OpenURLTracker object. + * This class is responsible for all database calls for the OpenURLTracker object and is autowired by spring + * This class should never be accessed directly. + * */ public class OpenURLTrackerDAOImpl extends AbstractHibernateDAO implements OpenURLTrackerDAO { - protected OpenURLTrackerDAOImpl() { super(); } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java index 884d1b2f89..b31b076f68 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactory.java @@ -8,7 +8,7 @@ package org.dspace.statistics.export.factory; import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.FailedOpenURLTrackerService; import org.dspace.statistics.export.service.OpenUrlService; /** @@ -17,10 +17,10 @@ import org.dspace.statistics.export.service.OpenUrlService; public abstract class OpenURLTrackerLoggerServiceFactory { /** - * Returns the OpenURLTrackerLoggerService - * @return OpenURLTrackerLoggerService instance + * Returns the FailedOpenURLTrackerService + * @return FailedOpenURLTrackerService instance */ - public abstract OpenURLTrackerLoggerService getOpenUrlTrackerLoggerService(); + public abstract FailedOpenURLTrackerService getOpenUrlTrackerLoggerService(); /** * Retrieve the OpenURLTrackerLoggerServiceFactory diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java index 1b3efd85c0..f585fdf376 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/factory/OpenURLTrackerLoggerServiceFactoryImpl.java @@ -7,7 +7,7 @@ */ package org.dspace.statistics.export.factory; -import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.FailedOpenURLTrackerService; import org.dspace.statistics.export.service.OpenUrlService; import org.springframework.beans.factory.annotation.Autowired; @@ -17,18 +17,18 @@ import org.springframework.beans.factory.annotation.Autowired; public class OpenURLTrackerLoggerServiceFactoryImpl extends OpenURLTrackerLoggerServiceFactory { @Autowired(required = true) - private OpenURLTrackerLoggerService openURLTrackerLoggerService; + private FailedOpenURLTrackerService failedOpenURLTrackerService; @Autowired(required = true) private OpenUrlService openUrlService; /** - * Returns the OpenURLTrackerLoggerService - * @return OpenURLTrackerLoggerService instance + * Returns the FailedOpenURLTrackerService + * @return FailedOpenURLTrackerService instance */ @Override - public OpenURLTrackerLoggerService getOpenUrlTrackerLoggerService() { - return openURLTrackerLoggerService; + public FailedOpenURLTrackerService getOpenUrlTrackerLoggerService() { + return failedOpenURLTrackerService; } /** diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java index 5f8447bed0..b32f1bd6f4 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java @@ -73,7 +73,7 @@ public class BitstreamEventProcessor extends ExportEventProcessor { */ public void processEvent() throws SQLException, IOException { if (shouldProcessItem(item)) { - String baseParam = getBaseParamaters(item); + String baseParam = getBaseParameters(item); String fullParam = addObjectSpecificData(baseParam, item, bitstream); processObject(fullParam); } @@ -92,10 +92,10 @@ public class BitstreamEventProcessor extends ExportEventProcessor { StringBuilder data = new StringBuilder(string); String bitstreamInfo = getBitstreamInfo(item, bitstream); - data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(bitstreamInfo, "UTF-8")); - data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(BITSTREAM_DOWNLOAD, "UTF-8")); + data.append("&").append(URLEncoder.encode("svc_dat", UTF_8)).append("=") + .append(URLEncoder.encode(bitstreamInfo, UTF_8)); + data.append("&").append(URLEncoder.encode("rft_dat", UTF_8)).append("=") + .append(URLEncoder.encode(BITSTREAM_DOWNLOAD, UTF_8)); return data.toString(); } @@ -125,7 +125,7 @@ public class BitstreamEventProcessor extends ExportEventProcessor { // If we can, append the pretty name of the bitstream to the URL try { if (bitstream.getName() != null) { - sb.append(Util.encodeBitstreamName(bitstream.getName(), "UTF-8")); + sb.append(Util.encodeBitstreamName(bitstream.getName(), UTF_8)); } } catch (UnsupportedEncodingException uee) { // just ignore it, we don't have to have a pretty diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java index 508b512343..88291268ff 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.codec.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.content.DCDate; @@ -67,6 +68,8 @@ public abstract class ExportEventProcessor { protected static OpenUrlService openUrlService; + protected final static String UTF_8 = CharEncoding.UTF_8; + protected Context context; protected HttpServletRequest request; @@ -111,7 +114,7 @@ public abstract class ExportEventProcessor { * @return the parameter string to be used in the url * @throws UnsupportedEncodingException */ - protected String getBaseParamaters(Item item) + protected String getBaseParameters(Item item) throws UnsupportedEncodingException { //We have a valid url collect the rest of the data @@ -134,20 +137,20 @@ public abstract class ExportEventProcessor { //Start adding our data StringBuilder data = new StringBuilder(); - data.append(URLEncoder.encode("url_ver", "UTF-8") + "=" + URLEncoder.encode(trackerUrlVersion, "UTF-8")); - data.append("&").append(URLEncoder.encode("req_id", "UTF-8")).append("=") - .append(URLEncoder.encode(clientIP, "UTF-8")); - data.append("&").append(URLEncoder.encode("req_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(clientUA, "UTF-8")); - data.append("&").append(URLEncoder.encode("rft.artnum", "UTF-8")).append("="). + data.append(URLEncoder.encode("url_ver", UTF_8) + "=" + URLEncoder.encode(trackerUrlVersion, UTF_8)); + data.append("&").append(URLEncoder.encode("req_id", UTF_8)).append("=") + .append(URLEncoder.encode(clientIP, UTF_8)); + data.append("&").append(URLEncoder.encode("req_dat", UTF_8)).append("=") + .append(URLEncoder.encode(clientUA, UTF_8)); + data.append("&").append(URLEncoder.encode("rft.artnum", UTF_8)).append("="). append(URLEncoder.encode("oai:" + configurationService.getProperty("dspace.hostname") + ":" + item - .getHandle(), "UTF-8")); - data.append("&").append(URLEncoder.encode("rfr_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(referer, "UTF-8")); - data.append("&").append(URLEncoder.encode("rfr_id", "UTF-8")).append("=") - .append(URLEncoder.encode(configurationService.getProperty("dspace.hostname"), "UTF-8")); - data.append("&").append(URLEncoder.encode("url_tim", "UTF-8")).append("=") - .append(URLEncoder.encode(getCurrentDateString(), "UTF-8")); + .getHandle(), UTF_8)); + data.append("&").append(URLEncoder.encode("rfr_dat", UTF_8)).append("=") + .append(URLEncoder.encode(referer, UTF_8)); + data.append("&").append(URLEncoder.encode("rfr_id", UTF_8)).append("=") + .append(URLEncoder.encode(configurationService.getProperty("dspace.hostname"), UTF_8)); + data.append("&").append(URLEncoder.encode("url_tim", UTF_8)).append("=") + .append(URLEncoder.encode(getCurrentDateString(), UTF_8)); return data.toString(); } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java index 8ef809da05..c8b809ab5d 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java @@ -44,7 +44,7 @@ public class ItemEventProcessor extends ExportEventProcessor { */ public void processEvent() throws SQLException, IOException { if (shouldProcessItem(item)) { - String baseParam = getBaseParamaters(item); + String baseParam = getBaseParameters(item); String fullParam = addObjectSpecificData(baseParam, item); processObject(fullParam); } @@ -60,10 +60,10 @@ public class ItemEventProcessor extends ExportEventProcessor { protected String addObjectSpecificData(final String string, Item item) throws UnsupportedEncodingException { StringBuilder data = new StringBuilder(string); String itemInfo = getItemInfo(item); - data.append("&").append(URLEncoder.encode("svc_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(itemInfo, "UTF-8")); - data.append("&").append(URLEncoder.encode("rft_dat", "UTF-8")).append("=") - .append(URLEncoder.encode(ITEM_VIEW, "UTF-8")); + data.append("&").append(URLEncoder.encode("svc_dat", UTF_8)).append("=") + .append(URLEncoder.encode(itemInfo, UTF_8)); + data.append("&").append(URLEncoder.encode("rft_dat", UTF_8)).append("=") + .append(URLEncoder.encode(ITEM_VIEW, UTF_8)); return data.toString(); } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java rename to dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java index 46bc0b040b..9b482e3d54 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenURLTrackerLoggerService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java @@ -16,7 +16,7 @@ import org.dspace.statistics.export.OpenURLTracker; /** * Interface of the service that handles the OpenURLTracker database operations */ -public interface OpenURLTrackerLoggerService { +public interface FailedOpenURLTrackerService { /** * Removes an OpenURLTracker from the database diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index 54b72f169e..39d9ec3217 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -29,7 +29,7 @@ public class OpenUrlServiceImpl implements OpenUrlService { private Logger log = Logger.getLogger(OpenUrlService.class); @Autowired - protected OpenURLTrackerLoggerService openUrlTrackerLoggerService; + protected FailedOpenURLTrackerService failedOpenUrlTrackerService; /** * Processes the url @@ -87,7 +87,7 @@ public class OpenUrlServiceImpl implements OpenUrlService { success = false; } finally { if (success) { - openUrlTrackerLoggerService + failedOpenUrlTrackerService .remove(context, tracker); // If the tracker was able to post successfully, we remove it from the database log.info("Successfully posted " + tracker.getUrl() + " from " + tracker.getUploadDate()); @@ -104,11 +104,11 @@ public class OpenUrlServiceImpl implements OpenUrlService { * @throws SQLException */ public void reprocessFailedQueue(Context context) throws SQLException { - if (openUrlTrackerLoggerService == null) { - log.error("Error retrieving the \"openUrlTrackerLoggerService\" instance, aborting the processing"); + if (failedOpenUrlTrackerService == null) { + log.error("Error retrieving the \"failedOpenUrlTrackerService\" instance, aborting the processing"); return; } - List openURLTrackers = openUrlTrackerLoggerService.findAll(context); + List openURLTrackers = failedOpenUrlTrackerService.findAll(context); for (OpenURLTracker openURLTracker : openURLTrackers) { tryReprocessFailed(context, openURLTracker); } @@ -126,7 +126,7 @@ public class OpenUrlServiceImpl implements OpenUrlService { return; } - OpenURLTracker tracker = openUrlTrackerLoggerService.create(context); + OpenURLTracker tracker = failedOpenUrlTrackerService.create(context); tracker.setUploadDate(now); tracker.setUrl(url); } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml index d6e49b5655..1d3be040a3 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/openurltracker.xml @@ -4,7 +4,7 @@ default-lazy-init="true"> - + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 4f7fdb6a5c..ee8244b9e7 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -14,7 +14,7 @@ - + diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImplTest.java similarity index 92% rename from dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java rename to dspace-api/src/test/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImplTest.java index afb72ee115..25c1a9b02b 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/OpenURLTrackerLoggerServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImplTest.java @@ -26,13 +26,13 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; /** - * Class to test the OpenURLTrackerLoggerServiceImpl + * Class to test the FailedOpenURLTrackerServiceImpl */ @RunWith(MockitoJUnitRunner.class) -public class OpenURLTrackerLoggerServiceImplTest { +public class FailedOpenURLTrackerServiceImplTest { @InjectMocks - private OpenURLTrackerLoggerServiceImpl openURLTrackerLoggerService; + private FailedOpenURLTrackerServiceImpl openURLTrackerLoggerService; @Mock private Context context; diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java index ced76d9299..15283db011 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java @@ -47,7 +47,7 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; -import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.FailedOpenURLTrackerService; import org.dspace.usage.UsageEvent; import org.junit.After; import org.junit.Before; @@ -72,7 +72,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); - protected OpenURLTrackerLoggerService openURLTrackerLoggerService = + protected FailedOpenURLTrackerService failedOpenURLTrackerService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService(); protected ArrayList testProcessedUrls = DSpaceServicesFactory.getInstance().getServiceManager() @@ -129,9 +129,9 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { entityTypeService.delete(context, entityType); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); for (OpenURLTracker tracker : all) { - openURLTrackerLoggerService.remove(context, tracker); + failedOpenURLTrackerService.remove(context, tracker); } entityType = null; @@ -167,7 +167,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { exportUsageEventListener.receiveEvent(usageEvent); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + @@ -201,7 +201,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { exportUsageEventListener.receiveEvent(usageEvent); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + @@ -241,7 +241,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { exportUsageEventListener.receiveEvent(usageEvent); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); assertThat(testProcessedUrls.size(), is(0)); @@ -275,7 +275,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { assertThat(testProcessedUrls.size(), is(1)); assertThat(isMatch, is(true)); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); assertThat(all.size(), is(0)); } @@ -296,7 +296,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { exportUsageEventListener.receiveEvent(usageEvent); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + @@ -336,7 +336,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { exportUsageEventListener.receiveEvent(usageEvent); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); assertThat(all.size(), is(0)); @@ -361,7 +361,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { exportUsageEventListener.receiveEvent(usageEvent); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); assertThat(all.size(), is(0)); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryfailedOpenUrlTracker.java similarity index 80% rename from dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java rename to dspace-api/src/test/java/org/dspace/statistics/export/ITRetryfailedOpenUrlTracker.java index f3f9fe294f..c520ec06bd 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryOpenUrlTracker.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryfailedOpenUrlTracker.java @@ -21,22 +21,22 @@ import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ScriptService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; -import org.dspace.statistics.export.service.OpenURLTrackerLoggerService; +import org.dspace.statistics.export.service.FailedOpenURLTrackerService; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; /** - * Class to test the RetryOpenUrlTracker + * Class to test the RetryfailedOpenUrlTracker */ @RunWith(MockitoJUnitRunner.class) -public class ITRetryOpenUrlTracker extends AbstractIntegrationTest { +public class ITRetryfailedOpenUrlTracker extends AbstractIntegrationTest { - private static Logger log = Logger.getLogger(ITRetryOpenUrlTracker.class); + private static Logger log = Logger.getLogger(ITRetryfailedOpenUrlTracker.class); - protected OpenURLTrackerLoggerService openURLTrackerLoggerService = + protected FailedOpenURLTrackerService failedOpenURLTrackerService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService(); protected ArrayList testProcessedUrls = DSpaceServicesFactory.getInstance().getServiceManager() @@ -54,9 +54,9 @@ public class ITRetryOpenUrlTracker extends AbstractIntegrationTest { try { context.turnOffAuthorisationSystem(); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); for (OpenURLTracker tracker : all) { - openURLTrackerLoggerService.remove(context, tracker); + failedOpenURLTrackerService.remove(context, tracker); } // Clear the list of processedUrls @@ -85,7 +85,7 @@ public class ITRetryOpenUrlTracker extends AbstractIntegrationTest { retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); retryOpenUrlTracker.internalRun(); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); assertThat(testProcessedUrls.size(), is(0)); assertThat(all.size(), is(1)); @@ -101,20 +101,20 @@ public class ITRetryOpenUrlTracker extends AbstractIntegrationTest { TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); - String[] args = {}; + String[] args = {"-r"}; - OpenURLTracker tracker1 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker1 = failedOpenURLTrackerService.create(context); tracker1.setUrl("test-url-1"); - OpenURLTracker tracker2 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker2 = failedOpenURLTrackerService.create(context); tracker2.setUrl("test-url-2"); - OpenURLTracker tracker3 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker3 = failedOpenURLTrackerService.create(context); tracker3.setUrl("test-url-3"); retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); retryOpenUrlTracker.internalRun(); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); assertThat(testProcessedUrls.size(), is(3)); assertThat(testProcessedUrls.contains("test-url-1"), is(true)); @@ -133,24 +133,24 @@ public class ITRetryOpenUrlTracker extends AbstractIntegrationTest { TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); - String[] args = {}; + String[] args = {"-r"}; - OpenURLTracker tracker1 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker1 = failedOpenURLTrackerService.create(context); tracker1.setUrl("test-url-1"); - OpenURLTracker tracker2 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker2 = failedOpenURLTrackerService.create(context); tracker2.setUrl("test-url-2-fail"); - OpenURLTracker tracker3 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker3 = failedOpenURLTrackerService.create(context); tracker3.setUrl("test-url-3-fail"); - OpenURLTracker tracker4 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker4 = failedOpenURLTrackerService.create(context); tracker4.setUrl("test-url-4-fail"); - OpenURLTracker tracker5 = openURLTrackerLoggerService.create(context); + OpenURLTracker tracker5 = failedOpenURLTrackerService.create(context); tracker5.setUrl("test-url-5"); retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); retryOpenUrlTracker.internalRun(); - List all = openURLTrackerLoggerService.findAll(context); + List all = failedOpenURLTrackerService.findAll(context); List storedTrackerUrls = new ArrayList<>(); for (OpenURLTracker tracker : all) { storedTrackerUrls.add(tracker.getUrl()); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java index 2f5699beb0..eedfbb352f 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java @@ -76,8 +76,8 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { when(item.getHandle()).thenReturn("123456/1"); - when(exportEventProcessor.getBaseParamaters(item)).thenCallRealMethod(); - String result = exportEventProcessor.getBaseParamaters(item); + when(exportEventProcessor.getBaseParameters(item)).thenCallRealMethod(); + String result = exportEventProcessor.getBaseParameters(item); String expected = "url_ver=Z39.88-2004&req_id=test-client-ip&req_dat=test-user-agent&rft.artnum=" + "oai%3Alocalhost%3A123456%2F1&rfr_dat=test-referer&rfr_id=localhost&url_tim=2020-01-24T13%3A24%3A33Z"; diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java index 5f1a648ba6..a0f02a7bb6 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -45,7 +45,7 @@ public class OpenUrlServiceImplTest { private OpenUrlServiceImpl openUrlService; @Mock - private OpenURLTrackerLoggerService openURLTrackerLoggerService; + private FailedOpenURLTrackerService failedOpenURLTrackerService; /** * Test the processUrl method @@ -104,7 +104,7 @@ public class OpenUrlServiceImplTest { trackers.add(tracker2); trackers.add(tracker3); - when(openURLTrackerLoggerService.findAll(any(Context.class))).thenReturn(trackers); + when(failedOpenURLTrackerService.findAll(any(Context.class))).thenReturn(trackers); doNothing().when(openUrlService).tryReprocessFailed(any(Context.class), any(OpenURLTracker.class)); openUrlService.reprocessFailedQueue(context); @@ -125,7 +125,7 @@ public class OpenUrlServiceImplTest { doCallRealMethod().when(tracker1).setUrl(anyString()); when(tracker1.getUrl()).thenCallRealMethod(); - when(openURLTrackerLoggerService.create(any(Context.class))).thenReturn(tracker1); + when(failedOpenURLTrackerService.create(any(Context.class))).thenReturn(tracker1); String failedUrl = "failed-url"; openUrlService.logfailed(context, failedUrl); diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index 12a5cee8ca..ee81bb82ab 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -212,7 +212,7 @@ retry-tracker Retry all failed commits to the OpenURLTracker - org.dspace.statistics.export.RetryOpenUrlTracker + org.dspace.statistics.export.RetryfailedOpenUrlTracker diff --git a/dspace/config/spring/api/openurltracker.xml b/dspace/config/spring/api/openurltracker.xml index 65d0785779..01dab53904 100644 --- a/dspace/config/spring/api/openurltracker.xml +++ b/dspace/config/spring/api/openurltracker.xml @@ -4,7 +4,7 @@ default-lazy-init="true"> - + \ No newline at end of file diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index b358267cbe..598be60ad7 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -9,7 +9,7 @@ - + From 25362cb704a20b5c710f9f08f6912bd60b89ae0f Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 10 Feb 2020 11:58:12 +0100 Subject: [PATCH 027/749] 68716: fix typo in script class name --- ...edOpenUrlTracker.java => RetryFailedOpenUrlTracker.java} | 4 ++-- .../test/data/dspaceFolder/config/spring/api/scripts.xml | 2 +- ...OpenUrlTracker.java => ITRetryFailedOpenUrlTracker.java} | 6 +++--- dspace/config/launcher.xml | 2 +- dspace/config/spring/api/scripts.xml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename dspace-api/src/main/java/org/dspace/statistics/export/{RetryfailedOpenUrlTracker.java => RetryFailedOpenUrlTracker.java} (97%) rename dspace-api/src/test/java/org/dspace/statistics/export/{ITRetryfailedOpenUrlTracker.java => ITRetryFailedOpenUrlTracker.java} (97%) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryfailedOpenUrlTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTracker.java similarity index 97% rename from dspace-api/src/main/java/org/dspace/statistics/export/RetryfailedOpenUrlTracker.java rename to dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTracker.java index 967a414e4c..0795b3cdf0 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/RetryfailedOpenUrlTracker.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTracker.java @@ -19,7 +19,7 @@ import org.dspace.statistics.export.service.OpenUrlService; * Script to retry the failed url transmissions to IRUS * This script also has an option to add new failed urls for testing purposes */ -public class RetryfailedOpenUrlTracker extends DSpaceRunnable { +public class RetryFailedOpenUrlTracker extends DSpaceRunnable { private Context context = null; private String lineToAdd = null; @@ -82,7 +82,7 @@ public class RetryfailedOpenUrlTracker extends DSpaceRunnable { } } - private RetryfailedOpenUrlTracker() { + private RetryFailedOpenUrlTracker() { Options options = constructOptions(); this.options = options; } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index ee8244b9e7..06cf78910a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -14,7 +14,7 @@ - + diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryfailedOpenUrlTracker.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java similarity index 97% rename from dspace-api/src/test/java/org/dspace/statistics/export/ITRetryfailedOpenUrlTracker.java rename to dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java index c520ec06bd..1f21ed389e 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryfailedOpenUrlTracker.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java @@ -28,12 +28,12 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; /** - * Class to test the RetryfailedOpenUrlTracker + * Class to test the RetryFailedOpenUrlTracker */ @RunWith(MockitoJUnitRunner.class) -public class ITRetryfailedOpenUrlTracker extends AbstractIntegrationTest { +public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { - private static Logger log = Logger.getLogger(ITRetryfailedOpenUrlTracker.class); + private static Logger log = Logger.getLogger(ITRetryFailedOpenUrlTracker.class); protected FailedOpenURLTrackerService failedOpenURLTrackerService = diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index ee81bb82ab..e45e186bf7 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -212,7 +212,7 @@ retry-tracker Retry all failed commits to the OpenURLTracker - org.dspace.statistics.export.RetryfailedOpenUrlTracker + org.dspace.statistics.export.RetryFailedOpenUrlTracker diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 598be60ad7..81392a23d3 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -9,7 +9,7 @@ - + From f3003fbf84f3562fd203b588a587e8288069b547 Mon Sep 17 00:00:00 2001 From: Philip Vissenaekens Date: Mon, 10 Feb 2020 13:41:50 +0100 Subject: [PATCH 028/749] 68214: cleaned up stats.cfg --- dspace/config/modules/stats.cfg | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dspace/config/modules/stats.cfg b/dspace/config/modules/stats.cfg index 638edeec6a..d4977584bc 100644 --- a/dspace/config/modules/stats.cfg +++ b/dspace/config/modules/stats.cfg @@ -1,9 +1,3 @@ - - -#-----------------------# -# Atmire stats exporter # -#-----------------------# - # OPTIONAL metadata field used for filtering. # If items with specific values for the "dc.type" field should be excluded, "dc.type" should be placed here. # This should comply to the syntax schema.element.qualified or schema.element if the qualifier is null. From b70c660b65062c4f8ec67590cee86a05ea096194 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 11 Feb 2020 10:15:21 +0100 Subject: [PATCH 029/749] Configure missing search configurations --- dspace/config/spring/api/discovery.xml | 24 ++++++++++++------------ dspace/config/submission-forms.xml | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 24e4286950..49f12f70a9 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -57,10 +57,10 @@ - - - - + + + + @@ -850,9 +850,9 @@ - - + @@ -915,9 +915,9 @@ - - + @@ -976,9 +976,9 @@ - - + @@ -1036,9 +1036,9 @@ - - + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 1a6ddcf049..39662515d8 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -490,7 +490,7 @@ isVolumeOfJournal - periodical + journal creativework.publisher:somepublishername Select the journal related to this volume. @@ -1750,4 +1750,4 @@ - \ No newline at end of file + From 90073abfb7b7ffec2482751dd6ad92939ec874b0 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 11 Feb 2020 12:41:48 +0100 Subject: [PATCH 030/749] [Task 68534] applied feedback to the metadata import and export script and wrote tests for them --- .../dspace/app/bulkedit/MetadataExport.java | 2 +- .../MetadataDSpaceCsvExportServiceImpl.java | 2 +- .../org/dspace/scripts/ScriptServiceImpl.java | 1 + .../dspaceFolder/assetstore/testImport.csv | 2 + .../config/spring/api/scripts.xml | 4 ++ .../app/bulkedit/MetadataExportTest.java | 65 +++++++++++++++++++ .../app/bulkedit/MetadataImportTest.java | 52 +++++++++++++++ .../impl/TestDSpaceRunnableHandler.java | 36 ++++++++++ .../src/test/resources/test-config.properties | 2 + .../dspace/app/rest/model/ParameterRest.java | 23 +++++++ .../app/rest/ScriptRestRepositoryIT.java | 10 +-- 11 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv create mode 100644 dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java create mode 100644 dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java create mode 100644 dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java index 706a84620e..c61c71742c 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -93,7 +93,7 @@ public class MetadataExport extends DSpaceRunnable { exportAllMetadata = commandLine.hasOption('a'); - if (commandLine.hasOption('i')) { + if (!commandLine.hasOption('i')) { exportAllItems = true; } handle = commandLine.getOptionValue('i'); diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 57027a5100..2dc0cd5f54 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -32,7 +32,7 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo DSpaceRunnableHandler handler) throws Exception { Iterator toExport = null; - if (!exportAllItems) { + if (exportAllItems) { handler.logInfo("Exporting whole repository WARNING: May take some time!"); toExport = itemService.findAll(context); } else { diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index 7f1f0b83e6..6e87da849f 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; * The implementation for the {@link ScriptService} */ public class ScriptServiceImpl implements ScriptService { + @Autowired private ServiceManager serviceManager; diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv b/dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv new file mode 100644 index 0000000000..cb658de4ed --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv @@ -0,0 +1,2 @@ +id,collection,dc.contributor.author ++,"123456789/2","Donald, SmithImported" diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 8072292b28..a151984be0 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -8,6 +8,10 @@ + + + + diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java new file mode 100644 index 0000000000..e08fb62d9c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.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.bulkedit; + +import static junit.framework.TestCase.assertTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractIntegrationTest; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; +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.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; + +public class MetadataExportTest extends AbstractIntegrationTest { + + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + private WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + private InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @Test + public void metadataExportToCsvTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = communityService.create(null, context); + Collection collection = collectionService.create(context, community); + WorkspaceItem wi = workspaceItemService.create(context, collection, true); + Item item = wi.getItem(); + itemService.addMetadata(context, item, "dc", "contributor", "author", null, "Donald, Smith"); + item = installItemService.installItem(context, wi); + String fileLocation = configurationService.getProperty("dspace.dir") + testProps.get("test.exportcsv") + .toString(); + + String[] args = new String[] {"metadata-export", "-i", String.valueOf(item.getHandle()), "-f", fileLocation}; + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + File file = new File(fileLocation); + String fileContent = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8); + assertTrue(fileContent.contains("Donald, Smith")); + assertTrue(fileContent.contains(String.valueOf(item.getID()))); + + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java new file mode 100644 index 0000000000..3b649cabf9 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java @@ -0,0 +1,52 @@ +/** + * 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.bulkedit; + +import static junit.framework.TestCase.assertTrue; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.AbstractIntegrationTest; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.ItemService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; + +public class MetadataImportTest extends AbstractIntegrationTest { + + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @Test + public void metadataImportTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = communityService.create(null, context); + collectionService.create(context, community); + + String fileLocation = configurationService.getProperty("dspace.dir") + testProps.get("test.importcsv") + .toString(); + String[] args = new String[] {"metadata-import", "-f", fileLocation, "-e", eperson.getEmail(), "-s"}; + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + Item importedItem = itemService.findAll(context).next(); + assertTrue( + StringUtils.equals( + itemService.getMetadata(importedItem, "dc", "contributor", "author", Item.ANY).get(0).getValue(), + "Donald, SmithImported")); + + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java new file mode 100644 index 0000000000..1b5b3fa7ac --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java @@ -0,0 +1,36 @@ +/** + * 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.scripts.handler.impl; + +import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; + +/** + * This class will be used as a DSpaceRunnableHandler for the Tests so that we can stop the handler + * from calling System.exit() when a script would throw an exception + */ +public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler { + + private Exception exception = null; + + /** + * We're overriding this method so that we can stop the script from doing the System.exit() if + * an exception within the script is thrown + */ + @Override + public void handleException(String message, Exception e) { + exception = e; + } + + /** + * Generic getter for the exception + * @return the exception value of this TestDSpaceRunnableHandler + */ + public Exception getException() { + return exception; + } +} diff --git a/dspace-api/src/test/resources/test-config.properties b/dspace-api/src/test/resources/test-config.properties index 273d93c968..a61361d328 100644 --- a/dspace-api/src/test/resources/test-config.properties +++ b/dspace-api/src/test/resources/test-config.properties @@ -11,3 +11,5 @@ test.folder.assetstore = ./target/testing/dspace/assetstore #Path for a test file to create bitstreams test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf +test.exportcsv = /assetstore/test.csv +test.importcsv = /assetstore/testImport.csv diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.java index b24544c1bc..34d0057dc8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.java @@ -25,6 +25,13 @@ public class ParameterRest { */ private String type; + /** + * Boolean indicating whether the parameter is mandatory or not + */ + private boolean mandatory; + + + public String getName() { return name; } @@ -48,4 +55,20 @@ public class ParameterRest { public void setType(String type) { this.type = type; } + + /** + * Generic getter for the mandatory + * @return the mandatory value of this ParameterRest + */ + public boolean isMandatory() { + return mandatory; + } + + /** + * Generic setter for the mandatory + * @param mandatory The mandatory to be set on this ParameterRest + */ + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 2be9929e58..074150f86b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -77,7 +77,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), dSpaceRunnableList.get(1).getDescription()), ScriptMatcher.matchScript(dSpaceRunnableList.get(2).getName(), - dSpaceRunnableList.get(2).getDescription()) + dSpaceRunnableList.get(2).getDescription()), + ScriptMatcher.matchScript(dSpaceRunnableList.get(3).getName(), + dSpaceRunnableList.get(3).getDescription()) ))); } @@ -116,8 +118,8 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts").param("size", "1").param("page", "1")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.scripts", hasItem( - ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), - dSpaceRunnableList.get(0).getDescription()) + ScriptMatcher.matchScript(dSpaceRunnableList.get(2).getName(), + dSpaceRunnableList.get(2).getDescription()) ))) .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), @@ -134,7 +136,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts/mock-script")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ScriptMatcher - .matchMockScript(dSpaceRunnableList.get(2).getOptions()))); + .matchMockScript(dSpaceRunnableList.get(3).getOptions()))); } @Test From 7e099a9b29f0ee9cf4e9fe4b918be4163f91d7ff Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 11 Feb 2020 13:03:37 +0100 Subject: [PATCH 031/749] Fix tests for upgraded mockito version --- .../processor/ExportEventProcessorTest.java | 25 ++++++------------- .../service/OpenUrlServiceImplTest.java | 8 +++--- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java index eedfbb352f..eaebe1bdd7 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorTest.java @@ -9,7 +9,8 @@ package org.dspace.statistics.export.processor; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -30,12 +31,15 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; /** * Test for the ExportEventProcessor class */ +@RunWith(MockitoJUnitRunner.class) public class ExportEventProcessorTest extends AbstractDSpaceTest { @Mock @@ -94,11 +98,6 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { exportEventProcessor.itemService = itemService; exportEventProcessor.context = context; - when(item.isArchived()).thenReturn(true); - when(itemService.canEdit(context, item)).thenReturn(false); - when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); - when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); - when(exportEventProcessor.shouldProcessItem(null)).thenCallRealMethod(); boolean result = exportEventProcessor.shouldProcessItem(null); assertThat(result, is(false)); @@ -112,10 +111,6 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { exportEventProcessor.itemService = itemService; exportEventProcessor.context = context; - when(itemService.canEdit(context, item)).thenReturn(false); - when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); - when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); - when(item.isArchived()).thenReturn(false); when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); @@ -132,8 +127,6 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { exportEventProcessor.context = context; when(item.isArchived()).thenReturn(true); - when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(true); - when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); when(itemService.canEdit(context, item)).thenReturn(true); @@ -153,8 +146,6 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { when(item.isArchived()).thenReturn(true); when(itemService.canEdit(context, item)).thenReturn(false); - when(exportEventProcessor.shouldProcessEntityType(item)).thenReturn(true); - when(exportEventProcessor.shouldProcessItemType(item)).thenReturn(false); when(exportEventProcessor.shouldProcessItem(item)).thenCallRealMethod(); @@ -292,8 +283,7 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { List values = new ArrayList<>(); values.add(metadataValue); - when(itemService.getMetadata(any(Item.class), any(String.class), any(String.class), any(String.class), - any(String.class))).thenReturn(values); + doReturn(values).when(itemService).getMetadata(item, "dc", "type", null, Item.ANY); when(exportEventProcessor.shouldProcessItemType(item)).thenCallRealMethod(); @@ -323,8 +313,7 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { List values = new ArrayList<>(); values.add(metadataValue); - when(itemService.getMetadata(any(Item.class), any(String.class), any(String.class), any(String.class), - any(String.class))).thenReturn(values); + doReturn(values).when(itemService).getMetadata(item, "dc", "type", null, Item.ANY); when(exportEventProcessor.shouldProcessItemType(item)).thenCallRealMethod(); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java index a0f02a7bb6..192b771458 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -8,8 +8,8 @@ package org.dspace.statistics.export.service; import static org.hamcrest.CoreMatchers.is; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -32,7 +32,7 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; /** * Test class for the OpenUrlServiceImpl @@ -58,8 +58,6 @@ public class OpenUrlServiceImplTest { doReturn(HttpURLConnection.HTTP_OK).when(openUrlService) .getResponseCodeFromUrl(anyString()); - doNothing().when(openUrlService).logfailed(any(Context.class), anyString()); - openUrlService.processUrl(context, "test-url"); verify(openUrlService, times(0)).logfailed(context, "test-url"); From 3107a1dee175a556766be1942f8b46e1d534ec38 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Thu, 13 Feb 2020 18:31:58 +0100 Subject: [PATCH 032/749] Also renaming personOrOrganization to personOrOrgunit --- dspace/config/spring/api/discovery.xml | 6 +++--- dspace/config/submission-forms.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 49f12f70a9..9f8e163866 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -63,7 +63,7 @@ - + @@ -1096,9 +1096,9 @@ - - + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 39662515d8..5f023a3b8f 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -614,7 +614,7 @@ isAuthorOfPublication - personOrOrganization + personOrOrgunit true true From aaca3a35d140351e54bb1c48aa4b7df1eb4be82b Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Fri, 14 Feb 2020 10:07:10 +0100 Subject: [PATCH 033/749] Also fixing relation field config --- .../src/test/data/dspaceFolder/config/submission-forms.xml | 2 +- .../java/org/dspace/app/rest/SubmissionFormsControllerIT.java | 2 +- dspace/config/submission-forms.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 6ddfef9b83..d3d396ffb1 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -237,7 +237,7 @@ it, please enter the types and the actual numbers or codes.

- isVolumeOfJournal + isJournalOfVolume periodical creativework.publisher:somepublishername diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index f024d38c92..f88b030620 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -138,7 +138,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe // check the first two rows .andExpect(jsonPath("$.rows[0].fields", contains( SubmissionFormFieldMatcher.matchFormClosedRelationshipFieldDefinition("Journal", null, - false,"Select the journal related to this volume.", "isVolumeOfJournal", + false,"Select the journal related to this volume.", "isJournalOfVolume", "creativework.publisher:somepublishername", "periodical", false)))) ; } diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 5f023a3b8f..9729fb74c5 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -489,7 +489,7 @@ - isVolumeOfJournal + isJournalOfVolume journal creativework.publisher:somepublishername From b8bca78e508d53f77cf4a49ac31a1eaaf0f78767 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 18 Feb 2020 12:50:55 +0100 Subject: [PATCH 034/749] When moving metadata, make sure virtual metadata is handled correctly --- .../content/DSpaceObjectServiceImpl.java | 102 ++++++++++-------- .../org/dspace/content/ItemServiceImpl.java | 27 +++++ .../content/RelationshipMetadataValue.java | 6 ++ .../content/service/DSpaceObjectService.java | 31 +++--- 4 files changed, 107 insertions(+), 59 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index ee188dc144..2d55622e7b 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -207,8 +207,8 @@ public abstract class DSpaceObjectServiceImpl implements } @Override - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - List values) throws SQLException { + public List addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, List values) throws SQLException { MetadataField metadataField = metadataFieldService.findByElement(context, schema, element, qualifier); if (metadataField == null) { throw new SQLException( @@ -216,12 +216,12 @@ public abstract class DSpaceObjectServiceImpl implements "exist!"); } - addMetadata(context, dso, metadataField, lang, values); + return addMetadata(context, dso, metadataField, lang, values); } @Override - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - List values, List authorities, List confidences) + public List addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, List values, List authorities, List confidences) throws SQLException { // We will not verify that they are valid entries in the registry // until update() is called. @@ -231,15 +231,16 @@ public abstract class DSpaceObjectServiceImpl implements "bad_dublin_core schema=" + schema + "." + element + "." + qualifier + ". Metadata field does not " + "exist!"); } - addMetadata(context, dso, metadataField, lang, values, authorities, confidences); + return addMetadata(context, dso, metadataField, lang, values, authorities, confidences); } @Override - public void addMetadata(Context context, T dso, MetadataField metadataField, String lang, List values, - List authorities, List confidences) + public List addMetadata(Context context, T dso, MetadataField metadataField, String lang, + List values, List authorities, List confidences) throws SQLException { boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField); boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField); + List newMetadata = new ArrayList<>(values.size()); // We will not verify that they are valid entries in the registry // until update() is called. for (int i = 0; i < values.size(); i++) { @@ -250,6 +251,7 @@ public abstract class DSpaceObjectServiceImpl implements } } MetadataValue metadataValue = metadataValueService.create(context, dso, metadataField); + newMetadata.add(metadataValue); //Set place to list length of all metadatavalues for the given schema.element.qualifier combination. // Subtract one to adhere to the 0 as first element rule metadataValue.setPlace( @@ -304,29 +306,31 @@ public abstract class DSpaceObjectServiceImpl implements // metadataValueService.update(context, metadataValue); dso.addDetails(metadataField.toString()); } + return newMetadata; } @Override - public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value, - String authority, int confidence) throws SQLException { - addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority), - Arrays.asList(confidence)); + public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, + String value, String authority, int confidence) throws SQLException { + return addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority), + Arrays.asList(confidence)).get(0); } @Override - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - String value) throws SQLException { - addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)); + public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, String value) throws SQLException { + return addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)).get(0); } @Override - public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value) + public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value) throws SQLException { - addMetadata(context, dso, metadataField, language, Arrays.asList(value)); + return addMetadata(context, dso, metadataField, language, Arrays.asList(value)).get(0); } @Override - public void addMetadata(Context context, T dso, MetadataField metadataField, String language, List values) + public List addMetadata(Context context, T dso, MetadataField metadataField, String language, + List values) throws SQLException { if (metadataField != null) { String fieldKey = metadataAuthorityService @@ -343,18 +347,19 @@ public abstract class DSpaceObjectServiceImpl implements getAuthoritiesAndConfidences(fieldKey, null, values, authorities, confidences, i); } } - addMetadata(context, dso, metadataField, language, values, authorities, confidences); + return addMetadata(context, dso, metadataField, language, values, authorities, confidences); } else { - addMetadata(context, dso, metadataField, language, values, null, null); + return addMetadata(context, dso, metadataField, language, values, null, null); } } + return new ArrayList<>(0); } @Override - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - String value, String authority, int confidence) throws SQLException { - addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value), Arrays.asList(authority), - Arrays.asList(confidence)); + public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, String value, String authority, int confidence) throws SQLException { + return addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value), + Arrays.asList(authority), Arrays.asList(confidence)).get(0); } @Override @@ -660,33 +665,35 @@ public abstract class DSpaceObjectServiceImpl implements @Override public void addAndShiftRightMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, String value, String authority, int confidence, int index) - throws SQLException { + throws SQLException { List list = getMetadata(dso, schema, element, qualifier); - clearMetadata(context, dso, schema, element, qualifier, Item.ANY); - int idx = 0; + int place = 0; boolean last = true; for (MetadataValue rr : list) { if (idx == index) { - addMetadata(context, dso, schema, element, qualifier, - lang, value, authority, confidence); + MetadataValue newMetadata = addMetadata(context, dso, schema, element, qualifier, + lang, value, authority, confidence); + + moveSingleMetadataValue(context, dso, schema, element, qualifier, place, newMetadata); + place++; last = false; } - addMetadata(context, dso, schema, element, qualifier, - rr.getLanguage(), rr.getValue(), rr.getAuthority(), rr.getConfidence()); + moveSingleMetadataValue(context, dso, schema, element, qualifier, place, rr); + place++; idx++; } if (last) { addMetadata(context, dso, schema, element, qualifier, - lang, value, authority, confidence); + lang, value, authority, confidence); } } @Override public void moveMetadata(Context context, T dso, String schema, String element, String qualifier, int from, int to) - throws SQLException, IllegalArgumentException { + throws SQLException, IllegalArgumentException { if (from == to) { throw new IllegalArgumentException("The \"from\" location MUST be different from \"to\" location"); @@ -696,11 +703,9 @@ public abstract class DSpaceObjectServiceImpl implements if (from >= list.size()) { throw new IllegalArgumentException( - "The \"from\" location MUST exist for the operation to be successful. Idx:" + from); + "The \"from\" location MUST exist for the operation to be successful. Idx:" + from); } - clearMetadata(context, dso, schema, element, qualifier, Item.ANY); - int idx = 0; MetadataValue moved = null; for (MetadataValue md : list) { @@ -712,30 +717,39 @@ public abstract class DSpaceObjectServiceImpl implements } idx = 0; + int place = 0; boolean last = true; for (MetadataValue rr : list) { if (idx == to && to < from) { - addMetadata(context, dso, schema, element, qualifier, moved.getLanguage(), moved.getValue(), - moved.getAuthority(), moved.getConfidence()); + moveSingleMetadataValue(context, dso, schema, element, qualifier, place, moved); + place++; last = false; } if (idx != from) { - addMetadata(context, dso, schema, element, qualifier, rr.getLanguage(), rr.getValue(), - rr.getAuthority(), rr.getConfidence()); + moveSingleMetadataValue(context, dso, schema, element, qualifier, place, rr); + place++; } if (idx == to && to > from) { - addMetadata(context, dso, schema, element, qualifier, moved.getLanguage(), moved.getValue(), - moved.getAuthority(), moved.getConfidence()); + moveSingleMetadataValue(context, dso, schema, element, qualifier, place, moved); + place++; last = false; } idx++; } if (last) { - addMetadata(context, dso, schema, element, qualifier, moved.getLanguage(), moved.getValue(), - moved.getAuthority(), moved.getConfidence()); + moveSingleMetadataValue(context, dso, schema, element, qualifier, place, moved); } } + /** + * Supports moving metadata by updating the place of the metadata value + */ + protected void moveSingleMetadataValue(Context context, T dso, String schema, String element, + String qualifier, int place, MetadataValue rr) throws SQLException { + //just move the metadata + rr.setPlace(place); + } + @Override public void replaceMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, String value, String authority, int confidence, int index) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 9502a2ca32..ab302a3f9a 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1372,6 +1372,33 @@ prevent the generation of resource policy entry values with null dspace_object a } + /** + * Supports moving metadata by adding the metadata value or updating the place of the relationship + */ + @Override + protected void moveSingleMetadataValue(Context context, Item dso, String schema, String element, + String qualifier, int place, MetadataValue rr) throws SQLException { + if (rr instanceof RelationshipMetadataValue) { + try { + //Retrieve the applicable relationship + Relationship rs = relationshipService.find(context, + ((RelationshipMetadataValue) rr).getRelationshipId()); + if (rs.getLeftItem() == dso) { + rs.setLeftPlace(place); + } else { + rs.setRightPlace(place); + } + relationshipService.update(context, rs); + } catch (Exception e) { + //should not occur, otherwise metadata can't be updated either + log.error("An error occurred while moving " + rr.getAuthority() + " for item " + dso.getID(), e); + } + } else { + //just move the metadata + rr.setPlace(place); + } + } + /** * This method will sort the List of MetadataValue objects based on the MetadataSchema, MetadataField Element, * MetadataField Qualifier and MetadataField Place in that order. diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java index 88d2e38beb..f443f89ce5 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java @@ -7,6 +7,8 @@ */ package org.dspace.content; +import org.dspace.core.Constants; + /** * This class is used as a representation of MetadataValues for the MetadataValues that are derived from the * Relationships that the item has. This includes the useForPlace property which we'll have to use to determine @@ -57,4 +59,8 @@ public class RelationshipMetadataValue extends MetadataValue { } return super.equals(obj); } + + public int getRelationshipId() { + return Integer.parseInt(getAuthority().substring(Constants.VIRTUAL_AUTHORITY_PREFIX.length())); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java index 753516a373..0fde8484d0 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java @@ -202,8 +202,8 @@ public interface DSpaceObjectService { * @param values the values to add. * @throws SQLException if database error */ - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - List values) throws SQLException; + public List addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, List values) throws SQLException; /** * Add metadata fields. These are appended to existing values. @@ -225,8 +225,8 @@ public interface DSpaceObjectService { * @param confidences the authority confidence (default 0) * @throws SQLException if database error */ - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - List values, List authorities, List confidences) + public List addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, List values, List authorities, List confidences) throws SQLException; /** @@ -244,9 +244,10 @@ public interface DSpaceObjectService { * @param authorities the external authority key for this value (or null) * @param confidences the authority confidence (default 0) * @throws SQLException if database error + * @return */ - public void addMetadata(Context context, T dso, MetadataField metadataField, String lang, List values, - List authorities, List confidences) throws SQLException; + public List addMetadata(Context context, T dso, MetadataField metadataField, String lang, + List values, List authorities, List confidences) throws SQLException; /** * Shortcut for {@link #addMetadata(Context, DSpaceObject, MetadataField, String, List, List, List)} when a single @@ -261,14 +262,14 @@ public interface DSpaceObjectService { * @param confidence * @throws SQLException */ - public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value, - String authority, int confidence) throws SQLException; + public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, + String value, String authority, int confidence) throws SQLException; - public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value) + public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value) throws SQLException; - public void addMetadata(Context context, T dso, MetadataField metadataField, String language, List values) - throws SQLException; + public List addMetadata(Context context, T dso, MetadataField metadataField, String language, + List values) throws SQLException; /** * Add a single metadata field. This is appended to existing @@ -287,8 +288,8 @@ public interface DSpaceObjectService { * @param value the value to add. * @throws SQLException if database error */ - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - String value) throws SQLException; + public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, String value) throws SQLException; /** * Add a single metadata field. This is appended to existing @@ -309,8 +310,8 @@ public interface DSpaceObjectService { * @param confidence the authority confidence (default 0) * @throws SQLException if database error */ - public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, - String value, String authority, int confidence) throws SQLException; + public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier, + String lang, String value, String authority, int confidence) throws SQLException; /** * Clear metadata values. As with getDC above, From 243b0c077fba8047a9718ac326ba426febcb9aca Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 18 Feb 2020 13:29:31 +0100 Subject: [PATCH 035/749] When moving metadata, make sure virtual metadata is handled correctly --- .../dspace/content/DSpaceObjectServiceImpl.java | 15 +++++++-------- .../java/org/dspace/content/ItemServiceImpl.java | 3 +-- .../impl/MetadataValueRemovePatchOperation.java | 14 ++------------ 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index 2d55622e7b..d990ea8a04 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -677,11 +677,11 @@ public abstract class DSpaceObjectServiceImpl implements MetadataValue newMetadata = addMetadata(context, dso, schema, element, qualifier, lang, value, authority, confidence); - moveSingleMetadataValue(context, dso, schema, element, qualifier, place, newMetadata); + moveSingleMetadataValue(context, dso, place, newMetadata); place++; last = false; } - moveSingleMetadataValue(context, dso, schema, element, qualifier, place, rr); + moveSingleMetadataValue(context, dso, place, rr); place++; idx++; } @@ -721,31 +721,30 @@ public abstract class DSpaceObjectServiceImpl implements boolean last = true; for (MetadataValue rr : list) { if (idx == to && to < from) { - moveSingleMetadataValue(context, dso, schema, element, qualifier, place, moved); + moveSingleMetadataValue(context, dso, place, moved); place++; last = false; } if (idx != from) { - moveSingleMetadataValue(context, dso, schema, element, qualifier, place, rr); + moveSingleMetadataValue(context, dso, place, rr); place++; } if (idx == to && to > from) { - moveSingleMetadataValue(context, dso, schema, element, qualifier, place, moved); + moveSingleMetadataValue(context, dso, place, moved); place++; last = false; } idx++; } if (last) { - moveSingleMetadataValue(context, dso, schema, element, qualifier, place, moved); + moveSingleMetadataValue(context, dso, place, moved); } } /** * Supports moving metadata by updating the place of the metadata value */ - protected void moveSingleMetadataValue(Context context, T dso, String schema, String element, - String qualifier, int place, MetadataValue rr) throws SQLException { + protected void moveSingleMetadataValue(Context context, T dso, int place, MetadataValue rr) { //just move the metadata rr.setPlace(place); } diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ab302a3f9a..00ab6df51e 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1376,8 +1376,7 @@ prevent the generation of resource policy entry values with null dspace_object a * Supports moving metadata by adding the metadata value or updating the place of the relationship */ @Override - protected void moveSingleMetadataValue(Context context, Item dso, String schema, String element, - String qualifier, int place, MetadataValue rr) throws SQLException { + protected void moveSingleMetadataValue(Context context, Item dso, int place, MetadataValue rr) { if (rr instanceof RelationshipMetadataValue) { try { //Retrieve the applicable relationship diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java index 5ea17cc3cd..4f2d092eb3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import org.dspace.app.rest.model.MetadataValueRest; @@ -40,18 +41,7 @@ public abstract class MetadataValueRemovePatchOperation mm = getDSpaceObjectService().getMetadata(source, metadata[0], metadata[1], metadata[2], Item.ANY); - getDSpaceObjectService().clearMetadata(context, source, metadata[0], metadata[1], metadata[2], Item.ANY); - if (index != -1) { - int idx = 0; - for (MetadataValue m : mm) { - if (idx != index) { - getDSpaceObjectService().addMetadata(context, source, metadata[0], metadata[1], metadata[2], - m.getLanguage(), m.getValue(), m.getAuthority(), - m.getConfidence()); - } - idx++; - } - } + getDSpaceObjectService().removeMetadataValues(context, source, Arrays.asList(mm.get(index))); } protected abstract DSpaceObjectService getDSpaceObjectService(); From cc2534ddd3f3a045d9df4f6bd5cbea7c42a5cb93 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 18 Feb 2020 14:41:48 +0100 Subject: [PATCH 036/749] When moving metadata, make sure virtual metadata is handled correctly --- .../factory/impl/MetadataValueRemovePatchOperation.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java index 4f2d092eb3..1660a5455a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java @@ -41,7 +41,11 @@ public abstract class MetadataValueRemovePatchOperation mm = getDSpaceObjectService().getMetadata(source, metadata[0], metadata[1], metadata[2], Item.ANY); - getDSpaceObjectService().removeMetadataValues(context, source, Arrays.asList(mm.get(index))); + if (index != -1) { + getDSpaceObjectService().removeMetadataValues(context, source, Arrays.asList(mm.get(index))); + } else { + getDSpaceObjectService().clearMetadata(context, source, metadata[0], metadata[1], metadata[2], Item.ANY); + } } protected abstract DSpaceObjectService getDSpaceObjectService(); From 71c5cc678f9551a1f67f7e9adbfa42b9c86b6b14 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Wed, 19 Feb 2020 14:50:26 +0100 Subject: [PATCH 037/749] When moving metadata, make sure virtual metadata is handled correctly --- .../dspace/content/DSpaceObjectServiceImpl.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index d990ea8a04..2751e2113f 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -755,19 +755,8 @@ public abstract class DSpaceObjectServiceImpl implements List list = getMetadata(dso, schema, element, qualifier); - clearMetadata(context, dso, schema, element, qualifier, Item.ANY); - - int idx = 0; - for (MetadataValue rr : list) { - if (idx == index) { - addMetadata(context, dso, schema, element, qualifier, - lang, value, authority, confidence); - } else { - addMetadata(context, dso, schema, element, qualifier, - rr.getLanguage(), rr.getValue(), rr.getAuthority(), rr.getConfidence()); - } - idx++; - } + removeMetadataValues(context, dso, Arrays.asList(list.get(index))); + addAndShiftRightMetadata(context, dso, schema, element, qualifier, lang, value, authority, confidence, index); } } From 42e0f2fb23eae1e1fbe27debb3bb188827e2243e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 21 Feb 2020 14:18:54 +0100 Subject: [PATCH 038/749] 68820: Moving relationships in workspace items ITs --- .../org/dspace/app/rest/PatchMetadataIT.java | 292 ++++++++++++++++++ .../rest/builder/WorkspaceItemBuilder.java | 4 + 2 files changed, 296 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java new file mode 100644 index 0000000000..fc496b9453 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -0,0 +1,292 @@ +package org.dspace.app.rest; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.builder.WorkspaceItemBuilder; +import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.model.patch.MoveOperation; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.RelationshipType; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.content.service.WorkspaceItemService; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +/** + * Created by kristof on 20/02/2020 + */ +public class PatchMetadataIT extends AbstractEntityIntegrationTest { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private ItemService itemService; + + @Autowired + private WorkspaceItemService workspaceItemService; + + private Collection collection; + private WorkspaceItem publicationItem; + private Item personItem1; + private Item personItem2; + private RelationshipType publicationPersonRelationshipType; + + private List authorsOriginalOrder; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Parent community") + .build(); + collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .build(); + + context.restoreAuthSystemState(); + } + + private void initPersonPublicationWorkspace() throws Exception { + // Setup the original order of authors + authorsOriginalOrder = new ArrayList<>(); + authorsOriginalOrder.add("Whyte, William"); + // Second one will be virtual metadata + authorsOriginalOrder.add("Dahlen, Sarah"); + authorsOriginalOrder.add("Peterson, Karrie"); + authorsOriginalOrder.add("Perotti, Enrico"); + // 5th one will be virtual metadata + authorsOriginalOrder.add("Linton, Oliver"); + + context.turnOffAuthorisationSystem(); + + personItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Person 1") + .withPersonIdentifierFirstName("Sarah") + .withPersonIdentifierLastName("Dahlen") + .withRelationshipType("Person") + .build(); + personItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Person 2") + .withPersonIdentifierFirstName("Oliver") + .withPersonIdentifierLastName("Linton") + .withRelationshipType("Person") + .build(); + publicationItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Publication 1") + .withRelationshipType("Publication") + .build(); + publicationPersonRelationshipType = relationshipTypeService.findbyTypesAndTypeName(context, + entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", + "isPublicationOfAuthor"); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // Make sure we grab the latest instance of the Item from the database before adding a regular author + WorkspaceItem publication = workspaceItemService.find(context, publicationItem.getID()); + itemService.addMetadata(context, publication.getItem(), + "dc", "contributor", "author", Item.ANY, authorsOriginalOrder.get(0)); + workspaceItemService.update(context, publication); + + context.restoreAuthSystemState(); + + // Create a relationship between publication and person 1 + MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", publicationPersonRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/core/items/" + publicationItem.getItem().getID() + "\n" + + "https://localhost:8080/server/api/core/items/" + personItem1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + context.turnOffAuthorisationSystem(); + + // Add two more regular authors + List regularMetadata = new ArrayList<>(); + publication = workspaceItemService.find(context, publicationItem.getID()); + regularMetadata.add(authorsOriginalOrder.get(2)); + regularMetadata.add(authorsOriginalOrder.get(3)); + itemService.addMetadata(context, publication.getItem(), + "dc", "contributor", "author", null, regularMetadata); + workspaceItemService.update(context, publication); + + context.restoreAuthSystemState(); + + // Create a relationship between publication and person 2 + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", publicationPersonRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/core/items/" + publicationItem.getItem().getID() + "\n" + + "https://localhost:8080/server/api/core/items/" + personItem2.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + publication = workspaceItemService.find(context, publicationItem.getID()); + List publicationAuthorList = + itemService.getMetadata(publication.getItem(), "dc", "contributor", "author", Item.ANY); + assertThat(publicationAuthorList.size(), equalTo(5)); + assertThat(publicationAuthorList.get(0).getValue(), equalTo(authorsOriginalOrder.get(0))); + assertThat(publicationAuthorList.get(0).getAuthority(), not(startsWith("virtual::"))); + assertThat(publicationAuthorList.get(1).getValue(), equalTo(authorsOriginalOrder.get(1))); + assertThat(publicationAuthorList.get(1).getAuthority(), startsWith("virtual::")); + assertThat(publicationAuthorList.get(2).getValue(), equalTo(authorsOriginalOrder.get(2))); + assertThat(publicationAuthorList.get(2).getAuthority(), not(startsWith("virtual::"))); + assertThat(publicationAuthorList.get(3).getValue(), equalTo(authorsOriginalOrder.get(3))); + assertThat(publicationAuthorList.get(3).getAuthority(), not(startsWith("virtual::"))); + assertThat(publicationAuthorList.get(4).getValue(), equalTo(authorsOriginalOrder.get(4))); + assertThat(publicationAuthorList.get(4).getAuthority(), startsWith("virtual::")); + } + + @Test + public void moveTraditionalPageOneAuthorOneToZeroTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + moveTraditionalPageOneAuthorTest(1, 0, expectedOrder); + } + + @Test + public void moveTraditionalPageOneAuthorTwoToZeroTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + moveTraditionalPageOneAuthorTest(2, 0, expectedOrder); + } + + @Test + public void moveTraditionalPageOneAuthorThreeToZeroTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + moveTraditionalPageOneAuthorTest(3, 0, expectedOrder); + } + + @Test + public void moveTraditionalPageOneAuthorFourToZeroTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(4)); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + + moveTraditionalPageOneAuthorTest(4, 0, expectedOrder); + } + + @Test + public void moveTraditionalPageOneAuthorOneToThreeTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + moveTraditionalPageOneAuthorTest(1, 3, expectedOrder); + } + + @Test + public void moveTraditionalPageOneAuthorOneToFourTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + expectedOrder.add(authorsOriginalOrder.get(1)); + + moveTraditionalPageOneAuthorTest(1, 4, expectedOrder); + } + + private void moveTraditionalPageOneAuthorTest(int from, int path, List expectedOrder) throws Exception { + List ops = new ArrayList(); + MoveOperation moveOperation = getTraditionalPageOneMoveAuthorOperation(from, path); + ops.add(moveOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + String authorField = "dc.contributor.author"; + getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) + ))); + } + + private MoveOperation getTraditionalPageOneMoveAuthorOperation(int from, int path) { + return new MoveOperation("/sections/traditionalpageone/dc.contributor.author/" + path, + "/sections/traditionalpageone/dc.contributor.author/" + from); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java index 1d670bb3e1..eb5994dd32 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java @@ -144,6 +144,10 @@ public class WorkspaceItemBuilder extends AbstractBuilder Date: Fri, 21 Feb 2020 17:27:25 +0100 Subject: [PATCH 039/749] 68820: PatchMetadataIT JavaDocs + header --- .../org/dspace/app/rest/PatchMetadataIT.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index fc496b9453..0c86e02b05 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -1,3 +1,10 @@ +/** + * 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.CoreMatchers.equalTo; @@ -80,6 +87,15 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { context.restoreAuthSystemState(); } + /** + * A method to create a workspace publication containing 5 authors: 3 regular authors and 2 related Person items. + * The authors are added in a specific order: + * - "Whyte, William": Regular author + * - "Dahlen, Sarah": Related Person + * - "Peterson, Karrie": Regular author + * - "Perotti, Enrico": Regular author + * - "Linton, Oliver": Related Person + */ private void initPersonPublicationWorkspace() throws Exception { // Setup the original order of authors authorsOriginalOrder = new ArrayList<>(); @@ -174,6 +190,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { assertThat(publicationAuthorList.get(4).getAuthority(), startsWith("virtual::")); } + /** + * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position 1 to 0 using a PATCH request and verify the order of the authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 1,0,2,3,4 + */ @Test public void moveTraditionalPageOneAuthorOneToZeroTest() throws Exception { initPersonPublicationWorkspace(); @@ -188,6 +210,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(1, 0, expectedOrder); } + /** + * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position 2 to 0 using a PATCH request and verify the order of the authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 2,0,1,3,4 + */ @Test public void moveTraditionalPageOneAuthorTwoToZeroTest() throws Exception { initPersonPublicationWorkspace(); @@ -202,6 +230,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(2, 0, expectedOrder); } + /** + * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position 3 to 0 using a PATCH request and verify the order of the authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 3,0,1,2,4 + */ @Test public void moveTraditionalPageOneAuthorThreeToZeroTest() throws Exception { initPersonPublicationWorkspace(); @@ -216,6 +250,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(3, 0, expectedOrder); } + /** + * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position 4 to 0 using a PATCH request and verify the order of the authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 4,0,1,2,3 + */ @Test public void moveTraditionalPageOneAuthorFourToZeroTest() throws Exception { initPersonPublicationWorkspace(); @@ -230,6 +270,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(4, 0, expectedOrder); } + /** + * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position 1 to 3 using a PATCH request and verify the order of the authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,2,3,1,4 + */ @Test public void moveTraditionalPageOneAuthorOneToThreeTest() throws Exception { initPersonPublicationWorkspace(); @@ -244,6 +290,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(1, 3, expectedOrder); } + /** + * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position 1 to 4 using a PATCH request and verify the order of the authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,2,3,4,1 + */ @Test public void moveTraditionalPageOneAuthorOneToFourTest() throws Exception { initPersonPublicationWorkspace(); @@ -258,6 +310,14 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(1, 4, expectedOrder); } + /** + * This method moves an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from position "from" to "path" using a PATCH request and verifies the order of the authors within the + * section using an ordered list of expected author names. + * @param from The "from" index to use for the Move operation + * @param path The "path" index to use for the Move operation + * @param expectedOrder A list of author names sorted in the expected order + */ private void moveTraditionalPageOneAuthorTest(int from, int path, List expectedOrder) throws Exception { List ops = new ArrayList(); MoveOperation moveOperation = getTraditionalPageOneMoveAuthorOperation(from, path); @@ -284,6 +344,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { ))); } + /** + * Create a move operation on a workspace item's "traditionalpageone" section for + * metadata field "dc.contributor.author". + * @param from The "from" index to use for the Move operation + * @param path The "path" index to use for the Move operation + */ private MoveOperation getTraditionalPageOneMoveAuthorOperation(int from, int path) { return new MoveOperation("/sections/traditionalpageone/dc.contributor.author/" + path, "/sections/traditionalpageone/dc.contributor.author/" + from); From 6748d63bba87a2c141cf94b800b73fdccca88df3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 24 Feb 2020 12:52:07 +0100 Subject: [PATCH 040/749] 68850: Add tests for adding authors on different places on workspace item --- .../org/dspace/app/rest/PatchMetadataIT.java | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 0c86e02b05..231ee11538 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -26,6 +26,7 @@ import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.MoveOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; @@ -71,6 +72,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { private List authorsOriginalOrder; + private String addedAuthor; + @Before @Override public void setUp() throws Exception { @@ -107,6 +110,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { // 5th one will be virtual metadata authorsOriginalOrder.add("Linton, Oliver"); + addedAuthor = "Semple, Robert"; + context.turnOffAuthorisationSystem(); personItem1 = ItemBuilder.createItem(context, collection) @@ -310,6 +315,138 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(1, 4, expectedOrder); } + /** + * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 0 using a PATCH request and verify the place of the new author and the order of the + * authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: +,0,1,2,3,4 (with + being the new author) + */ + public void addAuthorOnTraditionalPageOnePlaceZero() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(addedAuthor); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + addTraditionalPageOneAuthorTest("0", expectedOrder); + + } + + /** + * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 1 using a PATCH request and verify the place of the new author and the order of the + * authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,+,1,2,3,4 (with + being the new author) + */ + public void addAuthorOnTraditionalPageOnePlaceOne() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(addedAuthor); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + addTraditionalPageOneAuthorTest("1", expectedOrder); + + } + + /** + * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 2 using a PATCH request and verify the place of the new author and the order of the + * authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,1,+,2,3,4 (with + being the new author) + */ + public void addAuthorOnTraditionalPageOnePlaceTwo() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(addedAuthor); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + addTraditionalPageOneAuthorTest("2", expectedOrder); + + } + + /** + * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 3 using a PATCH request and verify the place of the new author and the order of the + * authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,1,2,+,3,4 (with + being the new author) + */ + public void addAuthorOnTraditionalPageOnePlaceThree() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(addedAuthor); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + addTraditionalPageOneAuthorTest("3", expectedOrder); + + } + + /** + * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 4 using a PATCH request and verify the place of the new author and the order of the + * authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,1,2,3,+,4 (with + being the new author) + */ + public void addAuthorOnTraditionalPageOnePlaceFour() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(addedAuthor); + expectedOrder.add(authorsOriginalOrder.get(4)); + + addTraditionalPageOneAuthorTest("4", expectedOrder); + + } + + /** + * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 0 using a PATCH request and verify the place of the new author and the order of the + * authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: +,0,1,2,3,4 (with + being the new author) + */ + public void addAuthorOnTraditionalPageOneLastPlace() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + expectedOrder.add(addedAuthor); + + addTraditionalPageOneAuthorTest("-", expectedOrder); + + } + /** * This method moves an author (dc.description.author) within a workspace publication's "traditionalpageone" * section from position "from" to "path" using a PATCH request and verifies the order of the authors within the @@ -344,6 +481,41 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { ))); } + /** + * This method adds an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section to the position "path" using a PATCH request and verifies the place of the new author and the + * order of the previous authors within the section using an ordered list of expected author names. + * @param path The "path" index to use for the Add operation + * @param expectedOrder A list of author names sorted in the expected order + */ + private void addTraditionalPageOneAuthorTest(String path, List expectedOrder) throws Exception { + List ops = new ArrayList(); + AddOperation addOperation = new AddOperation("/sections/traditionalpageone/dc.contributor.author/" + path, + addedAuthor); + ops.add(addOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + String authorField = "dc.contributor.author"; + getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(5), 5)) + ))); + } + /** * Create a move operation on a workspace item's "traditionalpageone" section for * metadata field "dc.contributor.author". From 0691330d847d9659b8cc74f6f3e819e6f5aa5043 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 24 Feb 2020 15:23:53 +0100 Subject: [PATCH 041/749] 68851: Add test to Remove metadata through PATCH --- .../org/dspace/app/rest/PatchMetadataIT.java | 188 +++++++++++++++++- 1 file changed, 181 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 231ee11538..7b76ea9a8f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -26,9 +26,11 @@ import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.MoveOperation; import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.RemoveOperation; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -322,7 +324,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * Original Order: 0,1,2,3,4 * Expected Order: +,0,1,2,3,4 (with + being the new author) */ - public void addAuthorOnTraditionalPageOnePlaceZero() throws Exception { + @Test + public void addAuthorOnTraditionalPageOnePlaceZeroTest() throws Exception { initPersonPublicationWorkspace(); List expectedOrder = new ArrayList<>(); @@ -344,7 +347,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * Original Order: 0,1,2,3,4 * Expected Order: 0,+,1,2,3,4 (with + being the new author) */ - public void addAuthorOnTraditionalPageOnePlaceOne() throws Exception { + @Test + public void addAuthorOnTraditionalPageOnePlaceOneTest() throws Exception { initPersonPublicationWorkspace(); List expectedOrder = new ArrayList<>(); @@ -366,7 +370,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * Original Order: 0,1,2,3,4 * Expected Order: 0,1,+,2,3,4 (with + being the new author) */ - public void addAuthorOnTraditionalPageOnePlaceTwo() throws Exception { + @Test + public void addAuthorOnTraditionalPageOnePlaceTwoTest() throws Exception { initPersonPublicationWorkspace(); List expectedOrder = new ArrayList<>(); @@ -388,7 +393,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * Original Order: 0,1,2,3,4 * Expected Order: 0,1,2,+,3,4 (with + being the new author) */ - public void addAuthorOnTraditionalPageOnePlaceThree() throws Exception { + @Test + public void addAuthorOnTraditionalPageOnePlaceThreeTest() throws Exception { initPersonPublicationWorkspace(); List expectedOrder = new ArrayList<>(); @@ -410,7 +416,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * Original Order: 0,1,2,3,4 * Expected Order: 0,1,2,3,+,4 (with + being the new author) */ - public void addAuthorOnTraditionalPageOnePlaceFour() throws Exception { + @Test + public void addAuthorOnTraditionalPageOnePlaceFourTest() throws Exception { initPersonPublicationWorkspace(); List expectedOrder = new ArrayList<>(); @@ -432,7 +439,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * Original Order: 0,1,2,3,4 * Expected Order: +,0,1,2,3,4 (with + being the new author) */ - public void addAuthorOnTraditionalPageOneLastPlace() throws Exception { + @Test + public void addAuthorOnTraditionalPageOneLastPlaceTest() throws Exception { initPersonPublicationWorkspace(); List expectedOrder = new ArrayList<>(); @@ -445,6 +453,137 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { addTraditionalPageOneAuthorTest("-", expectedOrder); + } + + /** + * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * section at position 0 using a PATCH request and verify the order of the remaining authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 1,2,3,4 + */ + @Test + public void removeAuthorOnTraditionalPageFromPlaceZeroTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + removeTraditionalPageOneAuthorTest(0, expectedOrder); + } + /** + * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * section at position 1 using a PATCH request and verify the order of the remaining authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,2,3,4 + */ + @Test + public void removeAuthorOnTraditionalPageFromPlaceOneTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + // The author at the first place is linked through a relationship and cannot be deleted through a PATCH request + removeTraditionalPageOneAuthorTest(1, expectedOrder); + } + /** + * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * section at position 2 using a PATCH request and verify the order of the remaining authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,1,3,4 + */ + @Test + public void removeAuthorOnTraditionalPageFromPlaceTwoTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + removeTraditionalPageOneAuthorTest(2, expectedOrder); + } + /** + * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * section at position 3 using a PATCH request and verify the order of the remaining authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,1,2,4 + */ + @Test + public void removeAuthorOnTraditionalPageFromPlaceThreeTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + removeTraditionalPageOneAuthorTest(3, expectedOrder); + } + /** + * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * section at position 4 using a PATCH request and verify the order of the remaining authors within the section. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,1,2,3 + */ + @Test + public void removeAuthorOnTraditionalPageFromPlaceFourTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + // The author at the fourth place is linked through a relationship and cannot be deleted through a PATCH request + removeTraditionalPageOneAuthorTest(4, expectedOrder); + } + + /** + * This test will remove all authors (dc.description.author) from a workspace publication's "traditionalpageone" + * section using a PATCH request and verify that there are no remaining authors within the section. + */ + @Test + public void removeAllAuthorsOnTraditionalPageTest() throws Exception { + initPersonPublicationWorkspace(); + + List ops = new ArrayList(); + RemoveOperation removeOperation = new RemoveOperation("/sections/traditionalpageone/dc.contributor.author"); + ops.add(removeOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + String authorField = "dc.contributor.author"; + getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.sections.traditionalpageone", + // The author at the first and fourth place are linked through a relationship + // and cannot be deleted through a PATCH request + Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, authorsOriginalOrder.get(1), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, authorsOriginalOrder.get(4), 1)) + ))); + + + } /** @@ -490,8 +629,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { */ private void addTraditionalPageOneAuthorTest(String path, List expectedOrder) throws Exception { List ops = new ArrayList(); + MetadataValueRest value = new MetadataValueRest(); + value.setValue(addedAuthor); AddOperation addOperation = new AddOperation("/sections/traditionalpageone/dc.contributor.author/" + path, - addedAuthor); + value); ops.add(addOperation); String patchBody = getPatchContent(ops); @@ -516,6 +657,39 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { ))); } + /** + * This method removes an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section from the position "path" using a PATCH request and verifies the order of the remaining authors + * within the section using an ordered list of expected author names. + * @param path The "path" index to use for the Remove operation + * @param expectedOrder A list of author names sorted in the expected order + */ + private void removeTraditionalPageOneAuthorTest(int path, List expectedOrder) throws Exception { + List ops = new ArrayList(); + RemoveOperation removeOperation = new RemoveOperation("/sections/traditionalpageone/dc.contributor.author/" + + path); + ops.add(removeOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + String authorField = "dc.contributor.author"; + getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)) + ))); + } + /** * Create a move operation on a workspace item's "traditionalpageone" section for * metadata field "dc.contributor.author". From af2f1ee15b8a6b7b338d8f372c58649c5bf92933 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 25 Feb 2020 11:30:43 +0100 Subject: [PATCH 042/749] 68919: Add replace tests --- .../org/dspace/app/rest/PatchMetadataIT.java | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 7b76ea9a8f..9508782b39 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -31,6 +31,7 @@ import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.MoveOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -75,6 +76,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { private List authorsOriginalOrder; private String addedAuthor; + private String replacedAuthor; @Before @Override @@ -113,6 +115,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { authorsOriginalOrder.add("Linton, Oliver"); addedAuthor = "Semple, Robert"; + replacedAuthor = "New Value"; context.turnOffAuthorisationSystem(); @@ -317,6 +320,64 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { moveTraditionalPageOneAuthorTest(1, 4, expectedOrder); } + /** + * This test will replace an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 0 using a PATCH request and verify the order and value of the authors within the section. + * @throws Exception + */ + @Test + public void replaceTraditionalPageOneAuthorZeroTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(replacedAuthor); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + replaceTraditionalPageOneAuthorTest(0, expectedOrder); + } + + /** + * This test will replace an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 2 using a PATCH request and verify the order and value of the authors within the section. + * @throws Exception + */ + @Test + public void replaceTraditionalPageOneAuthorTwoTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(replacedAuthor); + expectedOrder.add(authorsOriginalOrder.get(3)); + expectedOrder.add(authorsOriginalOrder.get(4)); + + replaceTraditionalPageOneAuthorTest(2, expectedOrder); + } + + /** + * This test will replace an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position 3 using a PATCH request and verify the order and value of the authors within the section. + * @throws Exception + */ + @Test + public void replaceTraditionalPageOneAuthorThreeTest() throws Exception { + initPersonPublicationWorkspace(); + + List expectedOrder = new ArrayList<>(); + expectedOrder.add(authorsOriginalOrder.get(0)); + expectedOrder.add(authorsOriginalOrder.get(1)); + expectedOrder.add(authorsOriginalOrder.get(2)); + expectedOrder.add(replacedAuthor); + expectedOrder.add(authorsOriginalOrder.get(4)); + + replaceTraditionalPageOneAuthorTest(3, expectedOrder); + } + + /** * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" * section at position 0 using a PATCH request and verify the place of the new author and the order of the @@ -620,6 +681,43 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { ))); } + /** + * This method replaces an author (dc.description.author) within a workspace publication's "traditionalpageone" + * section at position "path" using a PATCH request and verifies the order of the authors within the + * section using an ordered list of expected author names. + * @param path The "path" index to use for the Replace operation + * @param expectedOrder A list of author names sorted in the expected order + */ + private void replaceTraditionalPageOneAuthorTest(int path, List expectedOrder) throws Exception { + List ops = new ArrayList(); + MetadataValueRest value = new MetadataValueRest(); + value.setValue(replacedAuthor); + + ReplaceOperation replaceOperation = new ReplaceOperation("/sections/traditionalpageone/dc.contributor.author/" + + path, value); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + String authorField = "dc.contributor.author"; + getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) + ))); + } + /** * This method adds an author (dc.description.author) within a workspace publication's "traditionalpageone" * section to the position "path" using a PATCH request and verifies the place of the new author and the From 9b442743b69ca91aed952d54f79a80003cb280be Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 25 Feb 2020 13:32:28 +0100 Subject: [PATCH 043/749] 69108: Fix comments --- .../org/dspace/app/rest/PatchMetadataIT.java | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 9508782b39..1959079a84 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -201,7 +201,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will move an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position 1 to 0 using a PATCH request and verify the order of the authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 1,0,2,3,4 @@ -221,7 +221,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will move an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position 2 to 0 using a PATCH request and verify the order of the authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 2,0,1,3,4 @@ -241,7 +241,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will move an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position 3 to 0 using a PATCH request and verify the order of the authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 3,0,1,2,4 @@ -261,7 +261,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will move an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position 4 to 0 using a PATCH request and verify the order of the authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 4,0,1,2,3 @@ -281,7 +281,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will move an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position 1 to 3 using a PATCH request and verify the order of the authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 0,2,3,1,4 @@ -301,7 +301,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will move an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will move an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position 1 to 4 using a PATCH request and verify the order of the authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 0,2,3,4,1 @@ -321,7 +321,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will replace an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will replace an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 0 using a PATCH request and verify the order and value of the authors within the section. * @throws Exception */ @@ -340,7 +340,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will replace an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will replace an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 2 using a PATCH request and verify the order and value of the authors within the section. * @throws Exception */ @@ -359,7 +359,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will replace an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will replace an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 3 using a PATCH request and verify the order and value of the authors within the section. * @throws Exception */ @@ -379,7 +379,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { /** - * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 0 using a PATCH request and verify the place of the new author and the order of the * authors within the section. * Original Order: 0,1,2,3,4 @@ -402,7 +402,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 1 using a PATCH request and verify the place of the new author and the order of the * authors within the section. * Original Order: 0,1,2,3,4 @@ -425,7 +425,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 2 using a PATCH request and verify the place of the new author and the order of the * authors within the section. * Original Order: 0,1,2,3,4 @@ -448,7 +448,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 3 using a PATCH request and verify the place of the new author and the order of the * authors within the section. * Original Order: 0,1,2,3,4 @@ -471,7 +471,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 4 using a PATCH request and verify the place of the new author and the order of the * authors within the section. * Original Order: 0,1,2,3,4 @@ -494,7 +494,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will add an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position 0 using a PATCH request and verify the place of the new author and the order of the * authors within the section. * Original Order: 0,1,2,3,4 @@ -517,7 +517,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * This test will remove the author (dc.contributor.author) from a workspace publication's "traditionalpageone" * section at position 0 using a PATCH request and verify the order of the remaining authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 1,2,3,4 @@ -535,10 +535,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { removeTraditionalPageOneAuthorTest(0, expectedOrder); } /** - * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * This test will remove the author (dc.contributor.author) from a workspace publication's "traditionalpageone" * section at position 1 using a PATCH request and verify the order of the remaining authors within the section. * Original Order: 0,1,2,3,4 - * Expected Order: 0,2,3,4 + * Expected Order: 0,1,2,3,4 */ @Test public void removeAuthorOnTraditionalPageFromPlaceOneTest() throws Exception { @@ -555,7 +555,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { removeTraditionalPageOneAuthorTest(1, expectedOrder); } /** - * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * This test will remove the author (dc.contributor.author) from a workspace publication's "traditionalpageone" * section at position 2 using a PATCH request and verify the order of the remaining authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 0,1,3,4 @@ -573,7 +573,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { removeTraditionalPageOneAuthorTest(2, expectedOrder); } /** - * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * This test will remove the author (dc.contributor.author) from a workspace publication's "traditionalpageone" * section at position 3 using a PATCH request and verify the order of the remaining authors within the section. * Original Order: 0,1,2,3,4 * Expected Order: 0,1,2,4 @@ -591,10 +591,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { removeTraditionalPageOneAuthorTest(3, expectedOrder); } /** - * This test will remove the author (dc.description.author) from a workspace publication's "traditionalpageone" + * This test will remove the author (dc.contributor.author) from a workspace publication's "traditionalpageone" * section at position 4 using a PATCH request and verify the order of the remaining authors within the section. * Original Order: 0,1,2,3,4 - * Expected Order: 0,1,2,3 + * Expected Order: 0,1,2,3,4 */ @Test public void removeAuthorOnTraditionalPageFromPlaceFourTest() throws Exception { @@ -612,8 +612,9 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This test will remove all authors (dc.description.author) from a workspace publication's "traditionalpageone" - * section using a PATCH request and verify that there are no remaining authors within the section. + * This test will remove all authors (dc.contributor.author) that are not linked through a relationship from a + * workspace publication's "traditionalpageone" section using a PATCH request and verify that the only remaining + * authors are those coming from a relationship. */ @Test public void removeAllAuthorsOnTraditionalPageTest() throws Exception { @@ -648,7 +649,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This method moves an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This method moves an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position "from" to "path" using a PATCH request and verifies the order of the authors within the * section using an ordered list of expected author names. * @param from The "from" index to use for the Move operation @@ -682,7 +683,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This method replaces an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This method replaces an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position "path" using a PATCH request and verifies the order of the authors within the * section using an ordered list of expected author names. * @param path The "path" index to use for the Replace operation @@ -719,7 +720,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This method adds an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This method adds an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section to the position "path" using a PATCH request and verifies the place of the new author and the * order of the previous authors within the section using an ordered list of expected author names. * @param path The "path" index to use for the Add operation @@ -756,7 +757,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * This method removes an author (dc.description.author) within a workspace publication's "traditionalpageone" + * This method removes an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from the position "path" using a PATCH request and verifies the order of the remaining authors * within the section using an ordered list of expected author names. * @param path The "path" index to use for the Remove operation From 7c15049da11201ea711c3dd792ead29e898309c9 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 9 Mar 2020 16:43:38 +0100 Subject: [PATCH 044/749] [Task 69349] working implementation adminGroups get, post, delete. Tests unfinished --- .../CommunityAdminGroupRestController.java | 110 ++++++++ .../dspace/app/rest/model/CommunityRest.java | 5 + .../CommunityAdminGroupLinkRepository.java | 74 +++++ .../repository/CommunityRestRepository.java | 35 +++ .../CommunityAdminGroupRestControllerIT.java | 263 ++++++++++++++++++ .../app/rest/builder/CommunityBuilder.java | 5 + 6 files changed, 492 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java new file mode 100644 index 0000000000..8c0195ef4e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java @@ -0,0 +1,110 @@ +/** + * 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.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.model.hateoas.GroupResource; +import org.dspace.app.rest.repository.CommunityRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Community; +import org.dspace.content.service.CommunityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +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.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/core/communities" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/adminGroup") +public class CommunityAdminGroupRestController { + + @Autowired + private CommunityService communityService; + + @Autowired + private CommunityRestRepository communityRestRepository; + + @Autowired + private ConverterService converterService; + + + @Autowired + private AuthorizeService authorizeService; + + @RequestMapping(method = RequestMethod.POST) + @PreAuthorize("hasPermission(#uuid, 'COMMUNITY', 'WRITE')") + public ResponseEntity postAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + Community community = communityService.find(context, uuid); + + if (community == null) { + throw new ResourceNotFoundException("No such community: " + uuid); + } + if (!authorizeService.isAdmin(context) && !authorizeService.isCommunityAdmin(context)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + + " community: " + uuid); + } + if (community.getAdministrators() != null) { + throw new UnprocessableEntityException("The community with UUID: " + uuid + " already has " + + "an admin group"); + } + GroupRest adminGroup = communityRestRepository.createAdminGroup(context, request, community); + context.complete(); + GroupResource groupResource = converterService.toResource(adminGroup); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); + } + + @RequestMapping(method = RequestMethod.DELETE) + @PreAuthorize("hasPermission(#uuid, 'COMMUNITY', 'WRITE')") + public ResponseEntity deleteAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + + Context context = ContextUtil.obtainContext(request); + Community community = communityService.find(context, uuid); + if (community == null) { + throw new ResourceNotFoundException("No such community: " + uuid); + } + + if (!authorizeService.isAdmin(context) && !authorizeService.isCommunityAdmin(context)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + + " community: " + uuid); + } + if (community.getAdministrators() == null) { + throw new UnprocessableEntityException("The community with UUID: " + uuid + " doesn't have an admin " + + "group"); + } + communityRestRepository.deleteAdminGroup(context, community); + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } +} 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 ee80c9633b..f8ccbad10e 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 @@ -30,6 +30,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; @LinkRest( name = CommunityRest.PARENT_COMMUNITY, method = "getParentCommunity" + ), + @LinkRest( + name = CommunityRest.ADMIN_GROUP, + method = "getAdminGroup" ) }) public class CommunityRest extends DSpaceObjectRest { @@ -41,6 +45,7 @@ public class CommunityRest extends DSpaceObjectRest { public static final String LOGO = "logo"; public static final String SUBCOMMUNITIES = "subcommunities"; public static final String PARENT_COMMUNITY = "parentCommunity"; + public static final String ADMIN_GROUP = "adminGroup"; @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java new file mode 100644 index 0000000000..7108a06587 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Community; +import org.dspace.content.service.CommunityService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.ADMIN_GROUP) +public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private CommunityService communityService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private GroupService groupService; + + @PreAuthorize("hasPermission(#communityId, 'COMMUNITY', 'READ')") + public GroupRest getAdminGroup(@Nullable HttpServletRequest request, + UUID communityId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Community community = communityService.find(context, communityId); + if (community == null) { + throw new ResourceNotFoundException("No such community: " + communityId); + } + + Group administrators = community.getAdministrators(); + + if (!authorizeService.isAdmin(context) && !authorizeService.isCommunityAdmin(context)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + + " community: " + communityId); + } + if (administrators == null) { + return null; + } + if (!authorizeService.authorizeActionBoolean(context, administrators, Constants.READ)) { + throw new AccessDeniedException("The current user doesn't have sufficient rights to access the admin" + + "group of community with id: " + communityId); + } + return converter.toRest(administrators, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index f17f7d950b..da0a3dd4fd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -24,6 +25,8 @@ import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.utils.CommunityRestEqualityUtils; import org.dspace.authorize.AuthorizeException; @@ -32,6 +35,8 @@ import org.dspace.content.Community; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -58,6 +63,9 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group adminGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + + } + + @Test + public void postCommunityAdminGroupCreateAdminGroupUnAuthorized() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + getClient().perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void postCommunityAdminGroupCreateAdminGroupForbidden() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + } + + @Test + public void postCommunityAdminGroupCreateAdminGroupNotFound() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/communities/" + UUID.randomUUID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void postCommunityAdminGroupCreateAdminGroupUnProcessableName() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setName("Fail"); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void postCommunityAdminGroupCreateAdminGroupUnProcessablePermanent() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setPermanent(true); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void deleteCommunityAdminGroupTest() throws Exception { + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteCommunityAdminGroupUnAuthorizedTest() throws Exception { + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + + + getClient().perform(delete("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteCommunityAdminGroupForbiddenTest() throws Exception { + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isForbidden()); + } + + + @Test + public void deleteCommunityAdminGroupNotFoundTest() throws Exception { + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/communities/" + UUID.randomUUID() + "/adminGroup")) + .andExpect(status().isNotFound()); + } +} 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 788aa502a6..a9e54109ed 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 @@ -135,6 +135,11 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { Community community = communityService.find(c, uuid); if (community != null) { try { + if (community.getAdministrators() != null) { + Group adminGroup = community.getAdministrators(); + communityService.removeAdministrators(c, community); + groupService.delete(c, adminGroup); + } communityService.delete(c, community); } catch (AuthorizeException e) { // cannot occur, just wrap it to make the compiler happy From 02cb339bb88a03e5ddbae0cb86859cd91f4c02d6 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 10 Mar 2020 11:49:13 +0100 Subject: [PATCH 045/749] [Task 69349] fixed community and collection tests with new embed parameter instead of full projection --- .../repository/CommunityRestRepository.java | 1 - .../app/rest/CollectionRestRepositoryIT.java | 48 ++++++++++++------- .../CommunityAdminGroupRestControllerIT.java | 1 - .../app/rest/CommunityRestRepositoryIT.java | 44 ++++++++--------- .../app/rest/matcher/CollectionMatcher.java | 4 ++ .../app/rest/matcher/CommunityMatcher.java | 7 ++- 6 files changed, 64 insertions(+), 41 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index da0a3dd4fd..53a93ab6c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -26,7 +26,6 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.GroupRest; -import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.utils.CommunityRestEqualityUtils; import org.dspace.authorize.AuthorizeException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 547441e73f..f7984a30a4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -75,7 +75,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform(get("/api/core/collections") - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( @@ -109,7 +109,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform(get("/api/core/collections") .param("size", "1") - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.contains( @@ -126,7 +127,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform(get("/api/core/collections") .param("size", "1") .param("page", "1") - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.contains( @@ -166,7 +168,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes // When full projection is requested, response should include expected properties, links, and embeds. getClient().perform(get("/api/core/collections/" + col1.getID()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", CollectionMatcher.matchFullEmbeds())) @@ -205,7 +208,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + col1.getID()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( @@ -353,7 +357,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + col1.getID()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( @@ -383,7 +388,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); getClient().perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -411,7 +417,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ; getClient().perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -453,7 +460,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); getClient(token).perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -498,7 +506,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -547,7 +556,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .content(mapper.writeValueAsBytes(collectionRest)) .param("parent", parentCommunity.getID().toString()) .contentType(contentType) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", CollectionMatcher.matchFullEmbeds())) @@ -720,7 +730,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); getClient(token).perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -757,7 +768,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); getClient().perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -788,7 +800,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ; getClient().perform(get("/api/core/collections/" + col1.getID().toString()) - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -929,7 +942,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform(get("/api/core/collections") - .param("projection", "full")) + .param("embed", CollectionMatcher.getFullEmbedsParameter())) + .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( @@ -984,7 +998,9 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes // .doesNotExist() makes sure that this section is not embedded, it's not there at all .andExpect(jsonPath("$._embedded.logo._embedded.format").doesNotExist()); - getClient().perform(get("/api/core/collections/" + col1.getID()) + // Need this admin call for the AdminGroup embed in the Parentcommunity + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + col1.getID()) .param("projection", "level") .param("embedLevelDepth", "3")) .andExpect(status().isOk()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java index f8fc7877f4..913e1fe0f8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java @@ -222,7 +222,6 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg @Test public void deleteCommunityAdminGroupTest() throws Exception { Group adminGroup = communityService.createAdministrators(context, parentCommunity); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) .andExpect(status().isNoContent()); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 0a315aa5c3..ced792427d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -115,7 +115,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) .contentType(contentType) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", CommunityMatcher.matchFullEmbeds())) @@ -320,7 +320,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); getClient().perform(get("/api/core/communities") - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( @@ -352,8 +352,8 @@ 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("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( @@ -384,8 +384,8 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); - getClient().perform(get("/api/core/communities").param("size", "2").param("projection", - "full")) + getClient().perform(get("/api/core/communities").param("size", "2") + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( @@ -401,7 +401,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest 2, 4))); getClient().perform(get("/api/core/communities").param("size", "2").param("page", "1") - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( @@ -424,7 +424,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest parentCommunity = CommunityBuilder.createCommunity(context).withName("test").build(); getClient().perform(get("/api/core/communities") - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.contains( @@ -488,7 +488,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/core/communities") .param("size", "1") - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.contains( @@ -508,7 +508,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/core/communities") .param("size", "1") .param("page", "1") - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.contains( @@ -547,7 +547,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest // When full projection is requested, response should include expected properties, links, and embeds. getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", CommunityMatcher.matchFullEmbeds())) @@ -583,7 +583,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -665,7 +665,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); getClient().perform(get("/api/core/communities/search/top") - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( @@ -732,7 +732,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/core/communities/search/subCommunities") .param("parent", parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) //Checking that these communities are present @@ -767,7 +767,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/core/communities/search/subCommunities") .param("parent", parentCommunityChild2.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) //Checking that these communities are present @@ -860,7 +860,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -895,7 +895,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ; getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -950,7 +950,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -1013,7 +1013,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -1047,7 +1047,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -1082,7 +1082,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -1120,7 +1120,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ; getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString()) - .param("projection", "full")) + .param("embed", CommunityMatcher.getFullEmbedsParameters())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( 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 77c8247ae9..de5db48901 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 @@ -98,4 +98,8 @@ public class CollectionMatcher { BitstreamMatcher.matchBitstreamEntry(logo.getID(), logo.getSizeBytes())) ); } + + public static String getFullEmbedsParameter() { + return "license,logo,parentCommunity,mappedItems"; + } } 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 9a6bf242e1..d655c9f07b 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 @@ -99,7 +99,8 @@ public class CommunityMatcher { "logo", "self", "parentCommunity", - "subcommunities" + "subcommunities", + "adminGroup" ); } @@ -115,4 +116,8 @@ public class CommunityMatcher { ); } + public static String getFullEmbedsParameters() { + return "collections,logo,parentCommunity,subcommunities"; + } + } From 8f78381ae90740155a57034ae2ce9d86c370b376 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 10 Mar 2020 14:26:35 +0100 Subject: [PATCH 046/749] [Task 69349] changed communityAdmin check and added tests --- .../CommunityAdminGroupRestController.java | 7 +- .../CommunityAdminGroupLinkRepository.java | 7 +- .../CommunityAdminGroupRestControllerIT.java | 66 +++++++++++++++++++ 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java index 8c0195ef4e..3146f15302 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java @@ -25,6 +25,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Community; import org.dspace.content.service.CommunityService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; @@ -69,7 +70,8 @@ public class CommunityAdminGroupRestController { if (community == null) { throw new ResourceNotFoundException("No such community: " + uuid); } - if (!authorizeService.isAdmin(context) && !authorizeService.isCommunityAdmin(context)) { + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, community, + Constants.ADMIN, true)) { throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + " community: " + uuid); } @@ -95,7 +97,8 @@ public class CommunityAdminGroupRestController { throw new ResourceNotFoundException("No such community: " + uuid); } - if (!authorizeService.isAdmin(context) && !authorizeService.isCommunityAdmin(context)) { + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, community, + Constants.ADMIN, true)) { throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + " community: " + uuid); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java index 7108a06587..68172ce177 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java @@ -21,7 +21,6 @@ import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; -import org.dspace.eperson.service.GroupService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -38,9 +37,6 @@ public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestReposit @Autowired private AuthorizeService authorizeService; - @Autowired - private GroupService groupService; - @PreAuthorize("hasPermission(#communityId, 'COMMUNITY', 'READ')") public GroupRest getAdminGroup(@Nullable HttpServletRequest request, UUID communityId, @@ -55,7 +51,8 @@ public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestReposit Group administrators = community.getAdministrators(); - if (!authorizeService.isAdmin(context) && !authorizeService.isCommunityAdmin(context)) { + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, community, + Constants.ADMIN, true)) { throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + " community: " + communityId); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java index 913e1fe0f8..fda6f47f8f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java @@ -24,10 +24,13 @@ 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.authorize.service.AuthorizeService; import org.dspace.content.service.CommunityService; +import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +43,9 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg @Autowired private GroupService groupService; + @Autowired + private AuthorizeService authorizeService; + @Before public void setup() { context.turnOffAuthorisationSystem(); @@ -57,6 +63,19 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); } + @Test + public void getCommunityAdminGroupTestCommunityAdmin() throws Exception { + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + } + + @Test public void getCommunityAdminGroupUnAuthorizedTest() throws Exception { communityService.createAdministrators(context, parentCommunity); @@ -119,6 +138,37 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg } + @Test + public void postCommunityAdminGroupCreateAdminGroupSuccessCommunityAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group adminGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + + } + @Test public void postCommunityAdminGroupCreateAdminGroupUnAuthorized() throws Exception { @@ -230,6 +280,22 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg .andExpect(status().isNoContent()); } + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCommunityAdminGroupTestCommunityAdmin() throws Exception { + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + } + @Test public void deleteCommunityAdminGroupUnAuthorizedTest() throws Exception { Group adminGroup = communityService.createAdministrators(context, parentCommunity); From dc42d3e9b4b0c6d95ca33383a27346692c825d7c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 12 Mar 2020 11:37:03 +0100 Subject: [PATCH 047/749] 69609: Process IRUS DSpace 7 PR feedback --- .../java/org/dspace/content/EntityType.java | 13 ++++- .../export/ExportUsageEventListener.java | 58 +++++++++++-------- .../test/data/dspaceFolder/config/local.cfg | 7 +++ .../export/ITExportUsageEventListener.java | 42 +++++++++----- .../export/ITRetryFailedOpenUrlTracker.java | 16 +++-- dspace/config/modules/stats.cfg | 3 + .../spring/rest/event-service-listeners.xml | 6 +- 7 files changed, 97 insertions(+), 48 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java index cc80205162..312052bdec 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityType.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -16,6 +16,7 @@ import javax.persistence.SequenceGenerator; import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.ReloadableEntity; /** @@ -81,7 +82,8 @@ public class EntityType implements ReloadableEntity { } /** - * @param obj + * Determines whether two entity types are equal based on the id and the label + * @param obj object to be compared * @return */ public boolean equals(Object obj) { @@ -99,4 +101,13 @@ public class EntityType implements ReloadableEntity { } return true; } + + /** + * Returns a hash code value for the object. + * @return hash code value + */ + @Override + public int hashCode() { + return new HashCodeBuilder().append(getID()).toHashCode(); + } } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java index 5065d0f54e..2257bcb038 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/ExportUsageEventListener.java @@ -14,11 +14,13 @@ import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.services.ConfigurationService; import org.dspace.services.model.Event; import org.dspace.statistics.export.processor.BitstreamEventProcessor; import org.dspace.statistics.export.processor.ItemEventProcessor; import org.dspace.usage.AbstractUsageEventListener; import org.dspace.usage.UsageEvent; +import org.springframework.beans.factory.annotation.Autowired; /** * Class to receive usage events and send corresponding data to IRUS @@ -27,39 +29,45 @@ public class ExportUsageEventListener extends AbstractUsageEventListener { /* Log4j logger*/ private static Logger log = Logger.getLogger(ExportUsageEventListener.class); + @Autowired + ConfigurationService configurationService; + /** * Receives an event and processes to create a URL to send to IRUS when certain conditions are met + * * @param event includes all the information related to the event that occurred */ public void receiveEvent(Event event) { - if (event instanceof UsageEvent) { - UsageEvent ue = (UsageEvent) event; - Context context = ue.getContext(); + if (configurationService.getBooleanProperty("stats.tracker.enabled", false)) { + if (event instanceof UsageEvent) { + UsageEvent ue = (UsageEvent) event; + Context context = ue.getContext(); - try { - //Check for item investigation - if (ue.getObject() instanceof Item) { - ItemEventProcessor itemEventProcessor = new ItemEventProcessor(context, ue.getRequest(), - (Item) ue.getObject()); - itemEventProcessor.processEvent(); - } else if (ue.getObject() instanceof Bitstream) { - - BitstreamEventProcessor bitstreamEventProcessor = - new BitstreamEventProcessor(context, ue.getRequest(), (Bitstream) ue.getObject()); - bitstreamEventProcessor.processEvent(); - } - } catch (Exception e) { - UUID id; - id = ue.getObject().getID(); - - int type; try { - type = ue.getObject().getType(); - } catch (Exception e1) { - type = -1; + //Check for item investigation + if (ue.getObject() instanceof Item) { + ItemEventProcessor itemEventProcessor = new ItemEventProcessor(context, ue.getRequest(), + (Item) ue.getObject()); + itemEventProcessor.processEvent(); + } else if (ue.getObject() instanceof Bitstream) { + + BitstreamEventProcessor bitstreamEventProcessor = + new BitstreamEventProcessor(context, ue.getRequest(), (Bitstream) ue.getObject()); + bitstreamEventProcessor.processEvent(); + } + } catch (Exception e) { + UUID id; + id = ue.getObject().getID(); + + int type; + try { + type = ue.getObject().getType(); + } catch (Exception e1) { + type = -1; + } + log.error(LogManager.getHeader(ue.getContext(), "Error while processing export of use event", + "Id: " + id + " type: " + type), e); } - log.error(LogManager.getHeader(ue.getContext(), "Error while processing export of use event", - "Id: " + id + " type: " + type), e); } } } diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c4b4a839d..735d9dfec3 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -108,3 +108,10 @@ plugin.sequence.java.util.Collection = \ java.util.LinkedList, \ java.util.Stack, \ java.util.TreeSet + + +# Enable IRUS tracker for IRUS tests +stats.tracker.enabled = true + +dspace.hostname = localhost +dspace.url = http://localhost \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java index 15283db011..b5b844f359 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITExportUsageEventListener.java @@ -10,7 +10,7 @@ package org.dspace.statistics.export; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,6 +45,7 @@ import org.dspace.core.Context; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; import org.dspace.statistics.export.service.FailedOpenURLTrackerService; @@ -52,17 +53,21 @@ import org.dspace.usage.UsageEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; /** * Test class for the ExportUsageEventListener */ +@RunWith(MockitoJUnitRunner.class) public class ITExportUsageEventListener extends AbstractIntegrationTest { private static Logger log = Logger.getLogger(ITExportUsageEventListener.class); protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); @@ -79,8 +84,8 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { .getServiceByName("testProcessedUrls", ArrayList.class); - @Mock - ExportUsageEventListener exportUsageEventListener = mock(ExportUsageEventListener.class, CALLS_REAL_METHODS); + @Spy + ExportUsageEventListener exportUsageEventListener; private Item item; private Bitstream bitstream; @@ -97,6 +102,8 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { super.init(); context.turnOffAuthorisationSystem(); try { + exportUsageEventListener.configurationService = configurationService; + entityType = entityTypeService.create(context, "Publication"); community = communityService.create(null, context); collection = collectionService.create(context, community); @@ -145,7 +152,11 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { } catch (Exception e) { log.error(e.getMessage(), e); } finally { - context.restoreAuthSystemState(); + try { + context.complete(); + } catch (SQLException e) { + log.error(e); + } } super.destroy(); } @@ -164,6 +175,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new Context()); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); @@ -173,7 +185,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + - "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fhandle%2F" + URLEncoder + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%2Fhandle%2F" + URLEncoder .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); @@ -199,14 +211,16 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new Context()); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); + List all = failedOpenURLTrackerService.findAll(context); String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + - "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fhandle%2F" + URLEncoder + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%2Fhandle%2F" + URLEncoder .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; boolean isMatch = matchesString(all.get(0).getUrl(), regex); @@ -225,8 +239,6 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { context.turnOffAuthorisationSystem(); HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRemoteAddr()).thenReturn("client-ip-fail"); - when(request.getHeader(anyString())).thenReturn(null); UsageEvent usageEvent = mock(UsageEvent.class); when(usageEvent.getObject()).thenReturn(item); @@ -239,6 +251,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { context.restoreAuthSystemState(); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); List all = failedOpenURLTrackerService.findAll(context); @@ -262,12 +275,13 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new Context()); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + - "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fbitstream%2Fhandle%2F" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%2Fbitstream%2Fhandle%2F" + URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); @@ -294,6 +308,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new Context()); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); List all = failedOpenURLTrackerService.findAll(context); @@ -301,7 +316,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { String regex = "https://irus.jisc.ac.uk/counter/test/\\?url_ver=Z39.88-2004&req_id=" + URLEncoder.encode(request.getRemoteAddr(), "UTF-8") + "&req_dat=&rft" + ".artnum=oai%3Alocalhost%3A" + URLEncoder.encode(item.getHandle(), "UTF-8") + "&rfr_dat=&rfr_id" + - "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%3A3000%2Fbitstream%2Fhandle%2F" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=http%3A%2F%2Flocalhost%2Fbitstream%2Fhandle%2F" + URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; @@ -334,6 +349,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { context.restoreAuthSystemState(); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); List all = failedOpenURLTrackerService.findAll(context); @@ -351,14 +367,12 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { public void testReceiveEventOnNonRelevantObject() throws SQLException { HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRemoteAddr()).thenReturn("client-ip-fail"); - when(request.getHeader(anyString())).thenReturn(null); UsageEvent usageEvent = mock(UsageEvent.class); when(usageEvent.getObject()).thenReturn(community); - when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new Context()); + doCallRealMethod().when(exportUsageEventListener).receiveEvent(usageEvent); exportUsageEventListener.receiveEvent(usageEvent); List all = failedOpenURLTrackerService.findAll(context); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java index 1f21ed389e..3883b12495 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java @@ -10,6 +10,7 @@ package org.dspace.statistics.export; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -24,13 +25,10 @@ import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; import org.dspace.statistics.export.service.FailedOpenURLTrackerService; import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; /** * Class to test the RetryFailedOpenUrlTracker */ -@RunWith(MockitoJUnitRunner.class) public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { private static Logger log = Logger.getLogger(ITRetryFailedOpenUrlTracker.class); @@ -40,7 +38,8 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlTrackerLoggerService(); protected ArrayList testProcessedUrls = DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName("testProcessedUrls", ArrayList.class); + .getServiceByName("testProcessedUrls", + ArrayList.class); private ScriptService scriptService = ScriptServiceFactory.getInstance().getScriptService(); @@ -65,13 +64,18 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { } catch (Exception e) { log.error(e.getMessage(), e); } finally { - context.restoreAuthSystemState(); + try { + context.complete(); + } catch (SQLException e) { + log.error(e); + } } super.destroy(); } /** * Test the mode of the script that allows the user to add a failed url to the database + * * @throws Exception */ @Test @@ -94,6 +98,7 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { /** * Test to check that all logged failed urls are reprocessed succesfully and removed from the db + * * @throws Exception */ @Test @@ -126,6 +131,7 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { /** * Test to check that the successful retries are removed, but the failed retries remain in the db + * * @throws Exception */ @Test diff --git a/dspace/config/modules/stats.cfg b/dspace/config/modules/stats.cfg index d4977584bc..c53d461c6c 100644 --- a/dspace/config/modules/stats.cfg +++ b/dspace/config/modules/stats.cfg @@ -1,3 +1,6 @@ +# Enable the IRUS tracker. By default or when omitted, the tracker will be disabled +stats.tracker.enabled = false + # OPTIONAL metadata field used for filtering. # If items with specific values for the "dc.type" field should be excluded, "dc.type" should be placed here. # This should comply to the syntax schema.element.qualified or schema.element if the qualifier is null. diff --git a/dspace/config/spring/rest/event-service-listeners.xml b/dspace/config/spring/rest/event-service-listeners.xml index c93d53d878..7c22fc1d72 100644 --- a/dspace/config/spring/rest/event-service-listeners.xml +++ b/dspace/config/spring/rest/event-service-listeners.xml @@ -22,8 +22,8 @@ - - - + + + \ No newline at end of file From ff63e6b464e74349f3f5849fc179e140dbf65cc4 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 13 Mar 2020 14:38:43 +0100 Subject: [PATCH 048/749] [Task 69684] added javadoc and fixed that dc.title can't be set for AdminGroup --- .../CommunityAdminGroupRestController.java | 25 +++++++++++++++ .../CommunityAdminGroupLinkRepository.java | 12 +++++++ .../repository/CommunityRestRepository.java | 32 ++++++++++++++++++- .../CommunityAdminGroupRestControllerIT.java | 23 +++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java index 3146f15302..568da95f11 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java @@ -41,6 +41,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +/** + * This RestController will take care of all the calls for a specific community's admingroup + * This is handled by calling "/api/core/communities/{uuid}/adminGroup" with the correct RequestMethod + */ @RestController @RequestMapping("/api/core/communities" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/adminGroup") public class CommunityAdminGroupRestController { @@ -58,6 +62,16 @@ public class CommunityAdminGroupRestController { @Autowired private AuthorizeService authorizeService; + /** + * This method creates and returns an AdminGroup object for the given community + * This is called by using RequestMethod.POST on the default url for this class + * @param uuid The UUID of the community for which we'll create an adminGroup + * @param response The current response + * @param request The current request + * @return The created AdminGroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasPermission(#uuid, 'COMMUNITY', 'WRITE')") public ResponseEntity postAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, @@ -85,6 +99,17 @@ public class CommunityAdminGroupRestController { return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); } + /** + * This method takes care of the deletion of an AdminGroup for the given community + * This is called by using RequestMethod.DELETE on the default url for this class + * @param uuid The UUID of the community for which we'll delete the AdminGroup + * @param response The current response + * @param request The current request + * @return An empty response if the deletion was successful + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ @RequestMapping(method = RequestMethod.DELETE) @PreAuthorize("hasPermission(#uuid, 'COMMUNITY', 'WRITE')") public ResponseEntity deleteAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java index 68172ce177..95dd44d7a0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java @@ -28,6 +28,10 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +/** + * Link repository for "admingroup" subresource of an individual community. + * + */ @Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.ADMIN_GROUP) public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -37,6 +41,14 @@ public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestReposit @Autowired private AuthorizeService authorizeService; + /** + * This method is responsible for retrieving the AdminGroup of a Community + * @param request The current request + * @param communityId The id of the community that we'll retrieve the admingroup for + * @param optionalPageable The pageable if applicable + * @param projection The current Projection + * @return The admingroup of the given community + */ @PreAuthorize("hasPermission(#communityId, 'COMMUNITY', 'READ')") public GroupRest getAdminGroup(@Nullable HttpServletRequest request, UUID communityId, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index 53a93ab6c7..2b1b9b3de1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.SortedMap; import java.util.UUID; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; @@ -26,6 +27,8 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CommunityRest; 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.model.patch.Patch; import org.dspace.app.rest.utils.CommunityRestEqualityUtils; import org.dspace.authorize.AuthorizeException; @@ -276,6 +279,15 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository> map = metadata.getMap(); + if (map != null) { + List dcTitleMetadata = map.get("dc.title"); + if (dcTitleMetadata != null) { + if (!dcTitleMetadata.isEmpty()) { + throw new UnprocessableEntityException("The given GroupRest can't contain a dc.title mdv"); + } + } + } + metadataConverter.setMetadata(context, group, metadata); } catch (IOException e1) { throw new UnprocessableEntityException("Error parsing request body.", e1); } return converter.toRest(group, utils.obtainProjection()); } + /** + * This method will delete the AdminGroup for the given Community + * @param context The current context + * @param community The community for which we'll delete the admingroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ public void deleteAdminGroup(Context context, Community community) throws SQLException, AuthorizeException, IOException { Group adminGroup = community.getAdministrators(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java index fda6f47f8f..d6fdbb169f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java @@ -138,6 +138,29 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg } + @Test + public void postCommunityAdminGroupCreateAdminGroupDcTitleUnprocessable() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + metadataRest.put("dc.title", new MetadataValueRest("testTitle")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test public void postCommunityAdminGroupCreateAdminGroupSuccessCommunityAdmin() throws Exception { From de79405b743f7029a3ea06e22cabdb05475daa27 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Fri, 13 Mar 2020 15:38:49 +0100 Subject: [PATCH 049/749] DELETE /api/eperson/groups/<:uuid> --- .../rest/repository/GroupRestRepository.java | 36 ++++- .../app/rest/GroupRestRepositoryIT.java | 140 ++++++++++++++++++ 2 files changed, 168 insertions(+), 8 deletions(-) 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 c5d9164c4d..6e4f827dc7 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,14 +7,6 @@ */ 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; -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; @@ -30,9 +22,18 @@ import org.dspace.eperson.service.GroupService; 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; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import static org.apache.commons.lang3.StringUtils.isBlank; + /** * This is the repository responsible to manage Group Rest object * @@ -149,4 +150,23 @@ public class GroupRestRepository extends DSpaceObjectRestRepository getDomainClass() { return GroupRest.class; } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void delete(Context context, UUID uuid) throws AuthorizeException { + Group group = null; + try { + group = gs.find(context, uuid); + if (group == null) { + throw new ResourceNotFoundException( + GroupRest.CATEGORY + "." + GroupRest.NAME + + " with id: " + uuid + " not found" + ); + } + gs.delete(context, group); + } catch (SQLException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } 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 0b8b12f7fc..be9a346cc7 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 @@ -1509,4 +1509,144 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { } } } + + @Test + public void deleteGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + get("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isOk()); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isNoContent()); + + getClient(authToken).perform( + get("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isNotFound()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + } + } + + @Test + public void deleteGroupUnauthorizedTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + get("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isOk()); + + getClient().perform( + delete("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isUnauthorized()); + + getClient(authToken).perform( + get("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isOk()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + } + } + + @Test + public void deleteGroupForbiddenTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + + try { + context.turnOffAuthorisationSystem(); + + parentGroup = groupService.create(context); + + context.commit(); + + parentGroup = context.reloadEntity(parentGroup); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(adminToken).perform( + get("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isOk()); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isForbidden()); + + getClient(adminToken).perform( + get("/api/eperson/groups/" + parentGroup.getID()) + ).andExpect(status().isOk()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + } + } + + @Test + public void deleteGroupNotFoundTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Group parentGroup = null; + + try { + context.turnOffAuthorisationSystem(); + context.commit(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform( + delete("/api/eperson/groups/" + UUID.randomUUID()) + ).andExpect(status().isNotFound()); + + } finally { + if (parentGroup != null) { + GroupBuilder.deleteGroup(parentGroup.getID()); + } + } + } } From 65a1a2d15e7c72f9aaa22bbd1b09199ba339a50f Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Mon, 16 Mar 2020 10:55:27 +0100 Subject: [PATCH 050/749] GET /api/eperson/groups/<:uuid>/object --- .../dspace/app/rest/GroupRestController.java | 12 ++ .../rest/repository/GroupRestRepository.java | 52 ++++++++ .../app/rest/GroupRestRepositoryIT.java | 116 ++++++++++++++++-- .../app/rest/builder/CollectionBuilder.java | 23 ++++ 4 files changed, 191 insertions(+), 12 deletions(-) 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 index db545736f1..5555a730cc 100644 --- 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 @@ -11,6 +11,7 @@ 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_REQUESTMAPPING_IDENTIFIER_AS_UUID; 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; @@ -30,7 +31,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.repository.GroupRestRepository; import org.dspace.app.rest.utils.GroupUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; @@ -49,6 +52,7 @@ 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.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** @@ -76,6 +80,9 @@ public class GroupRestController { @Autowired GroupUtil groupUtil; + @Autowired + GroupRestRepository repository; + /** * 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. @@ -308,4 +315,9 @@ public class GroupRestController { throw new AuthorizeException("not authorized to manage this group"); } + + @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/object") + public DSpaceObjectRest object(@PathVariable UUID uuid) { + return repository.object(uuid); + } } 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 6e4f827dc7..8292234af9 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 @@ -10,12 +10,19 @@ package org.dspace.app.rest.repository; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.ConverterService; 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.DSpaceObjectRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; import org.dspace.core.Context; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; @@ -45,6 +52,18 @@ public class GroupRestRepository extends DSpaceObjectRestRepository { indexingService.commit(); } + /** + * Delete the Test Collection referred to by the given UUID + * + * @param uuid UUID of Test Collection to delete + * @throws SQLException + * @throws IOException + */ + public static void deleteCollection(UUID uuid) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Collection collection = collectionService.find(c, uuid); + if (collection != null) { + try { + collectionService.delete(c, collection); + } catch (AuthorizeException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + c.complete(); + } + } + @Override protected DSpaceObjectService getService() { return collectionService; From 2f741081d080b5556b8f66428f487866de8b6059 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 17 Mar 2020 16:28:30 +0100 Subject: [PATCH 051/749] [Task 69689] added various endpoints for Group management --- .../org/dspace/workflow/WorkflowService.java | 15 + .../BasicWorkflowServiceImpl.java | 7 + .../xmlworkflow/XmlWorkflowServiceImpl.java | 37 +- .../storedcomponents/PoolTaskServiceImpl.java | 5 + .../service/PoolTaskService.java | 10 + .../rest/CollectionGroupRestController.java | 471 ++++++++++++++++++ .../dspace/app/rest/model/CollectionRest.java | 23 +- .../CollectionAdminGroupLinkRepository.java | 83 +++ ...ctionBitstreamReadGroupLinkRepository.java | 88 ++++ ...CollectionItemReadGroupLinkRepository.java | 89 ++++ .../repository/CollectionRestRepository.java | 294 +++++++++++ ...ollectionSubmitterGroupLinkRepository.java | 83 +++ .../rest/CollectionGroupRestControllerIT.java | 415 +++++++++++++++ .../app/rest/builder/CollectionBuilder.java | 6 + .../app/rest/matcher/CollectionMatcher.java | 4 +- 15 files changed, 1612 insertions(+), 18 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java index 53da1660db..969fc80f13 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java @@ -17,6 +17,7 @@ import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.WorkflowConfigurationException; /** @@ -94,5 +95,19 @@ public interface WorkflowService { public Group getWorkflowRoleGroup(Context context, Collection collection, String roleName, Group roleGroup) throws SQLException, IOException, WorkflowConfigurationException, AuthorizeException, WorkflowException; + /** + * This method will create the workflowRoleGroup for a collection and the given rolename + * @param context The relevant DSpace context + * @param collection The collection + * @param roleName The rolename + * @return The created Group + * @throws AuthorizeException If something goes wrong + * @throws SQLException If something goes wrong + * @throws IOException If something goes wrong + * @throws WorkflowConfigurationException If something goes wrong + */ + public Group createWorkflowRoleGroup(Context context, Collection collection, String roleName) + throws AuthorizeException, SQLException, IOException, WorkflowConfigurationException; + public List getFlywayMigrationLocations(); } diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java index 58f393804a..7d271bf4a4 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java @@ -53,6 +53,7 @@ import org.dspace.usage.UsageWorkflowEvent; import org.dspace.workflowbasic.service.BasicWorkflowItemService; import org.dspace.workflowbasic.service.BasicWorkflowService; import org.dspace.workflowbasic.service.TaskListItemService; +import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.springframework.beans.factory.annotation.Autowired; public class BasicWorkflowServiceImpl implements BasicWorkflowService { @@ -1216,6 +1217,12 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { return roleGroup; } + @Override + public Group createWorkflowRoleGroup(Context context, Collection collection, String roleName) + throws AuthorizeException, SQLException, IOException, WorkflowConfigurationException { + return getWorkflowRoleGroup(context, collection, roleName, null); + } + @Override public List getFlywayMigrationLocations() { return Collections.emptyList(); 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..986c59c8df 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -158,22 +158,6 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { Role role = WorkflowUtils.getCollectionAndRepositoryRoles(collection).get(roleName); if (role.getScope() == Role.Scope.COLLECTION || role.getScope() == Role.Scope.REPOSITORY) { roleGroup = WorkflowUtils.getRoleGroup(context, collection, role); - if (roleGroup == null) { - authorizeService.authorizeAction(context, collection, Constants.WRITE); - roleGroup = groupService.create(context); - if (role.getScope() == Role.Scope.COLLECTION) { - groupService.setName(roleGroup, - "COLLECTION_" + collection.getID().toString() - + "_WORKFLOW_ROLE_" + roleName); - } else { - groupService.setName(roleGroup, role.getName()); - } - groupService.update(context, roleGroup); - authorizeService.addPolicy(context, collection, Constants.ADD, roleGroup); - if (role.getScope() == Role.Scope.COLLECTION) { - WorkflowUtils.createCollectionWorkflowRole(context, collection, roleName, roleGroup); - } - } } return roleGroup; } catch (WorkflowConfigurationException e) { @@ -181,6 +165,27 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } } + public Group createWorkflowRoleGroup(Context context, Collection collection, String roleName) + throws AuthorizeException, SQLException, IOException, WorkflowConfigurationException { + Group roleGroup; + authorizeService.authorizeAction(context, collection, Constants.WRITE); + roleGroup = groupService.create(context); + Role role = WorkflowUtils.getCollectionAndRepositoryRoles(collection).get(roleName); + if (role.getScope() == Role.Scope.COLLECTION) { + groupService.setName(roleGroup, + "COLLECTION_" + collection.getID().toString() + + "_WORKFLOW_ROLE_" + roleName); + } else { + groupService.setName(roleGroup, role.getName()); + } + groupService.update(context, roleGroup); + authorizeService.addPolicy(context, collection, Constants.ADD, roleGroup); + if (role.getScope() == Role.Scope.COLLECTION) { + WorkflowUtils.createCollectionWorkflowRole(context, collection, roleName, roleGroup); + } + return roleGroup; + } + @Override public List getFlywayMigrationLocations() { return Collections.singletonList("classpath:org.dspace.storage.rdbms.xmlworkflow"); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java index 684eb2cd04..25b4c79206 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java @@ -130,6 +130,11 @@ public class PoolTaskServiceImpl implements PoolTaskService { return poolTaskDAO.findByEPerson(context, ePerson); } + @Override + public List findByGroup(Context context, Group group) throws SQLException { + return poolTaskDAO.findByGroup(context, group); + } + @Override public PoolTask create(Context context) throws SQLException, AuthorizeException { return poolTaskDAO.create(context, new PoolTask()); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java index d0de6bef38..3dadcc7804 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java @@ -14,6 +14,7 @@ import java.util.List; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.dspace.service.DSpaceCRUDService; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -41,4 +42,13 @@ public interface PoolTaskService extends DSpaceCRUDService { throws SQLException, AuthorizeException; public List findByEPerson(Context context, EPerson ePerson) throws SQLException; + + /** + * This method will return a list of PoolTask for the given group + * @param context The relevant DSpace context + * @param group The Group to be searched on + * @return The list of PoolTask objects + * @throws SQLException If something goes wrong + */ + public List findByGroup(Context context, Group group) throws SQLException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java new file mode 100644 index 0000000000..ad656fbe6e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java @@ -0,0 +1,471 @@ +/** + * 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.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.model.hateoas.GroupResource; +import org.dspace.app.rest.repository.CollectionRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.workflow.WorkflowService; +import org.dspace.xmlworkflow.WorkflowUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +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.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * This RestController will take care of all the calls for a specific collection's admingroup + * This is handled by calling "/api/core/collections/{uuid}/adminGroup" with the correct RequestMethod + */ +@RestController +@RequestMapping("/api/core/collections" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) +public class CollectionGroupRestController { + + @Autowired + private CollectionService collectionService; + + @Autowired + private CollectionRestRepository collectionRestRepository; + + @Autowired + private ConverterService converterService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private WorkflowService workflowService; + + /** + * This method creates and returns an AdminGroup object for the given collection + * This is called by using RequestMethod.POST on the /adminGroup value + * @param uuid The UUID of the collection for which we'll create an adminGroup + * @param response The current response + * @param request The current request + * @return The created AdminGroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST, value = "/adminGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity postAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to edit the AdminGroup for" + + " collection: " + uuid); + } + if (collection.getAdministrators() != null) { + throw new UnprocessableEntityException("The collection with UUID: " + uuid + " already has " + + "an admin group"); + } + GroupRest adminGroup = collectionRestRepository.createAdminGroup(context, request, collection); + context.complete(); + GroupResource groupResource = converterService.toResource(adminGroup); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); + } + + /** + * This method takes care of the deletion of an AdminGroup for the given collection + * This is called by using RequestMethod.DELETE on the /adminGroup value + * @param uuid The UUID of the collection for which we'll delete the AdminGroup + * @param response The current response + * @param request The current request + * @return An empty response if the deletion was successful + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/adminGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity deleteAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + + " collection: " + uuid); + } + if (collection.getAdministrators() == null) { + throw new UnprocessableEntityException("The collection with UUID: " + uuid + " doesn't have an admin " + + "group"); + } + collectionRestRepository.deleteAdminGroup(context, collection); + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + + /** + * This method creates and returns a SubmitterGroup object for the given collection + * This is called by using RequestMethod.POST on the /submittersGroup + * @param uuid The UUID of the collection for which we'll create a submitterGroup + * @param response The current response + * @param request The current request + * @return The created SubmitterGroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST, value = "/submittersGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity postSubmittersGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to edit the SubmitterGroup for" + + " collection: " + uuid); + } + if (collection.getSubmitters() != null) { + throw new UnprocessableEntityException("The collection with UUID: " + uuid + " already has " + + "a submitter group"); + } + GroupRest submitterGroup = collectionRestRepository.createSubmitterGroup(context, request, collection); + context.complete(); + GroupResource groupResource = converterService.toResource(submitterGroup); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); + } + + /** + * This method takes care of the deletion of a SubmitterGroup for the given collection + * This is called by using RequestMethod.DELETE on the default url for this class + * @param uuid The UUID of the collection for which we'll delete the SubmittersGroup + * @param response The current response + * @param request The current request + * @return An empty response if the deletion was successful + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/submittersGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity deleteSubmittersGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the SubmitterGroup for" + + " collection: " + uuid); + } + if (collection.getSubmitters() == null) { + throw new UnprocessableEntityException("The collection with UUID: " + uuid + " doesn't have a submitter " + + "group"); + } + collectionRestRepository.deleteSubmitterGroup(context, collection); + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + + /** + * This method creates and returns a ItemReadGroup object for the given collection + * This is called by using RequestMethod.POST on the /itemReadGroup value + * @param uuid The UUID of the collection for which we'll create a ItemReadGroup + * @param response The current response + * @param request The current request + * @return The created ItemReadGroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST, value = "/itemReadGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity postItemReadGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to edit the ItemReadGroup for" + + " collection: " + uuid); + } + List itemGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_ITEM_READ); + if (itemGroups != null && !itemGroups.isEmpty()) { + Group itemReadGroup = itemGroups.get(0); + if (itemReadGroup != null && !StringUtils.equalsIgnoreCase(itemReadGroup.getName(),Group.ANONYMOUS)) { + throw new UnprocessableEntityException("Unable to create a new default read group because either the group already exists or multiple groups are assigned the default privileges."); + } + } + + GroupRest itemReadGroup = collectionRestRepository.createItemReadGroup(context, request, collection); + context.complete(); + GroupResource groupResource = converterService.toResource(itemReadGroup); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); + } + + /** + * This method takes care of the deletion of an ItemReadGroup for the given collection + * This is called by using RequestMethod.DELETE on the /itemReadGroup value + * @param uuid The UUID of the collection for which we'll delete the ItemReadGroup + * @param response The current response + * @param request The current request + * @return An empty response if the deletion was successful + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/itemReadGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity deleteItemReadGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the ItemReadGroup for" + + " collection: " + uuid); + } + + List itemGroups = authorizeService.getAuthorizedGroups(context, collection, Constants.DEFAULT_ITEM_READ); + if (itemGroups != null && !itemGroups.isEmpty()) { + Group itemReadGroup = itemGroups.get(0); + if (itemReadGroup == null || StringUtils.equalsIgnoreCase(itemReadGroup.getName(), Group.ANONYMOUS)) { + throw new UnprocessableEntityException("Unable to delete the default read group because it's the default"); + } + } else { + throw new UnprocessableEntityException("The collection with UUID: " + uuid + " doesn't have " + + "an ItemReadGroup group"); + + } + collectionRestRepository.deleteItemReadGroup(context, collection); + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + + /** + * This method creates and returns a BitstreamReadGroup object for the given collection + * This is called by using RequestMethod.POST on the /bitstreamReadGroup value + * @param uuid The UUID of the collection for which we'll create a BitstreamReadGroup + * @param response The current response + * @param request The current request + * @return The created BitstreamReadGroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST, value = "/bitstreamReadGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity postBitstreamReadGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to edit the ItemReadGroup for" + + " collection: " + uuid); + } + + List bitstreamGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); + if (bitstreamGroups != null && !bitstreamGroups.isEmpty()) { + Group bitstreamGroup = bitstreamGroups.get(0); + if (bitstreamGroup != null && !StringUtils.equalsIgnoreCase(bitstreamGroup.getName(),Group.ANONYMOUS)) { + throw new UnprocessableEntityException("Unable to create a new default read group because either the group already exists or multiple groups are assigned the default privileges."); + } + } + + + GroupRest bitstreamReadGroup = collectionRestRepository.createBitstreamReadGroup(context, request, collection); + context.complete(); + GroupResource groupResource = converterService.toResource(bitstreamReadGroup); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); + } + + /** + * This method takes care of the deletion of an BitstreamReadGroup for the given collection + * This is called by using RequestMethod.DELETE on the /bitstreamReadGroup value + * @param uuid The UUID of the collection for which we'll delete the bitstreamReadGroup + * @param response The current response + * @param request The current request + * @return An empty response if the deletion was successful + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/bitstreamReadGroup") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") + public ResponseEntity deleteBitstreamReadGroup(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the BitstreamReadGroup for" + + " collection: " + uuid); + } + + List bitstreamGroups = authorizeService.getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); + if (bitstreamGroups != null && !bitstreamGroups.isEmpty()) { + Group bitstreamReadGroup = bitstreamGroups.get(0); + if (bitstreamReadGroup == null || StringUtils.equalsIgnoreCase(bitstreamReadGroup.getName(), Group.ANONYMOUS)) { + throw new UnprocessableEntityException("Unable to delete the default read group because it's the default"); + } + } else { + throw new UnprocessableEntityException("The collection with UUID: " + uuid + " doesn't have " + + "an BitstreamReadGroup group"); + + } + collectionRestRepository.deleteBitstreamReadGroup(context, collection); + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + + /** + * This method will retrieve the workflowGroup for a given Collection and workflowRole + * @param uuid The UUID of the collection to retrieve + * @param response The current response + * @param request The current request + * @param workflowRole The given workflowRole + * @return The workflowGroup for the given collection and workflowrole + * @throws Exception If something goes wrong + */ + @RequestMapping(method = RequestMethod.GET, value = "/workflowGroups/{workflowRole}") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") + public GroupResource getWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request, @PathVariable String workflowRole) throws Exception { + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + GroupRest groupRest = collectionRestRepository.getWorkflowGroupForRole(context, collection, workflowRole); + return converterService.toResource(groupRest); + } + + /** + * This method will create the workflowGroup for a given Collection and workflowRole + * @param uuid The UUID of the collection to retrieve + * @param response The current response + * @param request The current request + * @param workflowRole The given workflowRole + * @return The workflowGroup for the given collection and workflowrole + * @throws Exception If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST, value = "/workflowGroups/{workflowRole}") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") + public GroupResource postWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request, @PathVariable String workflowRole) throws Exception { + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + Group group = workflowService.getWorkflowRoleGroup(context, collection, workflowRole, null); + if (group != null) { + throw new UnprocessableEntityException("WorkflowGroup already exists for the role: " + workflowRole + + " in collection with UUID: " + collection.getID()); + } + GroupRest groupRest = collectionRestRepository.createWorkflowGroupForRole(context, request, collection, workflowRole); + context.complete(); + return converterService.toResource(groupRest); + } + + /** + * This method will delete the workflowGroup for a given Collection and workflowRole + * @param uuid The UUID of the collection to retrieve + * @param response The current response + * @param request The current request + * @param workflowRole The given workflowRole + * @return + * @throws Exception If something goes wrong + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/workflowGroups/{workflowRole}") + @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") + public ResponseEntity deleteWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request, @PathVariable String workflowRole) throws Exception { + Context context = ContextUtil.obtainContext(request); + Collection collection = collectionService.find(context, uuid); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + uuid); + } + + collectionRestRepository.deleteWorkflowGroupForRole(context, request, collection, workflowRole); + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } +} 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 ac248f435b..1de4ec632c 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 @@ -30,7 +30,23 @@ import com.fasterxml.jackson.annotation.JsonProperty; @LinkRest( name = CollectionRest.PARENT_COMMUNITY, method = "getParentCommunity" - ) + ), + @LinkRest( + name = CollectionRest.ADMIN_GROUP, + method = "getAdminGroup" + ), + @LinkRest( + name = CollectionRest.SUBMITTERS_GROUP, + method = "getSubmittersGroup" + ), + @LinkRest( + name = CollectionRest.ITEM_READ_GROUP, + method = "getItemReadGroup" + ), + @LinkRest( + name = CollectionRest.BITSTREAM_READ_GROUP, + method = "getBitstreamReadGroup" + ), }) public class CollectionRest extends DSpaceObjectRest { public static final String NAME = "collection"; @@ -42,6 +58,11 @@ public class CollectionRest extends DSpaceObjectRest { public static final String LOGO = "logo"; public static final String MAPPED_ITEMS = "mappedItems"; public static final String PARENT_COMMUNITY = "parentCommunity"; + public static final String ADMIN_GROUP = "adminGroup"; + public static final String SUBMITTERS_GROUP = "submittersGroup"; + public static final String ITEM_READ_GROUP = "itemReadGroup"; + public static final String BITSTREAM_READ_GROUP = "bitstreamReadGroup"; + @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java new file mode 100644 index 0000000000..4b5dc3b70e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java @@ -0,0 +1,83 @@ +/** + * 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.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "admingroup" subresource of an individual collection. + * + */ +@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.ADMIN_GROUP) +public class CollectionAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private CollectionService collectionService; + + @Autowired + private AuthorizeService authorizeService; + + /** + * This method is responsible for retrieving the AdminGroup of a Collection + * @param request The current request + * @param collectionId The id of the collection that we'll retrieve the admingroup for + * @param optionalPageable The pageable if applicable + * @param projection The current Projection + * @return The admingroup of the given collection + */ + @PreAuthorize("hasPermission(#collectionId, 'COLLECTION', 'READ')") + public GroupRest getAdminGroup(@Nullable HttpServletRequest request, + UUID collectionId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Collection collection = collectionService.find(context, collectionId); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + collectionId); + } + + Group administrators = collection.getAdministrators(); + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the AdminGroup for" + + " collection: " + collectionId); + } + if (administrators == null) { + return null; + } + if (!authorizeService.authorizeActionBoolean(context, administrators, Constants.READ)) { + throw new AccessDeniedException("The current user doesn't have sufficient rights to access the admin" + + "group of collection with id: " + collectionId); + } + return converter.toRest(administrators, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java new file mode 100644 index 0000000000..0fe12ba983 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "BitstreamReadGroup" subresource of an individual collection. + * + */ +@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.BITSTREAM_READ_GROUP) +public class CollectionBitstreamReadGroupLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private CollectionService collectionService; + + @Autowired + private AuthorizeService authorizeService; + + /** + * This method is responsible for retrieving the BitstreamReadGroup of a Collection + * @param request The current request + * @param collectionId The id of the collection that we'll retrieve the BitstreamReadGroup for + * @param optionalPageable The pageable if applicable + * @param projection The current Projection + * @return The BitstreamReadGroup of the given collection + */ + @PreAuthorize("hasPermission(#collectionId, 'COLLECTION', 'READ')") + public GroupRest getBitstreamReadGroup(@Nullable HttpServletRequest request, + UUID collectionId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Collection collection = collectionService.find(context, collectionId); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + collectionId); + } + List bitstreamGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); + Group bitstreamReadGroup = bitstreamGroups.get(0); + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException( + "The current user was not allowed to retrieve the bitstreamReadGroup for" + + " collection: " + collectionId); + } + if (bitstreamReadGroup == null) { + return null; + } + if (!authorizeService.authorizeActionBoolean(context, bitstreamReadGroup, Constants.READ)) { + throw new AccessDeniedException( + "The current user doesn't have sufficient rights to access the bitstreamReadGroup" + + "group of collection with id: " + collectionId); + } + return converter.toRest(bitstreamReadGroup, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java new file mode 100644 index 0000000000..ee17ddae59 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java @@ -0,0 +1,89 @@ +/** + * 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 javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "ItemReadGroup" subresource of an individual collection. + * + */ +@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.ITEM_READ_GROUP) +public class CollectionItemReadGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private CollectionService collectionService; + + @Autowired + private AuthorizeService authorizeService; + + /** + * This method is responsible for retrieving the ItemReadGroup of a Collection + * @param request The current request + * @param collectionId The id of the collection that we'll retrieve the ItemReadGroup for + * @param optionalPageable The pageable if applicable + * @param projection The current Projection + * @return The ItemReadGroup of the given collection + */ + @PreAuthorize("hasPermission(#collectionId, 'COLLECTION', 'READ')") + public GroupRest getItemReadGroup(@Nullable HttpServletRequest request, + UUID collectionId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Collection collection = collectionService.find(context, collectionId); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + collectionId); + } + List itemGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_ITEM_READ); + if (itemGroups == null || itemGroups.isEmpty()) { + return null; + } + Group itemReadGroup = itemGroups.get(0); + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the itemReadGroup for" + + " collection: " + collectionId); + } + if (itemReadGroup == null) { + return null; + } + if (!authorizeService.authorizeActionBoolean(context, itemReadGroup, Constants.READ)) { + throw new AccessDeniedException( + "The current user doesn't have sufficient rights to access the itemReadGroup" + + "group of collection with id: " + collectionId); + } + return converter.toRest(itemReadGroup, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 4ff8e6b174..b5001098a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -10,12 +10,15 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.SortedMap; import java.util.UUID; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -24,11 +27,15 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; +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.model.TemplateItemRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.model.wrapper.TemplateItem; import org.dspace.app.rest.utils.CollectionRestEqualityUtils; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -39,6 +46,16 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.RequestService; +import org.dspace.workflow.WorkflowException; +import org.dspace.workflow.WorkflowService; +import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.WorkflowUtils; +import org.dspace.xmlworkflow.storedcomponents.CollectionRole; +import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; +import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -56,6 +73,8 @@ import org.springframework.web.multipart.MultipartFile; @Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME) public class CollectionRestRepository extends DSpaceObjectRestRepository { + public static Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionRestRepository.class); + @Autowired CommunityService communityService; @@ -71,6 +90,21 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository itemGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_ITEM_READ); + Group itemReadGroup = itemGroups.get(0); + groupService.delete(context, itemReadGroup); + authorizeService.addPolicy(context, collection, Constants.DEFAULT_ITEM_READ, + groupService.findByName(context, Group.ANONYMOUS)); + } + + /** + * This method will create an BitstreamReadGroup for the given Collection with the given Information through JSON + * @param context The current context + * @param request The current request + * @param collection The collection for which we'll create an BitstreamReadGroup + * @return The created BitstreamReadGroup's REST object + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + public GroupRest createBitstreamReadGroup(Context context, HttpServletRequest request, Collection collection) + throws SQLException, AuthorizeException { + + Group role = groupService.create(context); + groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_BITSTREAM_DEFAULT_READ"); + + // Remove existing privileges from the anonymous group. + authorizeService.removePoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); + + // Grant our new role the default privileges. + authorizeService.addPolicy(context, collection, Constants.DEFAULT_BITSTREAM_READ, role); + + // Commit the changes + groupService.update(context, role); + return populateGroupInformation(context, request, role); + } + + /** + * This method will delete the BitstreamReadGroup for the given Collection + * @param context The current context + * @param collection The community for which we'll delete the BitstreamReadGroup + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws IOException If something goes wrong + */ + public void deleteBitstreamReadGroup(Context context, Collection collection) + throws SQLException, AuthorizeException, IOException { + List itemGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); + Group itemReadGroup = itemGroups.get(0); + groupService.delete(context, itemReadGroup); + authorizeService.addPolicy(context, collection, Constants.DEFAULT_BITSTREAM_READ, + groupService.findByName(context, Group.ANONYMOUS)); + } + + + + private GroupRest populateGroupInformation(Context context, HttpServletRequest request, Group group) + throws SQLException, AuthorizeException { + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + try { + ServletInputStream input = request.getInputStream(); + groupRest = mapper.readValue(input, GroupRest.class); + if (groupRest.isPermanent() || StringUtils.isNotBlank(groupRest.getName())) { + throw new UnprocessableEntityException("The given GroupRest object has to be non-permanent and can't" + + " contain a name"); + } + MetadataRest metadata = groupRest.getMetadata(); + SortedMap> map = metadata.getMap(); + if (map != null) { + List dcTitleMetadata = map.get("dc.title"); + if (dcTitleMetadata != null) { + if (!dcTitleMetadata.isEmpty()) { + throw new UnprocessableEntityException("The given GroupRest can't contain a dc.title mdv"); + } + } + } + metadataConverter.setMetadata(context, group, metadata); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body.", e1); + } + return converter.toRest(group, utils.obtainProjection()); + } + + /** + * This method will retrieve the GroupRest object for the workflowGroup for the given Collection and workflowRole + * @param context The relevant DSpace context + * @param collection The given collection + * @param workflowRole The given workflowRole + * @return The GroupRest for the WorkflowGroup for the given Collection and workflowRole + * @throws SQLException If something goes wrong + * @throws IOException If something goes wrong + * @throws WorkflowConfigurationException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws WorkflowException If something goes wrong + */ + public GroupRest getWorkflowGroupForRole(Context context, Collection collection, String workflowRole) + throws SQLException, IOException, WorkflowConfigurationException, AuthorizeException, WorkflowException { + + if (WorkflowUtils.getCollectionAndRepositoryRoles(collection).get(workflowRole) == null) { + throw new ResourceNotFoundException("Couldn't find role for: " + workflowRole + + " in the collection with UUID: " + collection.getID()); + } + Group group = workflowService.getWorkflowRoleGroup(context, collection, workflowRole, null); + if (group == null) { + throw new ResourceNotFoundException("The requested Group was not found"); + } + return converter.toRest(group, utils.obtainProjection()); + } + + /** + * This method will create the WorkflowGroup for the given Collection and workflowRole + * @param context The relevant DSpace context + * @param request The current request + * @param collection The given collection + * @param workflowRole The given workflowRole + * @return The created WorkflowGroup for the given Collection and workflowRole + * @throws SQLException If something goes wrong + * @throws WorkflowConfigurationException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws WorkflowException If something goes wrong + * @throws IOException If something goes wrong + */ + public GroupRest createWorkflowGroupForRole(Context context, HttpServletRequest request, Collection collection, String workflowRole) + throws SQLException, WorkflowConfigurationException, AuthorizeException, WorkflowException, IOException { + Group group = workflowService.createWorkflowRoleGroup(context, collection, workflowRole); + populateGroupInformation(context, request, group); + return converter.toRest(group, utils.obtainProjection()); + } + + /** + * This method will delete the WorkflowGroup for a given Collection and workflowRole + * @param context The relevant DSpace context + * @param request The current DSpace request + * @param collection The given Collection + * @param workflowRole The given WorkflowRole + * @throws SQLException If something goes wrong + * @throws WorkflowConfigurationException If something goes wrong + * @throws AuthorizeException If something goes wrong + * @throws WorkflowException If something goes wrong + * @throws IOException If something goes wrong + */ + public void deleteWorkflowGroupForRole(Context context, HttpServletRequest request, Collection collection, String workflowRole) + throws SQLException, WorkflowConfigurationException, AuthorizeException, WorkflowException, IOException { + Group group = workflowService.getWorkflowRoleGroup(context, collection, workflowRole, null); + if (!poolTaskService.findByGroup(context, group).isEmpty()) { + throw new UnprocessableEntityException("The Group that was attempted to be deleted still has Pooltasks open"); + } + if (group == null) { + throw new ResourceNotFoundException("The requested Group was not found"); + } + List collectionRoles = collectionRoleService.findByGroup(context, group); + if (!collectionRoles.isEmpty()) { + collectionRoles.stream().forEach(collectionRole -> { + try { + collectionRoleService.delete(context, collectionRole); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + }); + } + groupService.delete(context, group); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java new file mode 100644 index 0000000000..511cac597e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java @@ -0,0 +1,83 @@ +/** + * 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.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "submittergroup" subresource of an individual collection. + * + */ +@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.SUBMITTERS_GROUP) +public class CollectionSubmitterGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private CollectionService collectionService; + + @Autowired + private AuthorizeService authorizeService; + + /** + * This method is responsible for retrieving the Submittergroup of a Collection + * @param request The current request + * @param collectionId The id of the collection that we'll retrieve the submitterGroup for + * @param optionalPageable The pageable if applicable + * @param projection The current Projection + * @return The submitterGroup of the given collection + */ + @PreAuthorize("hasPermission(#collectionId, 'COLLECTION', 'READ')") + public GroupRest getSubmittersGroup(@Nullable HttpServletRequest request, + UUID collectionId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Collection collection = collectionService.find(context, collectionId); + if (collection == null) { + throw new ResourceNotFoundException("No such collection: " + collectionId); + } + + Group submitters = collection.getSubmitters(); + + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException("The current user was not allowed to retrieve the submitterGroup for" + + " collection: " + collectionId); + } + if (submitters == null) { + return null; + } + if (!authorizeService.authorizeActionBoolean(context, submitters, Constants.READ)) { + throw new AccessDeniedException("The current user doesn't have sufficient rights to access the submitter" + + "group of collection with id: " + collectionId); + } + return converter.toRest(submitters, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java new file mode 100644 index 0000000000..807ace7b37 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java @@ -0,0 +1,415 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.JsonPath.read; +import static org.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.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +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.matcher.GroupMatcher; +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.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class CollectionGroupRestControllerIT extends AbstractControllerIntegrationTest { + + + @Autowired + private CollectionService collectionService; + + @Autowired + private GroupService groupService; + + @Autowired + private AuthorizeService authorizeService; + + Collection collection; + + @Before + public void setup() { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("test").build(); + collection = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + } + + @Test + public void getCollectionAdminGroupTest() throws Exception { + + Group adminGroup = collectionService.createAdministrators(context, collection); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + } + + @Test + public void getCollectionAdminGroupTestParentCommunityAdmin() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + } + + @Test + public void getCollectionAdminGroupTestCollectionAdmin() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + } + + @Test + public void getCollectionAdminGroupUnAuthorizedTest() throws Exception { + collectionService.createAdministrators(context, collection); + + getClient().perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getCollectionAdminGroupForbiddenTest() throws Exception { + collectionService.createAdministrators(context, collection); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isForbidden()); + } + + @Test + public void getCollectionAdminGroupNoContentTest() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + } + + @Test + public void getCollectionAdminGroupWrongCollectionUuidResourceNotFoundTest() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + UUID.randomUUID() + "/adminGroup")) + .andExpect(status().isNotFound()); + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupSuccess() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group adminGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupDcTitleUnprocessable() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + metadataRest.put("dc.title", new MetadataValueRest("testTitle")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + + @Test + public void postCollectionAdminGroupCreateAdminGroupSuccessParentCommunityAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group adminGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupSuccessCollectionAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group adminGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(adminGroup.getID(), adminGroup.getName()))); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupUnAuthorized() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + getClient().perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupForbidden() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupNotFound() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + UUID.randomUUID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupUnProcessableName() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setName("Fail"); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void postCollectionAdminGroupCreateAdminGroupUnProcessablePermanent() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setPermanent(true); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void deleteCollectionAdminGroupTest() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + } + + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionAdminGroupTestParentCommunityAdmin() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + } + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionAdminGroupTestCollectionAdmin() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteCollectionAdminGroupUnAuthorizedTest() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + + + getClient().perform(delete("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteCollectionAdminGroupForbiddenTest() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/adminGroup")) + .andExpect(status().isForbidden()); + } + + + @Test + public void deleteCollectionAdminGroupNotFoundTest() throws Exception { + Group adminGroup = collectionService.createAdministrators(context, collection); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + UUID.randomUUID() + "/adminGroup")) + .andExpect(status().isNotFound()); + } +} 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 269206a8ce..abed03cd00 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 @@ -155,6 +155,12 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { @Override public void cleanup() throws Exception { + context.turnOffAuthorisationSystem(); + if (collection.getAdministrators() != null) { + Group adminGroup = collection.getAdministrators(); + collectionService.removeAdministrators(context, collection); + groupService.delete(context, adminGroup); + } deleteWorkflowGroups(collection); delete(collection); } 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 de5db48901..3bc8aee1b6 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 @@ -84,7 +84,9 @@ public class CollectionMatcher { "logo", "mappedItems", "parentCommunity", - "self" + "self", + "adminGroup" + ); } From d4cc5f4587f12c7bbcce4b6dc982bf49ea2b9eb4 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 17 Mar 2020 16:41:10 +0100 Subject: [PATCH 052/749] Using token since workspace security is now in place --- .../test/java/org/dspace/app/rest/PatchMetadataIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 1959079a84..eb6b42d246 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -670,7 +670,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -707,7 +707,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -743,7 +743,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -778,7 +778,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( From 932664650c696fd037ddd2d01a2f97eeb9106a4b Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 17 Mar 2020 17:24:11 +0100 Subject: [PATCH 053/749] Using token since workspace security is now in place --- .../src/test/java/org/dspace/app/rest/PatchMetadataIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index eb6b42d246..e1b7258ae1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -633,7 +633,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient().perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", From b5c8afefeef291e657f6b9f3a9ce8094abb8fcfd Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 18 Mar 2020 15:17:34 +0100 Subject: [PATCH 054/749] [Task 69689] fixed the workflow groups endpoints --- .../org/dspace/xmlworkflow/state/Workflow.java | 2 +- .../dspaceFolder/config/spring/api/workflow.xml | 16 ++++++++-------- .../java/org/dspace/xmlworkflow/RoleTest.java | 6 +++--- dspace/config/spring/api/workflow-actions.xml | 2 +- dspace/config/spring/api/workflow.xml | 16 ++++++++-------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/Workflow.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/Workflow.java index a064e27ebf..a31b24a7b6 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/Workflow.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/Workflow.java @@ -104,7 +104,7 @@ public class Workflow implements BeanNameAware { Map roles = new HashMap<>(); for (Step step : steps) { if (step.getRole() != null) { - roles.put(step.getRole().getName(), step.getRole()); + roles.put(step.getRole().getId(), step.getRole()); } } return roles; 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 47f22c5d88..97dd957474 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 @@ -43,7 +43,7 @@ - + - + - + - + - + @@ -140,7 +140,7 @@ - + @@ -159,7 +159,7 @@ - + - + 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 262804f12b..ff2a84a440 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java @@ -30,7 +30,7 @@ public class RoleTest extends AbstractUnitTest { @Test public void defaultWorkflow_RoleReviewer() { - Role role = defaultWorkflow.getRoles().get("Reviewer"); + Role role = defaultWorkflow.getRoles().get("reviewer"); 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()); @@ -39,7 +39,7 @@ public class RoleTest extends AbstractUnitTest { @Test public void defaultWorkflow_RoleEditor() { - Role role = defaultWorkflow.getRoles().get("Editor"); + Role role = defaultWorkflow.getRoles().get("editor"); 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()); @@ -48,7 +48,7 @@ public class RoleTest extends AbstractUnitTest { @Test public void defaultWorkflow_RoleFinalEditor() { - Role role = defaultWorkflow.getRoles().get("Final Editor"); + Role role = defaultWorkflow.getRoles().get("finaleditor"); 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()); diff --git a/dspace/config/spring/api/workflow-actions.xml b/dspace/config/spring/api/workflow-actions.xml index 7381972961..318d1ad3d7 100644 --- a/dspace/config/spring/api/workflow-actions.xml +++ b/dspace/config/spring/api/workflow-actions.xml @@ -13,7 +13,7 @@ - + diff --git a/dspace/config/spring/api/workflow.xml b/dspace/config/spring/api/workflow.xml index 264b8add43..1c355618ea 100644 --- a/dspace/config/spring/api/workflow.xml +++ b/dspace/config/spring/api/workflow.xml @@ -43,7 +43,7 @@ - + - + - + - + - + @@ -140,7 +140,7 @@ - + @@ -159,7 +159,7 @@ - + - + From cc471c4f50fd8191bb803c52e2a18c4ee218c9f5 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Wed, 18 Mar 2020 15:52:21 +0100 Subject: [PATCH 055/749] Add the SolrServiceWorkspaceWorkflowRestrictionPlugin and the workflowAdmin bean This moves some workspace and workflow specific code from SolrServiceImpl to the new SolrServiceWorkspaceWorkflowRestrictionPlugin. This also updates the SolrServiceResourceRestrictionPlugin to restrict those records based on read rights as well. --- .../org/dspace/discovery/SolrServiceImpl.java | 41 +-------- .../SolrServiceResourceRestrictionPlugin.java | 32 ++++--- .../discovery/SolrServiceSearchPlugin.java | 3 +- ...iceWorkspaceWorkflowRestrictionPlugin.java | 87 +++++++++++++++++++ dspace/config/spring/api/discovery.xml | 4 +- 5 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 94361b7cf9..c9612721f5 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -848,46 +848,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { } - boolean isWorkspace = StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), - DISCOVER_WORKSPACE_CONFIGURATION_NAME); - boolean isWorkflow = StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), - DISCOVER_WORKFLOW_CONFIGURATION_NAME); - EPerson currentUser = context.getCurrentUser(); - - // extra security check to avoid the possibility that an anonymous user - // get access to workspace or workflow - if (currentUser == null && (isWorkflow || isWorkspace)) { - throw new IllegalStateException("An anonymous user cannot perform a workspace or workflow search"); - } - if (isWorkspace) { - // insert filter by submitter - solrQuery - .addFilterQuery("submitter_authority:(" + currentUser.getID() + ")"); - } else if (isWorkflow) { - // Retrieve all the groups the current user is a member of ! - Set groups; - try { - groups = groupService.allMemberGroupsSet(context, currentUser); - } catch (SQLException e) { - throw new org.dspace.discovery.SearchServiceException(e.getMessage(), e); - } - - // insert filter by controllers - StringBuilder controllerQuery = new StringBuilder(); - controllerQuery.append("taskfor:(e" + currentUser.getID()); - for (Group group : groups) { - controllerQuery.append(" OR g").append(group.getID()); - } - controllerQuery.append(")"); - solrQuery.addFilterQuery(controllerQuery.toString()); - } - - //Add any configured search plugins ! - List solrServiceSearchPlugins = DSpaceServicesFactory.getInstance().getServiceManager() - .getServicesByType( - SolrServiceSearchPlugin - .class); + List solrServiceSearchPlugins = DSpaceServicesFactory.getInstance() + .getServiceManager().getServicesByType(SolrServiceSearchPlugin.class); for (SolrServiceSearchPlugin searchPlugin : solrServiceSearchPlugins) { searchPlugin.additionalSearchParameters(context, discoveryQuery, solrQuery); } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java index 38e85dbf93..5cfec9b60c 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Set; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.common.SolrInputDocument; @@ -19,16 +18,22 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.DSpaceObject; +import org.dspace.content.InProgressSubmission; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.discovery.indexobject.IndexableClaimedTask; import org.dspace.discovery.indexobject.IndexableDSpaceObject; +import org.dspace.discovery.indexobject.IndexableInProgressSubmission; +import org.dspace.discovery.indexobject.IndexablePoolTask; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; +import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.springframework.beans.factory.annotation.Autowired; /** @@ -57,8 +62,21 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu @Override public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { + DSpaceObject dso = null; if (idxObj instanceof IndexableDSpaceObject) { - DSpaceObject dso = ((IndexableDSpaceObject) idxObj).getIndexedObject(); + dso = ((IndexableDSpaceObject) idxObj).getIndexedObject(); + } else if (idxObj instanceof IndexableInProgressSubmission) { + final InProgressSubmission inProgressSubmission + = ((IndexableInProgressSubmission) idxObj).getIndexedObject(); + dso = inProgressSubmission.getItem(); + } else if (idxObj instanceof IndexablePoolTask) { + final PoolTask poolTask = ((IndexablePoolTask) idxObj).getIndexedObject(); + dso = poolTask.getWorkflowItem().getItem(); + } else if (idxObj instanceof IndexableClaimedTask) { + final ClaimedTask claimedTask = ((IndexableClaimedTask) idxObj).getIndexedObject(); + dso = claimedTask.getWorkflowItem().getItem(); + } + if (dso != null) { try { List policies = authorizeService.getPoliciesActionFilter(context, dso, Constants.READ); for (ResourcePolicy resourcePolicy : policies) { @@ -79,7 +97,8 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu } } catch (SQLException e) { log.error(LogManager.getHeader(context, "Error while indexing resource policies", - "DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")")); + "DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")" + )); } } } @@ -87,13 +106,6 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu @Override public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery) { try { - // skip workspace and workflow queries as security for it them is builtin in the SolrServiceImpl - if (StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), - SolrServiceImpl.DISCOVER_WORKSPACE_CONFIGURATION_NAME) - || StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), - SolrServiceImpl.DISCOVER_WORKFLOW_CONFIGURATION_NAME)) { - return; - } if (!authorizeService.isAdmin(context)) { StringBuilder resourceQuery = new StringBuilder(); //Always add the anonymous group id to the query diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSearchPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSearchPlugin.java index 88cc5edd99..d9259ce808 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSearchPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSearchPlugin.java @@ -19,5 +19,6 @@ import org.dspace.core.Context; */ public interface SolrServiceSearchPlugin { - public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery); + public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery) + throws SearchServiceException; } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java new file mode 100644 index 0000000000..139a6134f8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java @@ -0,0 +1,87 @@ +package org.dspace.discovery; + +import org.apache.commons.lang3.StringUtils; +import org.apache.solr.client.solrj.SolrQuery; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.sql.SQLException; +import java.util.Set; + +import static org.dspace.discovery.SolrServiceImpl.DISCOVER_WORKFLOW_CONFIGURATION_NAME; +import static org.dspace.discovery.SolrServiceImpl.DISCOVER_WORKSPACE_CONFIGURATION_NAME; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 18 Mar 2020 + */ +public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServiceSearchPlugin { + + /** + * The name of the discover configuration used by administrators to search for workflow tasks + */ + public static final String DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME = "workflowAdmin"; + + @Autowired(required = true) + protected GroupService groupService; + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Override + public void additionalSearchParameters( + Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery + ) throws SearchServiceException { + boolean isWorkspace = StringUtils.startsWith( + discoveryQuery.getDiscoveryConfigurationName(), + DISCOVER_WORKSPACE_CONFIGURATION_NAME + ); + boolean isWorkflow = StringUtils.startsWith( + discoveryQuery.getDiscoveryConfigurationName(), + DISCOVER_WORKFLOW_CONFIGURATION_NAME + ); + boolean isWorkflowAdmin = isAdmin(context) + && DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + EPerson currentUser = context.getCurrentUser(); + + // extra security check to avoid the possibility that an anonymous user + // get access to workspace or workflow + if (currentUser == null && (isWorkflow || isWorkspace)) { + throw new IllegalStateException( + "An anonymous user cannot perform a workspace or workflow search"); + } + if (isWorkspace) { + // insert filter by submitter + solrQuery.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")"); + } else if (isWorkflow && !isWorkflowAdmin) { + // Retrieve all the groups the current user is a member of ! + Set groups; + try { + groups = groupService.allMemberGroupsSet(context, currentUser); + } catch (SQLException e) { + throw new SearchServiceException(e.getMessage(), e); + } + + // insert filter by controllers + StringBuilder controllerQuery = new StringBuilder(); + controllerQuery.append("taskfor:(e").append(currentUser.getID()); + for (Group group : groups) { + controllerQuery.append(" OR g").append(group.getID()); + } + controllerQuery.append(")"); + solrQuery.addFilterQuery(controllerQuery.toString()); + } + } + + private boolean isAdmin(Context context) throws SearchServiceException { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new SearchServiceException(e.getMessage(), e); + } + } +} diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 359f36d8bf..a3ae6c15f7 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -25,7 +25,8 @@ - + + @@ -54,6 +55,7 @@ + From 1b5761f2b9a9eff0b06f6ee571453185a5d7f6ee Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 19 Mar 2020 13:40:24 +0100 Subject: [PATCH 056/749] 69741: Restrict metadata exposure acccording to hidden metadata configuration --- .../rest/converter/DSpaceObjectConverter.java | 61 ++++++++++++++++++- .../app/rest/converter/ItemConverter.java | 11 ++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 7d3d89ad0c..4151793474 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -7,9 +7,22 @@ */ package org.dspace.app.rest.converter; +import java.sql.SQLException; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.MetadataValueList; import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.service.MetadataExposureService; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; +import org.dspace.core.Context; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; import org.springframework.beans.factory.annotation.Autowired; /** @@ -21,11 +34,23 @@ import org.springframework.beans.factory.annotation.Autowired; * @author Andrea Bollini (andrea.bollini at 4science.it) */ public abstract class DSpaceObjectConverter implements DSpaceConverter { + .DSpaceObjectRest> implements DSpaceConverter { + + private static final Logger log = LogManager.getLogger(DSpaceObjectConverter.class); @Autowired ConverterService converter; + @Autowired + AuthorizeService authorizeService; + + @Autowired + MetadataExposureService metadataExposureService; + + @Autowired + RequestService requestService; + + @Override public R convert(M obj, Projection projection) { R resource = newInstance(); @@ -35,10 +60,42 @@ public abstract class DSpaceObjectConverter metadata = obj.getMetadata(); + try { + if (context != null && authorizeService.isAdmin(context)) { + return new MetadataValueList(metadata); + } + for (MetadataValue mv : metadata) { + MetadataField metadataField = mv.getMetadataField(); + if (metadataExposureService + .isHidden(context, metadataField.getMetadataSchema().getNamespace(), + metadataField.getElement(), + metadataField.getQualifier())) { + metadata.remove(mv); + } + } + } catch (SQLException e) { + log.error(e); + } + return new MetadataValueList(metadata); + } + + private Context getContext() { + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + return ContextUtil.obtainContext(currentRequest.getServletRequest()); + } + return null; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 8d22def10e..d5b017fdc4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; +import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -47,13 +48,15 @@ public class ItemConverter item.setWithdrawn(obj.isWithdrawn()); item.setLastModified(obj.getLastModified()); - List fullList = itemService.getMetadata(obj, Item.ANY, Item.ANY, Item.ANY, Item.ANY, true); - MetadataValueList metadataValues = new MetadataValueList(fullList); - item.setMetadata(converter.toRest(metadataValues, projection)); - return item; } + @Override + public MetadataValueList getPermissionFilteredMetadata(Context context, Item obj) { + List fullList = itemService.getMetadata(obj, Item.ANY, Item.ANY, Item.ANY, Item.ANY, true); + return new MetadataValueList(fullList); + } + @Override protected ItemRest newInstance() { return new ItemRest(); From 2d048d7df82c272a8c6be8cb867249da355d9109 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 20 Mar 2020 08:25:13 +0100 Subject: [PATCH 057/749] Intermediate Commit --- .../org/dspace/workflow/WorkflowService.java | 1 - .../config/spring/api/workflow-actions.xml | 2 +- .../java/org/dspace/xmlworkflow/RoleTest.java | 7 +- .../rest/CollectionGroupRestController.java | 78 +++++++++++-------- .../repository/CollectionRestRepository.java | 10 ++- ...ollectionSubmitterGroupLinkRepository.java | 4 +- 6 files changed, 59 insertions(+), 43 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java index 969fc80f13..e190a56bc1 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java @@ -17,7 +17,6 @@ import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.WorkflowConfigurationException; /** 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 index 7381972961..318d1ad3d7 100644 --- 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 @@ -13,7 +13,7 @@ - + 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 ff2a84a440..e11f0f8a5b 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java @@ -12,6 +12,7 @@ import static junit.framework.TestCase.assertEquals; import org.dspace.AbstractUnitTest; import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.state.Workflow; +import org.junit.Ignore; import org.junit.Test; /** @@ -57,21 +58,21 @@ public class RoleTest extends AbstractUnitTest { @Test public void selectSingleReviewer_RoleReviewManagers() { - Role role = selectSingleReviewer.getRoles().get("ReviewManagers"); + Role role = selectSingleReviewer.getRoles().get("reviewmanagers"); assertEquals("ReviewManagers", role.getName()); assertEquals(Role.Scope.REPOSITORY, role.getScope()); } @Test public void selectSingleReviewer_RoleReviewer() { - Role role = selectSingleReviewer.getRoles().get("Reviewer"); + Role role = selectSingleReviewer.getRoles().get("scoreassignedreviewer"); assertEquals("Reviewer", role.getName()); assertEquals(Role.Scope.ITEM, role.getScope()); } @Test public void scoreReview_RoleScoreReviewers() { - Role role = scoreReview.getRoles().get("ScoreReviewers"); + Role role = scoreReview.getRoles().get("scorereviewers"); assertEquals("ScoreReviewers", role.getName()); assertEquals(Role.Scope.COLLECTION, role.getScope()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java index ad656fbe6e..f7290aaf03 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java @@ -31,7 +31,6 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; import org.dspace.workflow.WorkflowService; -import org.dspace.xmlworkflow.WorkflowUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -75,7 +74,7 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll create an adminGroup * @param response The current response * @param request The current request - * @return The created AdminGroup + * @return The created AdminGroup * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ @@ -112,7 +111,7 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll delete the AdminGroup * @param response The current response * @param request The current request - * @return An empty response if the deletion was successful + * @return An empty response if the deletion was successful * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong * @throws IOException If something goes wrong @@ -120,7 +119,7 @@ public class CollectionGroupRestController { @RequestMapping(method = RequestMethod.DELETE, value = "/adminGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") public ResponseEntity deleteAdminGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + HttpServletRequest request) throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainContext(request); @@ -149,14 +148,14 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll create a submitterGroup * @param response The current response * @param request The current request - * @return The created SubmitterGroup + * @return The created SubmitterGroup * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ @RequestMapping(method = RequestMethod.POST, value = "/submittersGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") public ResponseEntity postSubmittersGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + HttpServletRequest request) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); @@ -186,7 +185,7 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll delete the SubmittersGroup * @param response The current response * @param request The current request - * @return An empty response if the deletion was successful + * @return An empty response if the deletion was successful * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong * @throws IOException If something goes wrong @@ -194,7 +193,7 @@ public class CollectionGroupRestController { @RequestMapping(method = RequestMethod.DELETE, value = "/submittersGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") public ResponseEntity deleteSubmittersGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + HttpServletRequest request) throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainContext(request); @@ -223,14 +222,14 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll create a ItemReadGroup * @param response The current response * @param request The current request - * @return The created ItemReadGroup + * @return The created ItemReadGroup * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ @RequestMapping(method = RequestMethod.POST, value = "/itemReadGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") public ResponseEntity postItemReadGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + HttpServletRequest request) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); @@ -248,8 +247,10 @@ public class CollectionGroupRestController { .getAuthorizedGroups(context, collection, Constants.DEFAULT_ITEM_READ); if (itemGroups != null && !itemGroups.isEmpty()) { Group itemReadGroup = itemGroups.get(0); - if (itemReadGroup != null && !StringUtils.equalsIgnoreCase(itemReadGroup.getName(),Group.ANONYMOUS)) { - throw new UnprocessableEntityException("Unable to create a new default read group because either the group already exists or multiple groups are assigned the default privileges."); + if (itemReadGroup != null && !StringUtils.equalsIgnoreCase(itemReadGroup.getName(), Group.ANONYMOUS)) { + throw new UnprocessableEntityException( + "Unable to create a new default read group because either the group already exists or multiple " + + "groups are assigned the default privileges."); } } @@ -265,7 +266,7 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll delete the ItemReadGroup * @param response The current response * @param request The current request - * @return An empty response if the deletion was successful + * @return An empty response if the deletion was successful * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong * @throws IOException If something goes wrong @@ -273,7 +274,7 @@ public class CollectionGroupRestController { @RequestMapping(method = RequestMethod.DELETE, value = "/itemReadGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") public ResponseEntity deleteItemReadGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + HttpServletRequest request) throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainContext(request); @@ -292,7 +293,8 @@ public class CollectionGroupRestController { if (itemGroups != null && !itemGroups.isEmpty()) { Group itemReadGroup = itemGroups.get(0); if (itemReadGroup == null || StringUtils.equalsIgnoreCase(itemReadGroup.getName(), Group.ANONYMOUS)) { - throw new UnprocessableEntityException("Unable to delete the default read group because it's the default"); + throw new UnprocessableEntityException( + "Unable to delete the default read group because it's the default"); } } else { throw new UnprocessableEntityException("The collection with UUID: " + uuid + " doesn't have " + @@ -310,14 +312,14 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll create a BitstreamReadGroup * @param response The current response * @param request The current request - * @return The created BitstreamReadGroup + * @return The created BitstreamReadGroup * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ @RequestMapping(method = RequestMethod.POST, value = "/bitstreamReadGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") public ResponseEntity postBitstreamReadGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + HttpServletRequest request) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); @@ -336,8 +338,10 @@ public class CollectionGroupRestController { .getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); if (bitstreamGroups != null && !bitstreamGroups.isEmpty()) { Group bitstreamGroup = bitstreamGroups.get(0); - if (bitstreamGroup != null && !StringUtils.equalsIgnoreCase(bitstreamGroup.getName(),Group.ANONYMOUS)) { - throw new UnprocessableEntityException("Unable to create a new default read group because either the group already exists or multiple groups are assigned the default privileges."); + if (bitstreamGroup != null && !StringUtils.equalsIgnoreCase(bitstreamGroup.getName(), Group.ANONYMOUS)) { + throw new UnprocessableEntityException( + "Unable to create a new default read group because either the group already exists or multiple " + + "groups are assigned the default privileges."); } } @@ -354,15 +358,16 @@ public class CollectionGroupRestController { * @param uuid The UUID of the collection for which we'll delete the bitstreamReadGroup * @param response The current response * @param request The current request - * @return An empty response if the deletion was successful + * @return An empty response if the deletion was successful * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong * @throws IOException If something goes wrong */ @RequestMapping(method = RequestMethod.DELETE, value = "/bitstreamReadGroup") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'WRITE')") - public ResponseEntity deleteBitstreamReadGroup(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) + public ResponseEntity deleteBitstreamReadGroup(@PathVariable UUID uuid, + HttpServletResponse response, + HttpServletRequest request) throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainContext(request); @@ -377,11 +382,14 @@ public class CollectionGroupRestController { " collection: " + uuid); } - List bitstreamGroups = authorizeService.getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); + List bitstreamGroups = authorizeService + .getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); if (bitstreamGroups != null && !bitstreamGroups.isEmpty()) { Group bitstreamReadGroup = bitstreamGroups.get(0); - if (bitstreamReadGroup == null || StringUtils.equalsIgnoreCase(bitstreamReadGroup.getName(), Group.ANONYMOUS)) { - throw new UnprocessableEntityException("Unable to delete the default read group because it's the default"); + if (bitstreamReadGroup == null || StringUtils + .equalsIgnoreCase(bitstreamReadGroup.getName(), Group.ANONYMOUS)) { + throw new UnprocessableEntityException( + "Unable to delete the default read group because it's the default"); } } else { throw new UnprocessableEntityException("The collection with UUID: " + uuid + " doesn't have " + @@ -399,13 +407,14 @@ public class CollectionGroupRestController { * @param response The current response * @param request The current request * @param workflowRole The given workflowRole - * @return The workflowGroup for the given collection and workflowrole + * @return The workflowGroup for the given collection and workflowrole * @throws Exception If something goes wrong */ @RequestMapping(method = RequestMethod.GET, value = "/workflowGroups/{workflowRole}") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") public GroupResource getWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request, @PathVariable String workflowRole) throws Exception { + HttpServletRequest request, @PathVariable String workflowRole) + throws Exception { Context context = ContextUtil.obtainContext(request); Collection collection = collectionService.find(context, uuid); if (collection == null) { @@ -422,13 +431,14 @@ public class CollectionGroupRestController { * @param response The current response * @param request The current request * @param workflowRole The given workflowRole - * @return The workflowGroup for the given collection and workflowrole + * @return The workflowGroup for the given collection and workflowrole * @throws Exception If something goes wrong */ @RequestMapping(method = RequestMethod.POST, value = "/workflowGroups/{workflowRole}") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") public GroupResource postWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request, @PathVariable String workflowRole) throws Exception { + HttpServletRequest request, @PathVariable String workflowRole) + throws Exception { Context context = ContextUtil.obtainContext(request); Collection collection = collectionService.find(context, uuid); if (collection == null) { @@ -440,7 +450,8 @@ public class CollectionGroupRestController { throw new UnprocessableEntityException("WorkflowGroup already exists for the role: " + workflowRole + " in collection with UUID: " + collection.getID()); } - GroupRest groupRest = collectionRestRepository.createWorkflowGroupForRole(context, request, collection, workflowRole); + GroupRest groupRest = collectionRestRepository + .createWorkflowGroupForRole(context, request, collection, workflowRole); context.complete(); return converterService.toResource(groupRest); } @@ -456,8 +467,11 @@ public class CollectionGroupRestController { */ @RequestMapping(method = RequestMethod.DELETE, value = "/workflowGroups/{workflowRole}") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") - public ResponseEntity deleteWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request, @PathVariable String workflowRole) throws Exception { + public ResponseEntity deleteWorkflowGroupForRole(@PathVariable UUID uuid, + HttpServletResponse response, + HttpServletRequest request, + @PathVariable String workflowRole) + throws Exception { Context context = ContextUtil.obtainContext(request); Collection collection = collectionService.find(context, uuid); if (collection == null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index b5001098a5..b6d2cca95c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -48,7 +48,6 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; -import org.dspace.services.RequestService; import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowService; import org.dspace.xmlworkflow.WorkflowConfigurationException; @@ -554,7 +553,8 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository Date: Fri, 20 Mar 2020 10:04:13 +0100 Subject: [PATCH 058/749] Test for Unprocessable status and adjust code style --- .../dspace/app/rest/GroupRestController.java | 10 +++- .../rest/repository/GroupRestRepository.java | 25 ++++++---- .../app/rest/GroupRestRepositoryIT.java | 49 +++++++++++++++++++ 3 files changed, 73 insertions(+), 11 deletions(-) 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 index 5555a730cc..bc837f9c60 100644 --- 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 @@ -316,8 +316,14 @@ public class GroupRestController { throw new AuthorizeException("not authorized to manage this group"); } + /** + * This returns the DSpace Object (Community, Collection) belonging to this Group. + * This is only applicable for roles in that DSpace Object + * e.g. the Community Administrator or Collection Submitter Group + * @param uuid The uuid of the group + */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/object") - public DSpaceObjectRest object(@PathVariable UUID uuid) { - return repository.object(uuid); + public DSpaceObjectRest getParentObject(@PathVariable UUID uuid) { + return repository.getParentObject(uuid); } } 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 8292234af9..71775ae4ab 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,14 @@ */ 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; +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; @@ -33,14 +41,6 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; - -import static org.apache.commons.lang3.StringUtils.isBlank; - /** * This is the repository responsible to manage Group Rest object * @@ -188,8 +188,15 @@ public class GroupRestRepository extends DSpaceObjectRestRepository Date: Fri, 20 Mar 2020 11:29:16 +0100 Subject: [PATCH 059/749] Intermediate Commit --- .../src/test/java/org/dspace/xmlworkflow/RoleTest.java | 1 - .../java/org/dspace/app/rest/builder/CollectionBuilder.java | 1 - .../java/org/dspace/app/rest/matcher/CollectionMatcher.java | 5 ++++- 3 files changed, 4 insertions(+), 3 deletions(-) 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 e11f0f8a5b..f000f8f4d9 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/RoleTest.java @@ -12,7 +12,6 @@ import static junit.framework.TestCase.assertEquals; import org.dspace.AbstractUnitTest; import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.state.Workflow; -import org.junit.Ignore; import org.junit.Test; /** 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 abed03cd00..21d87950ec 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 @@ -159,7 +159,6 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { if (collection.getAdministrators() != null) { Group adminGroup = collection.getAdministrators(); collectionService.removeAdministrators(context, collection); - groupService.delete(context, adminGroup); } deleteWorkflowGroups(collection); delete(collection); 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 3bc8aee1b6..a1037511b9 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 @@ -85,7 +85,10 @@ public class CollectionMatcher { "mappedItems", "parentCommunity", "self", - "adminGroup" + "adminGroup", + "submittersGroup", + "itemReadGroup", + "bitstreamReadGroup" ); } From 227317bbe0f2431b651d11ac41cbb27c55e21b8b Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Fri, 20 Mar 2020 11:37:36 +0100 Subject: [PATCH 060/749] Test for Unprocessable status for permanent group names --- .../app/rest/GroupRestRepositoryIT.java | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) 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 89c6af2098..672e45b748 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 @@ -428,17 +428,13 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void patchGroupMetadataUnprocessable() throws Exception { - //We turn off the authorization system in order to create the structure defined below 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 two collections. parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -473,6 +469,33 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isUnprocessableEntity()); ; } + @Test + public void patchPermanentGroupMetadataUnprocessable() throws Exception { + context.turnOffAuthorisationSystem(); + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + final Group group = groupService.findByName(context, Group.ANONYMOUS); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + String requestBody + = " [\n" + + " {\n" + + " \"op\": \"replace\",\n" + + " \"path\": \"/metadata/dc.title/1/value\",\n" + + " \"value\": \"title A\"\n" + + " },\n" + + " {\n" + + " \"op\": \"replace\",\n" + + " \"path\": \"/metadata/dc.title/1/language\",\n" + + " \"value\": \"en_US\"\n" + + " }\n" + + " ]"; + + getClient(token) + .perform(patch("/api/eperson/groups/" + group.getID()).content(requestBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); ; + } + @Test public void addChildGroupTest() throws Exception { From 0541a25d2f356cf152a105eaa9e5253e4e123c6b Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 20 Mar 2020 13:08:27 +0100 Subject: [PATCH 061/749] Fixed various tests --- .../app/rest/BrowsesResourceControllerIT.java | 14 +++++++++----- .../app/rest/CollectionRestRepositoryIT.java | 5 +++-- .../org/dspace/app/rest/ItemRestRepositoryIT.java | 4 +++- .../app/rest/WorkflowItemRestRepositoryIT.java | 1 - 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index b9d8a030d0..d63767dac5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -417,9 +417,11 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + //** WHEN ** //An anonymous user browses the items in the Browse by item endpoint - getClient().perform(get("/api/discover/browses/title/items") + getClient(token).perform(get("/api/discover/browses/title/items") .param("projection", "full")) //** THEN ** //The status has to be 200 OK @@ -438,7 +440,7 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //** WHEN ** //An anonymous user browses the items in the Browse by item endpoint - getClient().perform(get("/api/discover/browses/author/entries") + getClient(token).perform(get("/api/discover/browses/author/entries") .param("projection", "full")) //** THEN ** //The status has to be 200 OK @@ -775,10 +777,12 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .build(); // ---- BROWSES BY ITEM ---- + String token = getAuthToken(admin.getEmail(), password); + //** WHEN ** //An anonymous user browses the items in the Browse by date issued endpoint //with startsWith set to 1990 - getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990") + getClient(token).perform(get("/api/discover/browses/dateissued/items?startsWith=1990") .param("size", "2") .param("projection", "full")) @@ -805,7 +809,7 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //** WHEN ** //An anonymous user browses the items in the Browse by Title endpoint //with startsWith set to T - getClient().perform(get("/api/discover/browses/title/items?startsWith=T") + getClient(token).perform(get("/api/discover/browses/title/items?startsWith=T") .param("size", "2") .param("projection", "full")) @@ -833,7 +837,7 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //** WHEN ** //An anonymous user browses the items in the Browse by Title endpoint //with startsWith set to Blade and scope set to Col 1 - getClient().perform(get("/api/discover/browses/title/items?startsWith=Blade") + getClient(token).perform(get("/api/discover/browses/title/items?startsWith=Blade") .param("scope", col1.getID().toString()) .param("size", "2") .param("projection", "full")) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index f7984a30a4..a64d41b745 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -977,7 +977,9 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .build(); Collection col2 = CollectionBuilder.createCollection(context, child1child).withName("Collection 2").build(); - getClient().perform(get("/api/core/collections/" + col1.getID()) + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/core/collections/" + col1.getID()) .param("projection", "level") .param("embedLevelDepth", "1")) .andExpect(status().isOk()) @@ -999,7 +1001,6 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$._embedded.logo._embedded.format").doesNotExist()); // Need this admin call for the AdminGroup embed in the Parentcommunity - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/core/collections/" + col1.getID()) .param("projection", "level") .param("embedLevelDepth", "3")) 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 45c398ba85..a4865020eb 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 @@ -247,8 +247,10 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Matcher publicItem1Matcher = ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, "Public item 1", "2017-10-17"); + String token = getAuthToken(admin.getEmail(), password); + // When full projection is requested, response should include expected properties, links, and embeds. - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient(token).perform(get("/api/core/items/" + publicItem1.getID()) .param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ItemMatcher.matchFullEmbeds())) 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 373d5fe4d2..a261b778f1 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 @@ -766,7 +766,6 @@ 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()) From f5fb694e851bc9b53edd77951f375649d06d57fa Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 20 Mar 2020 16:52:11 +0100 Subject: [PATCH 062/749] [Task 69689] added and fixed tests for the Collection Groups --- .../org/dspace/app/util/AuthorizeUtil.java | 6 + .../rest/CollectionGroupRestController.java | 35 +- ...ctionBitstreamReadGroupLinkRepository.java | 5 - ...CollectionItemReadGroupLinkRepository.java | 5 - .../repository/CollectionRestRepository.java | 11 + .../rest/CollectionGroupRestControllerIT.java | 1617 ++++++++++++++++- 6 files changed, 1659 insertions(+), 20 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java index 04db15eb4a..bbebc00d88 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java +++ b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java @@ -522,4 +522,10 @@ public class AuthorizeUtil { } } } + + public static void authorizeManageDefaultReadGroup(Context context, + Collection collection) throws AuthorizeException, SQLException { + AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + authorizeService.authorizeAction(context, collection, Constants.ADMIN); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java index f7290aaf03..f591e95ce0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java @@ -31,6 +31,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; import org.dspace.workflow.WorkflowService; +import org.dspace.xmlworkflow.WorkflowUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -420,6 +421,12 @@ public class CollectionGroupRestController { if (collection == null) { throw new ResourceNotFoundException("No such collection: " + uuid); } + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException( + "The current user was not allowed to retrieve the workflowGroup for" + + " collection: " + uuid); + } GroupRest groupRest = collectionRestRepository.getWorkflowGroupForRole(context, collection, workflowRole); return converterService.toResource(groupRest); @@ -436,15 +443,26 @@ public class CollectionGroupRestController { */ @RequestMapping(method = RequestMethod.POST, value = "/workflowGroups/{workflowRole}") @PreAuthorize("hasPermission(#uuid, 'COLLECTION', 'READ')") - public GroupResource postWorkflowGroupForRole(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request, @PathVariable String workflowRole) + public ResponseEntity postWorkflowGroupForRole(@PathVariable UUID uuid, + HttpServletResponse response, + HttpServletRequest request, + @PathVariable String workflowRole) throws Exception { Context context = ContextUtil.obtainContext(request); Collection collection = collectionService.find(context, uuid); if (collection == null) { throw new ResourceNotFoundException("No such collection: " + uuid); } - + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException( + "The current user was not allowed to retrieve the workflowGroup for" + + " collection: " + uuid); + } + if (WorkflowUtils.getCollectionAndRepositoryRoles(collection).get(workflowRole) == null) { + throw new ResourceNotFoundException("Couldn't find role for: " + workflowRole + + " in the collection with UUID: " + collection.getID()); + } Group group = workflowService.getWorkflowRoleGroup(context, collection, workflowRole, null); if (group != null) { throw new UnprocessableEntityException("WorkflowGroup already exists for the role: " + workflowRole + @@ -453,7 +471,9 @@ public class CollectionGroupRestController { GroupRest groupRest = collectionRestRepository .createWorkflowGroupForRole(context, request, collection, workflowRole); context.complete(); - return converterService.toResource(groupRest); + GroupResource groupResource = converterService.toResource(groupRest); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), groupResource); + } /** @@ -477,7 +497,12 @@ public class CollectionGroupRestController { if (collection == null) { throw new ResourceNotFoundException("No such collection: " + uuid); } - + if (!authorizeService.isAdmin(context) && !authorizeService.authorizeActionBoolean(context, collection, + Constants.ADMIN, true)) { + throw new AccessDeniedException( + "The current user was not allowed to retrieve the workflowGroup for" + + " collection: " + uuid); + } collectionRestRepository.deleteWorkflowGroupForRole(context, request, collection, workflowRole); context.complete(); return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java index 0fe12ba983..5c46dc80b5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java @@ -75,11 +75,6 @@ public class CollectionBitstreamReadGroupLinkRepository extends AbstractDSpaceRe if (bitstreamReadGroup == null) { return null; } - if (!authorizeService.authorizeActionBoolean(context, bitstreamReadGroup, Constants.READ)) { - throw new AccessDeniedException( - "The current user doesn't have sufficient rights to access the bitstreamReadGroup" + - "group of collection with id: " + collectionId); - } return converter.toRest(bitstreamReadGroup, projection); } catch (SQLException e) { throw new RuntimeException(e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java index ee17ddae59..32dc89cc84 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java @@ -76,11 +76,6 @@ public class CollectionItemReadGroupLinkRepository extends AbstractDSpaceRestRep if (itemReadGroup == null) { return null; } - if (!authorizeService.authorizeActionBoolean(context, itemReadGroup, Constants.READ)) { - throw new AccessDeniedException( - "The current user doesn't have sufficient rights to access the itemReadGroup" + - "group of collection with id: " + collectionId); - } return converter.toRest(itemReadGroup, projection); } catch (SQLException e) { throw new RuntimeException(e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index b6d2cca95c..59e3dc634f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -34,6 +34,7 @@ import org.dspace.app.rest.model.TemplateItemRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.model.wrapper.TemplateItem; import org.dspace.app.rest.utils.CollectionRestEqualityUtils; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; @@ -408,7 +409,11 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group submittersGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(submittersGroup.getID(), submittersGroup.getName()))); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupDcTitleUnprocessable() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + metadataRest.put("dc.title", new MetadataValueRest("testTitle")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupSuccessParentCommunityAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group submittersGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(submittersGroup.getID(), submittersGroup.getName()))); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupSuccessCollectionAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group submittersGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(submittersGroup.getID(), submittersGroup.getName()))); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupUnAuthorized() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + getClient().perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupForbidden() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupNotFound() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + UUID.randomUUID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupUnProcessableName() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setName("Fail"); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void postCollectionSubmittersGroupCreateSubmittersGroupUnProcessablePermanent() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setPermanent(true); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/submittersGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void deleteCollectionSubmitterGroupTest() throws Exception { + Group submittersGroup = collectionService.createSubmitters(context, collection); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isNoContent()); + } + + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionSubmittersGroupTestParentCommunityAdmin() throws Exception { + Group submittersGroup = collectionService.createSubmitters(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isNoContent()); + } + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionSubmittersGroupTestCollectionAdmin() throws Exception { + Group submittersGroup = collectionService.createSubmitters(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteCollectionSubmittersGroupUnAuthorizedTest() throws Exception { + Group submittersGroup = collectionService.createSubmitters(context, collection); + + + getClient().perform(delete("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteCollectionSubmittersGroupForbiddenTest() throws Exception { + Group submittersGroup = collectionService.createSubmitters(context, collection); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/submittersGroup")) + .andExpect(status().isForbidden()); + } + + + @Test + public void deleteCollectionSubmittersGroupNotFoundTest() throws Exception { + Group submittersGroup = collectionService.createSubmitters(context, collection); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + UUID.randomUUID() + "/submittersGroup")) + .andExpect(status().isNotFound()); + } + + private Group createGroup(String itemGroupString, int defaultItemRead) throws SQLException, AuthorizeException { + Group role = groupService.create(context); + groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_" + itemGroupString + + "_DEFAULT_READ"); + + // Remove existing privileges from the anonymous group. + authorizeService.removePoliciesActionFilter(context, collection, defaultItemRead); + + // Grant our new role the default privileges. + authorizeService.addPolicy(context, collection, defaultItemRead, role); + groupService.update(context, role); + return role; + } + + @Test + public void getCollectionItemReadGroupTest() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(role.getID(), role.getName()))); + } + + @Test + public void getCollectionDefaultItemReadGroupTestParentCommunityAdmin() throws Exception { + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(role.getID(), role.getName()))); + } + + @Test + public void getCollectionDefaultItemReadGroupTestCollectionAdmin() throws Exception { + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(role.getID(), role.getName()))); + } + + @Test + public void getCollectionDefaultItemReadGroupUnAuthorizedTest() throws Exception { + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + getClient().perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getCollectionDefaultItemReadGroupForbiddenTest() throws Exception { + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isForbidden()); + } + + @Test + public void getCollectionDefaultItemReadGroupAnonymousGroupTest() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + Group anon = groupService.findByName(context, Group.ANONYMOUS); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(anon.getID(), anon.getName()))); + } + + @Test + public void getCollectionDefaultItemReadGroupWrongCollectionUuidResourceNotFoundTest() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + UUID.randomUUID() + "/itemReadGroup")) + .andExpect(status().isNotFound()); + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupSuccess() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group itemReadGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(itemReadGroup.getID(), itemReadGroup.getName()))); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupDcTitleUnprocessable() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + metadataRest.put("dc.title", new MetadataValueRest("testTitle")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupSuccessParentCommunityAdmin() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group itemReadGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(itemReadGroup.getID(), itemReadGroup.getName()))); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupSuccessCollectionAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group itemReadGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(itemReadGroup.getID(), itemReadGroup.getName()))); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupUnAuthorized() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + getClient().perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupForbidden() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupNotFound() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + UUID.randomUUID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupUnProcessableName() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setName("Fail"); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupUnProcessablePermanent() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setPermanent(true); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/itemReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void deleteCollectionDefaultItemReadGroupTest() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isNoContent()); + + Group anon = groupService.findByName(context, Group.ANONYMOUS); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(anon.getID(), anon.getName()))); + + } + + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionDefaultItemReadGroupTestParentCommunityAdmin() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isNoContent()); + } + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionDefaultItemReadGroupTestCollectionAdmin() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteCollectionDefaultItemReadGroupUnAuthorizedTest() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + getClient().perform(delete("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteCollectionDefaultItemReadGroupForbiddenTest() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/itemReadGroup")) + .andExpect(status().isForbidden()); + } + + + @Test + public void deleteCollectionDefaultItemReadGroupNotFoundTest() throws Exception { + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + + Group role = createGroup(itemGroupString, defaultItemRead); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + UUID.randomUUID() + "/itemReadGroup")) + .andExpect(status().isNotFound()); + } + + @Test + public void getCollectionBitstreamReadGroupTest() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(role.getID(), role.getName()))); + } + + @Test + public void getCollectionDefaultBitstreamReadGroupTestParentCommunityAdmin() throws Exception { + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(role.getID(), role.getName()))); + } + + @Test + public void getCollectionDefaultBitstreamReadGroupTestCollectionAdmin() throws Exception { + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(role.getID(), role.getName()))); + } + + @Test + public void getCollectionDefaultBitstreamReadGroupUnAuthorizedTest() throws Exception { + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + getClient().perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getCollectionDefaultBitstreamReadGroupForbiddenTest() throws Exception { + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isForbidden()); + } + + @Test + public void getCollectionDefaultBitstreamReadGroupNoContentTest() throws Exception { + + Group anon = groupService.findByName(context, Group.ANONYMOUS); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(anon.getID(), anon.getName()))); + } + + @Test + public void getCollectionDefaultBitstreamReadGroupWrongCollectionUuidResourceNotFoundTest() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + UUID.randomUUID() + "/bitstreamReadGroup")) + .andExpect(status().isNotFound()); + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupSuccess() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group bitstreamReadGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher + .matchGroupEntry(bitstreamReadGroup.getID(), bitstreamReadGroup.getName()))); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupDcTitleUnprocessable() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + metadataRest.put("dc.title", new MetadataValueRest("testTitle")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupSuccessParentCommunityAdmin() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group bitstreamReadGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher + .matchGroupEntry(bitstreamReadGroup.getID(), bitstreamReadGroup.getName()))); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupSuccessCollectionAdmin() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group bitstreamReadGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher + .matchGroupEntry(bitstreamReadGroup.getID(), bitstreamReadGroup.getName()))); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupUnAuthorized() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + getClient().perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupForbidden() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupNotFound() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + UUID.randomUUID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupUnProcessableName() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setName("Fail"); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupUnProcessablePermanent() + throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setPermanent(true); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void deleteCollectionDefaultBitstreamReadGroupTest() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isNoContent()); + + Group anon = groupService.findByName(context, Group.ANONYMOUS); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(anon.getID(), anon.getName()))); + } + + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionDefaultBitstreamReadGroupTestParentCommunityAdmin() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isNoContent()); + } + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionDefaultBitstreamReadGroupTestCollectionAdmin() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteCollectionDefaultBitstreamReadGroupUnAuthorizedTest() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + getClient().perform(delete("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteCollectionDefaultBitstreamReadGroupForbiddenTest() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) + .andExpect(status().isForbidden()); + } + + + @Test + public void deleteCollectionDefaultBitstreamReadGroupNotFoundTest() throws Exception { + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group role = createGroup(bitstreamGroupString, defaultBitstreamRead); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + UUID.randomUUID() + "/bitstreamReadGroup")) + .andExpect(status().isNotFound()); + } + + @Test + public void getWorkflowGroupForCollectionAndRole() throws Exception { + + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(group.getID(), group.getName()))); + } + + @Test + public void getWorkflowGroupForCollectionAndRoleParentCommunityAdmin() throws Exception { + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(group.getID(), group.getName()))); + } + + @Test + public void getWorkflowGroupForCollectionAndRoleWrongUUIDCollectionNotFound() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + UUID.randomUUID() + "/workflowGroups/reviewer")) + .andExpect(status().isNotFound()); + } + + @Test + public void getWorkflowGroupForCollectionAndRoleWrongRoleNotFound() throws Exception { + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + UUID.randomUUID() + "/workflowGroups/wrongRole")) + .andExpect(status().isNotFound()); + } + + @Test + public void getWorkflowGroupCommunityAdmin() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(group.getID(), group.getName()))); + } + + @Test + public void getWorkflowGroupCollectionAdmin() throws Exception { + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", GroupMatcher.matchGroupEntry(group.getID(), group.getName()))); + } + + @Test + public void getWorkflowGroupUnAuthorized() throws Exception { + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + getClient().perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getWorkflowGroupForbidden() throws Exception { + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isForbidden()); + } + + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupSuccess() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group workflowGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(workflowGroup.getID(), workflowGroup.getName()))); + + } + + @Test + public void postCollectionWorkflowGroupWrongCollectionId() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + UUID.randomUUID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupWrongRole() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/wrongRole") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupDcTitleUnprocessable() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + metadataRest.put("dc.title", new MetadataValueRest("testTitle")); + + groupRest.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupSuccessParentCommunityAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group workflowGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(workflowGroup.getID(), workflowGroup.getName()))); + + } + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupSuccessCollectionAdmin() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + AtomicReference idRef = new AtomicReference<>(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + Group workflowGroup = groupService.find(context, idRef.get()); + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + GroupMatcher.matchGroupEntry(workflowGroup.getID(), workflowGroup.getName()))); + + } + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupUnAuthorized() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + getClient().perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupForbidden() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + } + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupUnProcessableName() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setName("Fail"); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void postCollectionWorkflowGroupCreateWorkflowGroupUnProcessablePermanent() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + groupRest.setPermanent(true); + MetadataRest metadataRest = new MetadataRest(); + metadataRest.put("dc.description", new MetadataValueRest("testingDescription")); + metadataRest.put("dc.subject", new MetadataValueRest("testSubject")); + + groupRest.setMetadata(metadataRest); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void deleteCollectionWorkflowGroupTest() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isNotFound()); + } + + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionWorkflowGroupTestParentCommunityAdmin() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isNoContent()); + } + + // This is currently not supported in DSpace API + @Ignore + @Test + public void deleteCollectionWorkflowGroupTestCollectionAdmin() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteCollectionWorkflowGroupUnAuthorizedTest() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + + getClient().perform(delete("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteCollectionWorkflowGroupForbiddenTest() throws Exception { + + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) + .andExpect(status().isForbidden()); + } + + + @Test + public void deleteCollectionWorkflowGroupNotFoundTest() throws Exception { + Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(delete("/api/core/collections/" + UUID.randomUUID() + "/workflowGroups/reviewer")) + .andExpect(status().isNotFound()); + } + } From 9a03eb8d6d258f1545eddac2bda0c92a1b1d1767 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 23 Mar 2020 10:06:41 +0100 Subject: [PATCH 063/749] 69944: ITs for metadata.hide --- .../rest/converter/DSpaceObjectConverter.java | 15 +- .../app/rest/converter/ItemConverter.java | 35 +++- .../app/rest/BitstreamRestRepositoryIT.java | 172 ++++++++++++++++++ .../app/rest/CollectionRestRepositoryIT.java | 97 ++++++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 138 +++++++++++--- .../app/rest/builder/BitstreamBuilder.java | 7 + .../app/rest/builder/CollectionBuilder.java | 8 + .../dspace/app/rest/builder/ItemBuilder.java | 4 + .../app/rest/matcher/MetadataMatcher.java | 11 ++ .../test/resources/config/local.properties | 6 + 10 files changed, 465 insertions(+), 28 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 4151793474..a2fb7605d9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -69,8 +69,14 @@ public abstract class DSpaceObjectConverter