From 9a41e6fece0ee2d428060cc6dd42a00be44e2a17 Mon Sep 17 00:00:00 2001 From: marsa Date: Wed, 18 Oct 2017 15:48:25 +0200 Subject: [PATCH 001/465] [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/465] [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/465] [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/465] 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/465] 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/465] 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/465] 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/465] 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 46a3407642f5aca0535edbfecb6892cde6623b1d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 9 Jan 2020 13:19:43 +0100 Subject: [PATCH 009/465] 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 010/465] 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 011/465] 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 74274ccafc864a33b7e62b939e584b7f3a0b2674 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 17 Jan 2020 13:33:34 +0100 Subject: [PATCH 012/465] 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 0ee7ec9bef0c32cd4aedd16b20a0f9098e5e0e38 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 31 Jan 2020 12:16:07 +0100 Subject: [PATCH 013/465] 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 014/465] 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 015/465] 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 016/465] 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 017/465] 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 018/465] 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 019/465] 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 020/465] 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 7e099a9b29f0ee9cf4e9fe4b918be4163f91d7ff Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 11 Feb 2020 13:03:37 +0100 Subject: [PATCH 021/465] 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 022/465] 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 023/465] 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 024/465] 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 025/465] 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 026/465] 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 027/465] 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 028/465] 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 029/465] 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 030/465] 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 031/465] 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 032/465] 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 033/465] 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 dc42d3e9b4b0c6d95ca33383a27346692c825d7c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 12 Mar 2020 11:37:03 +0100 Subject: [PATCH 034/465] 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 d4cc5f4587f12c7bbcce4b6dc982bf49ea2b9eb4 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Tue, 17 Mar 2020 16:41:10 +0100 Subject: [PATCH 035/465] 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 036/465] 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 5d714a174ee9c36e82baa2249df85b46357a0c08 Mon Sep 17 00:00:00 2001 From: nicholas Date: Wed, 1 Apr 2020 09:22:29 -0500 Subject: [PATCH 037/465] include limit and offset when retrieving items for a collection --- .../org/dspace/rest/CollectionsResource.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java index af06792b7b..66919ad5c7 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java @@ -274,16 +274,16 @@ public class CollectionsResource extends Resource { headers, request, context); items = new ArrayList(); - Iterator dspaceItems = itemService.findByCollection(context, dspaceCollection); - for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++) { + Iterator dspaceItems = itemService.findByCollection(context, dspaceCollection, + limit, offset); + + while (dspaceItems.hasNext()) { org.dspace.content.Item dspaceItem = dspaceItems.next(); - if (i >= offset) { - if (itemService.isItemListedForUser(context, dspaceItem)) { - items.add(new Item(dspaceItem, servletContext, expand, context)); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - } + if (itemService.isItemListedForUser(context, dspaceItem)) { + items.add(new Item(dspaceItem, servletContext, expand, context)); + writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, + headers, request, context); } } From 63c14b3ccb627721b5de7500cc1dd69bf5ba1f76 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 9 Apr 2020 16:15:12 +0200 Subject: [PATCH 038/465] [Task 70333] removed the RelationshipTypeId from Relationship endpoints --- .../java/org/dspace/app/rest/model/RelationshipRest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java index 16fcbbd0bf..e1aeb3ff6f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java @@ -27,7 +27,6 @@ public class RelationshipRest extends BaseObjectRest { @JsonIgnore private UUID rightId; - private int relationshipTypeId; private RelationshipTypeRest relationshipType; private int leftPlace; private int rightPlace; @@ -90,14 +89,6 @@ public class RelationshipRest extends BaseObjectRest { this.rightPlace = rightPlace; } - public int getRelationshipTypeId() { - return relationshipTypeId; - } - - public void setRelationshipTypeId(int relationshipTypeId) { - this.relationshipTypeId = relationshipTypeId; - } - public String getRightwardValue() { return rightwardValue; } From 44d3d2bcc3c39c57cea29ed1ae9b158e0769f2be Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 14 Apr 2020 11:58:50 +0200 Subject: [PATCH 039/465] [Task 70273] added POST endpoint for new registration creation and IT --- .../dspace/eperson/AccountServiceImpl.java | 6 ++ .../app/rest/RegistrationRestController.java | 86 +++++++++++++++++++ .../app/rest/model/RegistrationRest.java | 72 ++++++++++++++++ .../rest/RegistrationRestControllerIT.java | 75 ++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index e00a9568e3..9282fc116e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -22,6 +22,7 @@ import org.dspace.core.Utils; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.RegistrationDataService; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -47,6 +48,8 @@ public class AccountServiceImpl implements AccountService { protected EPersonService ePersonService; @Autowired(required = true) protected RegistrationDataService registrationDataService; + @Autowired + private ConfigurationService configurationService; protected AccountServiceImpl() { @@ -67,6 +70,9 @@ public class AccountServiceImpl implements AccountService { public void sendRegistrationInfo(Context context, String email) throws SQLException, IOException, MessagingException, AuthorizeException { + if (!configurationService.getBooleanProperty("user.registration", true)) { + throw new IllegalStateException("The user.registration parameter was set to false"); + } sendInfo(context, email, true, true); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java new file mode 100644 index 0000000000..f11bed240f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java @@ -0,0 +1,86 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.io.IOException; +import java.sql.SQLException; +import javax.mail.MessagingException; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.RegistrationRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.service.AccountService; +import org.dspace.eperson.service.EPersonService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/" + RegistrationRest.CATEGORY + "/" + RegistrationRest.NAME_PLURAL) +public class RegistrationRestController { + +// @Autowired +// private AuthorizationFeatureService authorizationFeatureService; +// +// @Autowired +// private SiteService siteService; +// +// @Autowired +// private ConverterService converterService; + + @Autowired + private AccountService accountService; + + @Autowired + private EPersonService ePersonService; + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity register(HttpServletRequest request, HttpServletResponse response) + throws SQLException, IOException, MessagingException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); +// AuthorizationFeature epersonRegistration = authorizationFeatureService.find("epersonRegistration"); +// Site site = siteService.findSite(context); +// SiteRest siteRest = converterService.toRest(site, Projection.DEFAULT); +// if (!authorizationFeatureService.isAuthorized(context, epersonRegistration, siteRest)) { +// throw new AccessDeniedException("Registration is disabled, you are not authorized to create +// a new Authorization"); +// } + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest; + try { + ServletInputStream input = request.getInputStream(); + registrationRest = mapper.readValue(input, RegistrationRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body.", e1); + } + if (StringUtils.isBlank(registrationRest.getEmail())) { + throw new UnprocessableEntityException("The email cannot be omitted from the Registration endpoint"); + } + if (ePersonService.findByEmail(context, registrationRest.getEmail()) != null) { + accountService.sendForgotPasswordInfo(context, registrationRest.getEmail()); + } else { + accountService.sendRegistrationInfo(context, registrationRest.getEmail()); + } + context.complete(); + return ControllerUtils.toEmptyResponse(HttpStatus.CREATED); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java new file mode 100644 index 0000000000..0d7f2e30bd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -0,0 +1,72 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RegistrationRestController; + + +public class RegistrationRest extends BaseObjectRest { + + public static final String NAME = "registration"; + public static final String NAME_PLURAL = "registrations"; + public static final String CATEGORY = EPERSON; + + private String email; + private UUID user; + + /** + * Generic getter for the email + * @return the email value of this RegisterRest + */ + public String getEmail() { + return email; + } + + /** + * Generic setter for the email + * @param email The email to be set on this RegisterRest + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Generic getter for the user + * @return the user value of this RegisterRest + */ + public UUID getUser() { + return user; + } + + /** + * Generic setter for the user + * @param user The user to be set on this RegisterRest + */ + public void setUser(UUID user) { + this.user = user; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RegistrationRestController.class; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java new file mode 100644 index 0000000000..16135d2c68 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -0,0 +1,75 @@ +/** + * 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.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.RegistrationRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.eperson.RegistrationData; +import org.dspace.eperson.dao.RegistrationDataDAO; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class RegistrationRestControllerIT extends AbstractControllerIntegrationTest { + + @Autowired + private RegistrationDataDAO registrationDataDAO; + + @Autowired + private ConfigurationService configurationService; + + @Test + public void registrationFlowTest() throws Exception { + List registrationData = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationData.isEmpty()); + + String token = getAuthToken(eperson.getEmail(), password); + String t = ";;"; + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationData = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationData.size() == 1); + assertTrue(StringUtils.equalsIgnoreCase(registrationData.get(0).getEmail(), eperson.getEmail())); + + String newEmail = "newEPersonTest@gmail.com"; + registrationRest.setEmail(newEmail); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationData = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationData.size() == 2); + assertTrue(StringUtils.equalsIgnoreCase(registrationData.get(0).getEmail(), newEmail) || + StringUtils.equalsIgnoreCase(registrationData.get(1).getEmail(), newEmail)); + configurationService.setProperty("user.registration", false); + + newEmail = "newEPersonTestTwo@gmail.com"; + registrationRest.setEmail(newEmail); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().is(500)); + + assertTrue(registrationData.size() == 2); + assertTrue(!StringUtils.equalsIgnoreCase(registrationData.get(0).getEmail(), newEmail) && + !StringUtils.equalsIgnoreCase(registrationData.get(1).getEmail(), newEmail)); + } +} From f07c3824412919b1760ee50acb966e8e3d4b037a Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 15 Apr 2020 10:31:29 +0200 Subject: [PATCH 040/465] [Task 70382] added the search findByToken method for Registrations and added ITs --- .../app/rest/model/RegistrationRest.java | 2 +- .../model/hateoas/RegistrationResource.java | 18 ++++ .../RegistrationRestRepository.java | 68 ++++++++++++++ .../rest/RegistrationRestControllerIT.java | 33 ++++--- .../rest/RegistrationRestRepositoryIT.java | 90 +++++++++++++++++++ .../app/rest/matcher/RegistrationMatcher.java | 29 ++++++ 6 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java index 0d7f2e30bd..b84699f140 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RegistrationRestController; -public class RegistrationRest extends BaseObjectRest { +public class RegistrationRest extends RestAddressableModel { public static final String NAME = "registration"; public static final String NAME_PLURAL = "registrations"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java new file mode 100644 index 0000000000..0d92b0d41e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java @@ -0,0 +1,18 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.RegistrationRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; + +@RelNameDSpaceResource(RegistrationRest.NAME) +public class RegistrationResource extends HALResource { + public RegistrationResource(RegistrationRest content) { + super(content); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java new file mode 100644 index 0000000000..f9ff623163 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -0,0 +1,68 @@ +/** + * 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 org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.RegistrationRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.RegistrationData; +import org.dspace.eperson.service.AccountService; +import org.dspace.eperson.service.RegistrationDataService; +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.stereotype.Component; + +@Component(RegistrationRest.CATEGORY + "." + RegistrationRest.NAME) +public class RegistrationRestRepository extends DSpaceRestRepository { + + @Autowired + private AccountService accountService; + + @Autowired + private RegistrationDataService registrationDataService; + + @Override + public RegistrationRest findOne(Context context, Integer integer) { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); + } + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); + } + + @Override + public Class getDomainClass() { + return RegistrationRest.class; + } + + @SearchRestMethod(name = "findByToken") + public RegistrationRest findByToken(@Parameter(value = "token", required = true) String token) + throws SQLException, AuthorizeException { + Context context = obtainContext(); + RegistrationData registrationData = registrationDataService.findByToken(context, token); + if (registrationData == null) { + throw new ResourceNotFoundException("The token: " + token + " couldn't be found"); + } + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(registrationData.getEmail()); + EPerson ePerson = accountService.getEPerson(context, token); + if (ePerson != null) { + registrationRest.setUser(ePerson.getID()); + } + return registrationRest; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java index 16135d2c68..26abcb3075 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Iterator; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,11 +34,9 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT @Test public void registrationFlowTest() throws Exception { - List registrationData = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationData.isEmpty()); + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationDataList.isEmpty()); - String token = getAuthToken(eperson.getEmail(), password); - String t = ";;"; ObjectMapper mapper = new ObjectMapper(); RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(eperson.getEmail()); @@ -45,9 +44,9 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); - registrationData = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationData.size() == 1); - assertTrue(StringUtils.equalsIgnoreCase(registrationData.get(0).getEmail(), eperson.getEmail())); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationDataList.size() == 1); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); String newEmail = "newEPersonTest@gmail.com"; registrationRest.setEmail(newEmail); @@ -55,10 +54,10 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); - registrationData = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationData.size() == 2); - assertTrue(StringUtils.equalsIgnoreCase(registrationData.get(0).getEmail(), newEmail) || - StringUtils.equalsIgnoreCase(registrationData.get(1).getEmail(), newEmail)); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationDataList.size() == 2); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) || + StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); configurationService.setProperty("user.registration", false); newEmail = "newEPersonTestTwo@gmail.com"; @@ -68,8 +67,14 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT .contentType(contentType)) .andExpect(status().is(500)); - assertTrue(registrationData.size() == 2); - assertTrue(!StringUtils.equalsIgnoreCase(registrationData.get(0).getEmail(), newEmail) && - !StringUtils.equalsIgnoreCase(registrationData.get(1).getEmail(), newEmail)); + assertTrue(registrationDataList.size() == 2); + assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && + !StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); + + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java new file mode 100644 index 0000000000..88feebb286 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java @@ -0,0 +1,90 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.matcher.RegistrationMatcher; +import org.dspace.app.rest.model.RegistrationRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.eperson.RegistrationData; +import org.dspace.eperson.dao.RegistrationDataDAO; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private RegistrationDataDAO registrationDataDAO; + + @Test + public void findByTokenTestExistingUserTest() throws Exception { + String email = eperson.getEmail(); + createTokenForEmail(email); + RegistrationData registrationData = registrationDataDAO.findByEmail(context, email); + + getClient().perform(get("/api/eperson/registrations/search/findByToken") + .param("token", registrationData.getToken())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, eperson.getID())))); + + email = "newUser@testnewuser.com"; + createTokenForEmail(email); + registrationData = registrationDataDAO.findByEmail(context, email); + + getClient().perform(get("/api/eperson/registrations/search/findByToken") + .param("token", registrationData.getToken())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + + registrationDataDAO.delete(context, registrationData); + + } + + @Test + public void findByTokenTestNewUserTest() throws Exception { + String email = "newUser@testnewuser.com"; + createTokenForEmail(email); + RegistrationData registrationData = registrationDataDAO.findByEmail(context, email); + + getClient().perform(get("/api/eperson/registrations/search/findByToken") + .param("token", registrationData.getToken())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + + registrationDataDAO.delete(context, registrationData); + } + + @Test + public void findByTokenNotExistingTokenTest() throws Exception { + getClient().perform(get("/api/eperson/registration/search/findByToken") + .param("token", "ThisTokenDoesNotExist")) + .andExpect(status().isNotFound()); + } + + private void createTokenForEmail(String email) throws Exception { + List registrationDatas; + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(email); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java new file mode 100644 index 0000000000..a154091a2e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import java.util.UUID; + +import org.hamcrest.Matcher; + +public class RegistrationMatcher { + + private RegistrationMatcher(){} + + public static Matcher matchRegistration(String email, UUID epersonUuid) { + return allOf( + hasJsonPath("$.email", is(email)), + hasJsonPath("$.user", is(epersonUuid == null ? null : String.valueOf(epersonUuid))) + ); + + } +} From 7da7ff03786802a4c707e2fb68e5c99079fa2bae Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 15 Apr 2020 12:57:51 +0200 Subject: [PATCH 041/465] [Task 70397] added EPersonRegistrationFeature and added ITs --- .../app/rest/RegistrationRestController.java | 38 ++++-- .../impl/EPersonRegistrationFeature.java | 55 ++++++++ .../rest/RegistrationRestControllerIT.java | 2 +- .../EPersonRegistrationFeatureIT.java | 128 ++++++++++++++++++ 4 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java index f11bed240f..b03eda36f0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java @@ -16,10 +16,17 @@ import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureService; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RegistrationRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; import org.dspace.core.Context; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; @@ -28,6 +35,7 @@ import org.springframework.data.rest.webmvc.ControllerUtils; import org.springframework.hateoas.ResourceSupport; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -36,14 +44,14 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/" + RegistrationRest.CATEGORY + "/" + RegistrationRest.NAME_PLURAL) public class RegistrationRestController { -// @Autowired -// private AuthorizationFeatureService authorizationFeatureService; -// -// @Autowired -// private SiteService siteService; -// -// @Autowired -// private ConverterService converterService; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + @Autowired + private SiteService siteService; + + @Autowired + private ConverterService converterService; @Autowired private AccountService accountService; @@ -56,13 +64,13 @@ public class RegistrationRestController { throws SQLException, IOException, MessagingException, AuthorizeException { Context context = ContextUtil.obtainContext(request); -// AuthorizationFeature epersonRegistration = authorizationFeatureService.find("epersonRegistration"); -// Site site = siteService.findSite(context); -// SiteRest siteRest = converterService.toRest(site, Projection.DEFAULT); -// if (!authorizationFeatureService.isAuthorized(context, epersonRegistration, siteRest)) { -// throw new AccessDeniedException("Registration is disabled, you are not authorized to create -// a new Authorization"); -// } + AuthorizationFeature epersonRegistration = authorizationFeatureService.find("epersonRegistration"); + Site site = siteService.findSite(context); + SiteRest siteRest = converterService.toRest(site, Projection.DEFAULT); + if (!authorizationFeatureService.isAuthorized(context, epersonRegistration, siteRest)) { + throw new AccessDeniedException( + "Registration is disabled, you are not authorized to create a new Authorization"); + } ObjectMapper mapper = new ObjectMapper(); RegistrationRest registrationRest; try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java new file mode 100644 index 0000000000..44de9ce6d7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java @@ -0,0 +1,55 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; + +import java.sql.SQLException; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.authenticate.service.AuthenticationService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.services.RequestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@AuthorizationFeatureDocumentation(name = EPersonRegistrationFeature.NAME, + description = "It can be used to register an eperson") +public class EPersonRegistrationFeature implements AuthorizationFeature { + + public static final String NAME = "epersonRegistration"; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private AuthenticationService authenticationService; + + @Autowired + private RequestService requestService; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (!(object instanceof SiteRest)) { + return false; + } + if (configurationService.getBooleanProperty("user.registration", true)) { + return authenticationService + .allowSetPassword(context, requestService.getCurrentRequest().getHttpServletRequest(), null); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[] {SiteRest.CATEGORY + "." + SiteRest.NAME}; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java index 26abcb3075..0201eb284f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -65,7 +65,7 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT getClient().perform(post("/api/eperson/registrations") .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) - .andExpect(status().is(500)); + .andExpect(status().is(401)); assertTrue(registrationDataList.size() == 2); assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java new file mode 100644 index 0000000000..a9bbdce3db --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java @@ -0,0 +1,128 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.authorization.impl.EPersonRegistrationFeature; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationTest { + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + @Autowired + private ConverterService converterService; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private SiteService siteService; + + @Autowired + private SiteConverter siteConverter; + + @Autowired + private Utils utils; + + private AuthorizationFeature epersonRegistrationFeature; + + public static final String[] SHIB_ONLY = {"org.dspace.authenticate.ShibAuthentication"}; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + epersonRegistrationFeature = authorizationFeatureService.find(EPersonRegistrationFeature.NAME); + } + + @Test + public void userRegistrationEnabledSuccessTest() throws Exception { + + Site site = siteService.findSite(context); + SiteRest SiteRest = siteConverter.convert(site, Projection.DEFAULT); + String siteUri = utils.linkToSingleResource(SiteRest, "self").getHref(); + + // access the authorization for the admin user + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + .param("uri", siteUri) + .param("eperson", admin.getID().toString()) + .param("feature", epersonRegistrationFeature.getName())) + .andExpect(status().isOk()); + } + + @Test + public void userRegistrationDisabledUnAuthorizedTest() throws Exception { + + Site site = siteService.findSite(context); + SiteRest SiteRest = siteConverter.convert(site, Projection.DEFAULT); + String siteUri = utils.linkToSingleResource(SiteRest, "self").getHref(); + + context.turnOffAuthorisationSystem(); + configurationService.setProperty("user.registration", false); + context.restoreAuthSystemState(); + // access the authorization for the admin user + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + .param("uri", siteUri) + .param("eperson", admin.getID().toString()) + .param("feature", epersonRegistrationFeature.getName())) + .andExpect(status().isNoContent()); + + context.turnOffAuthorisationSystem(); + configurationService.setProperty("user.registration", true); + context.restoreAuthSystemState(); + } + + + @Test + public void userRegistrationEnabledShibTest() throws Exception { + + Site site = siteService.findSite(context); + SiteRest SiteRest = siteConverter.convert(site, Projection.DEFAULT); + String siteUri = utils.linkToSingleResource(SiteRest, "self").getHref(); + + // access the authorization for the admin user + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + .param("uri", siteUri) + .param("eperson", admin.getID().toString()) + .param("feature", epersonRegistrationFeature.getName())) + .andExpect(status().isOk()); + + context.turnOffAuthorisationSystem(); + //Enable Shibboleth and password login + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY); + context.restoreAuthSystemState(); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + .param("uri", siteUri) + .param("eperson", admin.getID().toString()) + .param("feature", epersonRegistrationFeature.getName())) + .andExpect(status().isNoContent()); + + } +} From fee24e3f96dba9f2a21b97aabae29485915a3fca Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 17 Apr 2020 14:14:25 +0200 Subject: [PATCH 042/465] [Task 70398] implementing change password with token patch and added ITs for the functionality --- .../repository/EPersonRestRepository.java | 14 ++ .../EPersonPasswordReplaceOperation.java | 39 ++++ .../app/rest/EPersonRestRepositoryIT.java | 213 ++++++++++++++++++ 3 files changed, 266 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 073d1b25bd..aa2526c0fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -19,6 +19,7 @@ import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -28,6 +29,7 @@ import org.dspace.eperson.service.EPersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -167,6 +169,18 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository extends PatchOperation { + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(EPersonPasswordReplaceOperation.class); + /** * Path in json body of patch that uses this operation */ public static final String OPERATION_PASSWORD_CHANGE = "/password"; protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + @Autowired + private RequestService requestService; + + @Autowired + private AccountService accountService; + @Override public R perform(Context context, R object, Operation operation) { checkOperationValue(operation.getValue()); if (supports(object, operation)) { EPerson eperson = (EPerson) object; + String token = requestService.getCurrentRequest().getHttpServletRequest().getParameter("token"); checkModelForExistingValue(eperson); + if (StringUtils.isNotBlank(token)) { + patchWithToken(context,eperson, token, operation); + } ePersonService.setPassword(eperson, (String) operation.getValue()); return object; } else { @@ -46,6 +68,23 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { } } + private void patchWithToken(Context context, EPerson eperson, String token, Operation operation) { + try { + EPerson ePersonFromToken = accountService.getEPerson(context, token); + if (ePersonFromToken == null) { + throw new AccessDeniedException("The token in the parameter: " + token + " couldn't" + + " be associated with an EPerson"); + } + if (!ePersonFromToken.getID().equals(eperson.getID())) { + throw new AccessDeniedException("The token in the parameter belongs to a different EPerson" + + " than the uri indicates"); + } + accountService.deleteToken(context, token); + } catch (SQLException | AuthorizeException e) { + log.error(e.getMessage(), e); + } + } + /** * Checks whether the ePerson has a password via the ePersonService to checking if it has a non null password hash * throws a DSpaceBadRequestException if not pw hash was present diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 9610287273..7b6513f766 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -14,6 +14,8 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; @@ -28,6 +30,7 @@ import java.util.UUID; import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.EPersonBuilder; @@ -39,6 +42,7 @@ import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -47,12 +51,25 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.PasswordHash; +import org.dspace.eperson.service.AccountService; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.RegistrationDataService; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private AccountService accountService; + + @Autowired + private RegistrationDataService registrationDataService; + + @Autowired + private EPersonService ePersonService; @Test public void createTest() throws Exception { @@ -1590,4 +1607,200 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { ); } + + @Test + public void patchReplacePasswordWithToken() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + String newPassword = "newpassword"; + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/password", newPassword); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + accountService.sendRegistrationInfo(context, ePerson.getEmail()); + String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); + String token = getAuthToken(admin.getEmail(), password); + PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + .param("token", tokenForEPerson)) + .andExpect(status().isOk()); + + PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); + assertFalse(oldPassword.equals(newPasswordHash)); + assertTrue(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + } + + + @Test + public void patchReplacePasswordWithRandomTokenPatchFail() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + String newPassword = "newpassword"; + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/password", newPassword); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + accountService.sendRegistrationInfo(context, ePerson.getEmail()); + String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); + String token = getAuthToken(admin.getEmail(), password); + PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + .param("token", "RandomToken")) + .andExpect(status().isForbidden()); + + PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); + assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); + assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + assertTrue(StringUtils.equals(registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(), + tokenForEPerson)); + } + + @Test + public void patchReplacePasswordWithOtherUserTokenFail() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + + EPerson ePersonTwo = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Smith", "Donald") + .withEmail("donaldSmith@fake-email.com") + .withPassword(password) + .build(); + + String newPassword = "newpassword"; + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/password", newPassword); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + accountService.sendRegistrationInfo(context, ePerson.getEmail()); + accountService.sendRegistrationInfo(context, ePersonTwo.getEmail()); + String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); + String tokenForEPersonTwo = registrationDataService.findByEmail(context, ePersonTwo.getEmail()).getToken(); + + String token = getAuthToken(admin.getEmail(), password); + PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + .param("token", tokenForEPersonTwo)) + .andExpect(status().isForbidden()); + + PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); + assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); + assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + } + + @Test + public void patchReplaceEmailWithTokenFail() throws Exception { + context.turnOffAuthorisationSystem(); + + String originalEmail = "Johndoe@fake-email.com"; + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail(originalEmail) + .withPassword(password) + .build(); + + String newEmail = "johnyandmaria@fake-email.com"; + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/email", newEmail); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + accountService.sendRegistrationInfo(context, ePerson.getEmail()); + String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); + String token = getAuthToken(admin.getEmail(), password); + PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + .param("token", tokenForEPerson)) + .andExpect(status().isForbidden()); + + PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); + assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); + assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + assertTrue(StringUtils.equalsIgnoreCase(ePerson.getEmail(), originalEmail)); + } + + @Test + public void registerNewAccountPatchUpdatePasswordRandomUserUuidFail() throws Exception { + context.turnOffAuthorisationSystem(); + + ObjectMapper mapper = new ObjectMapper(); + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + String newPassword = "newpassword"; + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/password", newPassword); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + accountService.sendRegistrationInfo(context, ePerson.getEmail()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + String token = getAuthToken(admin.getEmail(), password); + PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + .param("token", newRegisterToken)) + .andExpect(status().isForbidden()); + + PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); + assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); + assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + assertFalse(registrationDataService.findByEmail(context, newRegisterEmail) == null); + } + } From cb8b5c8efc3baaa535b1545f31313c8a3364e02f Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 20 Apr 2020 11:31:34 +0200 Subject: [PATCH 043/465] [Task 70399] created the POST EPerson endpoint with token and added tests --- .../dspace/app/rest/model/EPersonRest.java | 6 +- .../repository/EPersonRestRepository.java | 95 ++++- .../app/rest/EPersonRestRepositoryIT.java | 365 ++++++++++++++++++ 3 files changed, 462 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 00881b9fd1..7b4c683322 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -41,7 +41,7 @@ public class EPersonRest extends DSpaceObjectRest { private boolean requireCertificate = false; - private boolean selfRegistered = false; + private Boolean selfRegistered; @JsonProperty(access = Access.WRITE_ONLY) private String password; @@ -92,11 +92,11 @@ public class EPersonRest extends DSpaceObjectRest { this.requireCertificate = requireCertificate; } - public boolean isSelfRegistered() { + public Boolean isSelfRegistered() { return selfRegistered; } - public void setSelfRegistered(boolean selfRegistered) { + public void setSelfRegistered(Boolean selfRegistered) { this.selfRegistered = selfRegistered; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index aa2526c0fe..1309e11713 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -15,17 +15,29 @@ import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.eperson.RegistrationData; +import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.RegistrationDataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -43,9 +55,23 @@ import org.springframework.stereotype.Component; @Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME) public class EPersonRestRepository extends DSpaceObjectRestRepository { + private static final Logger log = Logger.getLogger(EPersonRestRepository.class); + @Autowired AuthorizeService authorizeService; + @Autowired + private AccountService accountService; + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + @Autowired + private SiteService siteService; + + @Autowired + private RegistrationDataService registrationDataService; + private final EPersonService es; @@ -66,7 +92,21 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository epersonFirstName = metadataRest.getMap().get("eperson.firstname"); + List epersonLastName = metadataRest.getMap().get("eperson.lastname"); + if (epersonFirstName == null || epersonLastName == null || + epersonFirstName.isEmpty() || epersonLastName.isEmpty()) { + throw new AccessDeniedException("The eperson.firstname and eperson.lastname values need to be " + + "filled in"); + } + } + String password = epersonRest.getPassword(); + if (StringUtils.isBlank(password)) { + throw new AccessDeniedException("the password cannot be left blank"); + } } @Override diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 7b6513f766..5a1d47680c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -15,6 +15,8 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -26,6 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.ws.rs.core.MediaType; @@ -58,6 +61,7 @@ import org.dspace.eperson.service.RegistrationDataService; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MvcResult; public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -1803,4 +1807,365 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertFalse(registrationDataService.findByEmail(context, newRegisterEmail) == null); } + @Test + public void postEPersonWithTokenWithoutEmailProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"password\":\"somePassword\"," + + "\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))).andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String epersonUuid = String.valueOf(map.get("uuid")); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + context.turnOffAuthorisationSystem(); + ePersonService.delete(context, createdEPerson); + context.restoreAuthSystemState(); + } + + @Test + public void postEPersonWithTokenWithEmailProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.email", is(newRegisterEmail)), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))).andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String epersonUuid = String.valueOf(map.get("uuid")); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + ePersonService.delete(context, createdEPerson); + context.restoreAuthSystemState(); + } + + @Test + public void postEPersonWithTokenWithEmailAndSelfRegisteredProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"email\":\"" + newRegisterEmail + + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.email", is(newRegisterEmail)), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))).andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String epersonUuid = String.valueOf(map.get("uuid")); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + ePersonService.delete(context, createdEPerson); + context.restoreAuthSystemState(); + } + + @Test + public void postEPersonWithTokenWithTwoTokensDifferentEmailProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String newRegisterEmailTwo = "new-register-two@fake-email.com"; + RegistrationRest registrationRestTwo = new RegistrationRest(); + registrationRestTwo.setEmail(newRegisterEmailTwo); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRestTwo))) + .andExpect(status().isCreated()); + String newRegisterTokenTwo = registrationDataService.findByEmail(context, newRegisterEmailTwo).getToken(); + + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmailTwo + + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmailTwo); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + assertNotNull(registrationDataService.findByToken(context, newRegisterTokenTwo)); + } + + @Test + public void postEPersonWithRandomTokenWithEmailProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", "randomToken") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } + + @Test + public void postEPersonWithTokenWithEmailAndSelfRegisteredFalseProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":false,\"email\":\"" + newRegisterEmail + + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } + + @Test + public void postEPersonWithTokenWithoutLastNameProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]},\"selfRegistered\":true," + + "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } + + @Test + public void postEPersonWithTokenWithoutFirstNameProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true," + + "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } + + @Test + public void postEPersonWithTokenWithoutPasswordProperty() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String newRegisterEmail = "new-register@fake-email.com"; + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(newRegisterEmail); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]}," + + "\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } + + @Test + public void postEPersonWithWrongToken() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + String newEmail = "new-email@fake-email.com"; + + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(registrationRest))) + .andExpect(status().isCreated()); + String forgotPasswordToken = registrationDataService.findByEmail(context, eperson.getEmail()).getToken(); + + String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"password\":\"somePassword\"," + + "\"type\":\"eperson\"}"; + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/eperson/epersons") + .param("token", forgotPasswordToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + + EPerson createdEPerson = ePersonService.findByEmail(context, newEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, forgotPasswordToken)); + } } From 94d9f623587bd0f48fb64ee8c62706dfd8031ac1 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 27 Apr 2020 14:52:00 +0200 Subject: [PATCH 044/465] [Task 70399] added context authorizations modifications for eperson post with token and added its to verify functionality --- .../repository/EPersonRestRepository.java | 2 + .../app/rest/EPersonRestRepositoryIT.java | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 1309e11713..fa69d15253 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -159,7 +159,9 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository map = mapper.readValue(content, Map.class); + String epersonUuid = String.valueOf(map.get("uuid")); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + ePersonService.delete(context, createdEPerson); + context.restoreAuthSystemState(); + } } From b59aeecf0e09f5d215fd8b583d27659bbe66a80a Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 29 Apr 2020 14:03:03 +0200 Subject: [PATCH 045/465] Added extra cleanup to the EPersonRestRepositoryIT and optimized test in RegistrationRestControllerIT --- .../app/rest/EPersonRestRepositoryIT.java | 76 +++++++++++++++++++ .../rest/RegistrationRestControllerIT.java | 7 +- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 53a054e2f0..43a790b8af 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -14,6 +14,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -55,6 +56,8 @@ import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.PasswordHash; +import org.dspace.eperson.RegistrationData; +import org.dspace.eperson.dao.RegistrationDataDAO; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.RegistrationDataService; @@ -75,6 +78,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private EPersonService ePersonService; + @Autowired + private RegistrationDataDAO registrationDataDAO; + @Test public void createTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1672,6 +1678,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); assertFalse(oldPassword.equals(newPasswordHash)); assertTrue(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, tokenForEPerson); + context.restoreAuthSystemState(); } @@ -1709,6 +1719,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); assertTrue(StringUtils.equals(registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(), tokenForEPerson)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, tokenForEPerson); + context.restoreAuthSystemState(); } @Test @@ -1753,6 +1767,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, tokenForEPerson); + registrationDataService.deleteByToken(context, tokenForEPersonTwo); + context.restoreAuthSystemState(); } @Test @@ -1789,6 +1808,12 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); assertTrue(StringUtils.equalsIgnoreCase(ePerson.getEmail(), originalEmail)); + + context.turnOffAuthorisationSystem(); + registrationDataService.delete(context, registrationDataService.findByEmail(context, ePerson.getEmail())); + registrationDataService.deleteByToken(context, tokenForEPerson); + context.restoreAuthSystemState(); + } @Test @@ -1833,6 +1858,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); assertFalse(registrationDataService.findByEmail(context, newRegisterEmail) == null); + + context.turnOffAuthorisationSystem(); + registrationDataService.delete(context, registrationDataService.findByEmail(context, ePerson.getEmail())); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); } @Test @@ -1880,6 +1910,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); ePersonService.delete(context, createdEPerson); context.restoreAuthSystemState(); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); } @Test @@ -1928,6 +1962,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); ePersonService.delete(context, createdEPerson); context.restoreAuthSystemState(); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } @Test @@ -1976,6 +2015,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); ePersonService.delete(context, createdEPerson); context.restoreAuthSystemState(); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } @Test @@ -2017,6 +2061,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); assertNotNull(registrationDataService.findByToken(context, newRegisterTokenTwo)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + registrationDataService.deleteByToken(context, newRegisterTokenTwo); + context.restoreAuthSystemState(); } @Test @@ -2047,6 +2096,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); } @Test @@ -2077,6 +2130,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); } @Test @@ -2106,6 +2163,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); } @Test @@ -2135,6 +2196,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); } @Test @@ -2165,6 +2230,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } @Test @@ -2195,6 +2265,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.findByEmail(context, newEmail); assertNull(createdEPerson); assertNotNull(registrationDataService.findByToken(context, forgotPasswordToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, forgotPasswordToken); + context.restoreAuthSystemState(); + } @Test @@ -2241,6 +2316,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); ePersonService.delete(context, createdEPerson); + registrationDataService.deleteByToken(context, newRegisterToken); context.restoreAuthSystemState(); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java index 0201eb284f..d3147d4611 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -35,7 +36,7 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT @Test public void registrationFlowTest() throws Exception { List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationDataList.isEmpty()); + assertEquals(0, registrationDataList.size()); ObjectMapper mapper = new ObjectMapper(); RegistrationRest registrationRest = new RegistrationRest(); @@ -45,7 +46,7 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT .contentType(contentType)) .andExpect(status().isCreated()); registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationDataList.size() == 1); + assertEquals(1, registrationDataList.size()); assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); String newEmail = "newEPersonTest@gmail.com"; @@ -67,7 +68,7 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT .contentType(contentType)) .andExpect(status().is(401)); - assertTrue(registrationDataList.size() == 2); + assertEquals(2, registrationDataList.size()); assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && !StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); From fc91b0b858a47519cb5e9aaab6738a259c1dab05 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 5 May 2020 11:59:05 +0200 Subject: [PATCH 046/465] Fixed checkstyle in the EPersonRestRepositoryIT class --- .../test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 43a790b8af..8f48ac4a90 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -14,7 +14,6 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -56,7 +55,6 @@ import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.PasswordHash; -import org.dspace.eperson.RegistrationData; import org.dspace.eperson.dao.RegistrationDataDAO; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; From 3dc9f511d4a8797079a8d08fa84b93a949401127 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 8 May 2020 15:43:18 +0200 Subject: [PATCH 047/465] [Task 70808] changed the link the mail to include the token in the url instead of as a parameter --- .../src/main/java/org/dspace/eperson/AccountServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index 9282fc116e..2121cd669d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -239,8 +239,8 @@ public class AccountServiceImpl implements AccountService { // Note change from "key=" to "token=" String specialLink = new StringBuffer().append(base).append( base.endsWith("/") ? "" : "/").append( - isRegister ? "register" : "forgot").append("?") - .append("token=").append(rd.getToken()) + isRegister ? "register" : "forgot").append("/") + .append(rd.getToken()) .toString(); Locale locale = context.getCurrentLocale(); Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, isRegister ? "register" From b18b52a621f9b75d92417c4a33730e56343a19b4 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 11 May 2020 08:59:47 +0200 Subject: [PATCH 048/465] [Task 70808] added javadocs --- .../app/rest/RegistrationRestController.java | 19 +++++++++++++++++++ .../impl/EPersonRegistrationFeature.java | 4 ++++ .../app/rest/model/RegistrationRest.java | 5 +++++ .../model/hateoas/RegistrationResource.java | 4 ++++ .../repository/EPersonRestRepository.java | 4 ++++ .../RegistrationRestRepository.java | 10 ++++++++++ 6 files changed, 46 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java index b03eda36f0..f24b723e36 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java @@ -40,6 +40,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +/** + * This will be the Controller class that handles calls to the /api/eperson/registrations endpoints + */ @RestController @RequestMapping("/api/" + RegistrationRest.CATEGORY + "/" + RegistrationRest.NAME_PLURAL) public class RegistrationRestController { @@ -59,6 +62,22 @@ public class RegistrationRestController { @Autowired private EPersonService ePersonService; + /** + * This method will be used to either register a new user or to send forgotten password info in a mail. + * It can be called by doing a POST request to the /api/eperson/registrations endpoint. + * It'll create a RegistrationRest object from the inputstream in the request and it'll check whether the email + * defined in that object is in the DB or not. + * If it is in the db then we'll send the forgotten password info, if it wasn't in the database then we'll send + * registration info. + * + * @param request The current request + * @param response The current response + * @return An empty response containing a 201 status code + * @throws SQLException If something goes wrong + * @throws IOException If something goes wrong + * @throws MessagingException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ @RequestMapping(method = RequestMethod.POST) public ResponseEntity register(HttpServletRequest request, HttpServletResponse response) throws SQLException, IOException, MessagingException, AuthorizeException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java index 44de9ce6d7..d230d2ef97 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java @@ -20,6 +20,10 @@ import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * The EPerson Registration feature. It's able to be used on site objects if the user.registration property is set to + * true. If it's set to true, it'll check if the current context is allowed to set the password. + */ @Component @AuthorizationFeatureDocumentation(name = EPersonRegistrationFeature.NAME, description = "It can be used to register an eperson") diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java index b84699f140..69ed1d5653 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -13,6 +13,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RegistrationRestController; +/** + * This class acts as the REST representation of the RegistrationData model class. + * This class acts as a data holder for the RegistrationResource + * Refer to {@link org.dspace.eperson.RegistrationData} for explanation about the properties + */ public class RegistrationRest extends RestAddressableModel { public static final String NAME = "registration"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java index 0d92b0d41e..da53b680d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RegistrationResource.java @@ -10,6 +10,10 @@ package org.dspace.app.rest.model.hateoas; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +/** + * Registration HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ @RelNameDSpaceResource(RegistrationRest.NAME) public class RegistrationResource extends HALResource { public RegistrationResource(RegistrationRest content) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index fa69d15253..43e46a85c9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -93,6 +93,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository { @@ -49,6 +52,13 @@ public class RegistrationRestRepository extends DSpaceRestRepository Date: Mon, 11 May 2020 11:49:57 +0200 Subject: [PATCH 049/465] [Task 70808] applied feedback to the registration implementation --- .../org/dspace/app/util/AuthorizeUtil.java | 44 +++++++++++++++++++ .../dspace/eperson/AccountServiceImpl.java | 12 +++++ .../eperson/service/AccountService.java | 2 + .../app/rest/RegistrationRestController.java | 25 ++++++----- .../impl/EPersonRegistrationFeature.java | 17 +++---- .../repository/EPersonRestRepository.java | 32 ++++++-------- .../EPersonPasswordReplaceOperation.java | 5 +++ .../app/rest/EPersonRestRepositoryIT.java | 44 +++++++------------ .../rest/RegistrationRestControllerIT.java | 26 +++++++++++ .../EPersonRegistrationFeatureIT.java | 18 ++------ 10 files changed, 142 insertions(+), 83 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 6c4271e1f2..d5dbf5a514 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 @@ -9,7 +9,10 @@ package org.dspace.app.util; import java.sql.SQLException; import java.util.List; +import javax.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.Logger; +import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; @@ -25,6 +28,8 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.services.factory.DSpaceServicesFactory; /** * This class is an addition to the AuthorizeManager that perform authorization @@ -34,6 +39,7 @@ import org.dspace.core.Context; */ public class AuthorizeUtil { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorizeUtil.class); /** * Default constructor */ @@ -525,4 +531,42 @@ public class AuthorizeUtil { } } } + + /** + * This method will return a boolean indicating whether the current user is allowed to register a new + * account or not + * @param context The relevant DSpace context + * @param request The current request + * @return A boolean indicating whether the current user can register a new account or not + * @throws SQLException If something goes wrong + */ + public static boolean authorizeNewAccountRegistration(Context context, HttpServletRequest request) + throws SQLException { + if (DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("user.registration", true)) { + return AuthenticateServiceFactory.getInstance().getAuthenticationService() + .allowSetPassword(context, request, null); + } + return false; + } + + /** + * This method will return a boolean indicating whether it's allowed to update the password for the EPerson + * with the given email and canLogin property + * @param context The relevant DSpace context + * @param email The email to be checked + * @param canLogin The boolean canLogin property + * @return A boolean indicating if the password can be updated or not + */ + public static boolean authorizeUpdatePassword(Context context, String email, boolean canLogin) { + try { + if (EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email) != null + && canLogin) { + return true; + } + } catch (SQLException e) { + log.error("Something went wrong trying to retrieve EPerson for email: " + email, e); + } + return false; + } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index 2121cd669d..1aeaeaef8e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.util.Locale; import javax.mail.MessagingException; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.ConfigurationManager; @@ -161,6 +162,17 @@ public class AccountServiceImpl implements AccountService { registrationDataService.deleteByToken(context, token); } + @Override + public boolean verifyPasswordStructure(String password) { + if (StringUtils.isBlank(password)) { + return false; + } + if (StringUtils.length(password) < 6) { + return false; + } + return true; + } + /** * THIS IS AN INTERNAL METHOD. THE SEND PARAMETER ALLOWS IT TO BE USED FOR * TESTING PURPOSES. diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java b/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java index c8ecb0cc67..23d46ea00a 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java @@ -46,4 +46,6 @@ public interface AccountService { public void deleteToken(Context context, String token) throws SQLException; + + public boolean verifyPasswordStructure(String password); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java index f24b723e36..741efa995b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java @@ -16,18 +16,17 @@ import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RegistrationRest; -import org.dspace.app.rest.model.SiteRest; -import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; import org.springframework.beans.factory.annotation.Autowired; @@ -83,13 +82,6 @@ public class RegistrationRestController { throws SQLException, IOException, MessagingException, AuthorizeException { Context context = ContextUtil.obtainContext(request); - AuthorizationFeature epersonRegistration = authorizationFeatureService.find("epersonRegistration"); - Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, Projection.DEFAULT); - if (!authorizationFeatureService.isAuthorized(context, epersonRegistration, siteRest)) { - throw new AccessDeniedException( - "Registration is disabled, you are not authorized to create a new Authorization"); - } ObjectMapper mapper = new ObjectMapper(); RegistrationRest registrationRest; try { @@ -101,9 +93,18 @@ public class RegistrationRestController { if (StringUtils.isBlank(registrationRest.getEmail())) { throw new UnprocessableEntityException("The email cannot be omitted from the Registration endpoint"); } - if (ePersonService.findByEmail(context, registrationRest.getEmail()) != null) { + EPerson eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); + if (eperson != null) { + if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail(), eperson.canLogIn())) { + throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " + + eperson.getEmail()); + } accountService.sendForgotPasswordInfo(context, registrationRest.getEmail()); } else { + if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) { + throw new AccessDeniedException( + "Registration is disabled, you are not authorized to create a new Authorization"); + } accountService.sendRegistrationInfo(context, registrationRest.getEmail()); } context.complete(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java index d230d2ef97..a03d68fcc9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonRegistrationFeature.java @@ -13,9 +13,8 @@ import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.SiteRest; -import org.dspace.authenticate.service.AuthenticationService; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.core.Context; -import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -31,12 +30,6 @@ public class EPersonRegistrationFeature implements AuthorizationFeature { public static final String NAME = "epersonRegistration"; - @Autowired - private ConfigurationService configurationService; - - @Autowired - private AuthenticationService authenticationService; - @Autowired private RequestService requestService; @@ -45,11 +38,11 @@ public class EPersonRegistrationFeature implements AuthorizationFeature { if (!(object instanceof SiteRest)) { return false; } - if (configurationService.getBooleanProperty("user.registration", true)) { - return authenticationService - .allowSetPassword(context, requestService.getCurrentRequest().getHttpServletRequest(), null); + if (!AuthorizeUtil.authorizeNewAccountRegistration(context, + requestService.getCurrentRequest().getHttpServletRequest())) { + return false; } - return false; + return true; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 43e46a85c9..4226ff6ab4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -18,19 +18,17 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; -import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.projection.Projection; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -133,32 +131,30 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository epersonLastName = metadataRest.getMap().get("eperson.lastname"); if (epersonFirstName == null || epersonLastName == null || epersonFirstName.isEmpty() || epersonLastName.isEmpty()) { - throw new AccessDeniedException("The eperson.firstname and eperson.lastname values need to be " + + throw new DSpaceBadRequestException("The eperson.firstname and eperson.lastname values need to be " + "filled in"); } } String password = epersonRest.getPassword(); - if (StringUtils.isBlank(password)) { - throw new AccessDeniedException("the password cannot be left blank"); + if (!accountService.verifyPasswordStructure(password)) { + throw new DSpaceBadRequestException("the password cannot be left blank"); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java index 387721915d..5bac19ba96 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java @@ -13,6 +13,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -61,6 +62,10 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { if (StringUtils.isNotBlank(token)) { patchWithToken(context,eperson, token, operation); } + if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail(), eperson.canLogIn())) { + throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " + + eperson.getEmail()); + } ePersonService.setPassword(eperson, (String) operation.getValue()); return object; } else { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 8f48ac4a90..24ecd00b22 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -1881,8 +1881,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"password\":\"somePassword\"," + "\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/eperson/epersons") + MvcResult mvcResult = getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) @@ -1932,8 +1931,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/eperson/epersons") + MvcResult mvcResult = getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) @@ -1985,8 +1983,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/eperson/epersons") + MvcResult mvcResult = getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) @@ -2048,12 +2045,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmailTwo + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmailTwo); assertNull(createdEPerson); @@ -2084,12 +2080,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", "randomToken") .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); @@ -2118,12 +2113,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":false,\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); @@ -2151,12 +2145,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]},\"selfRegistered\":true," + "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); @@ -2184,12 +2177,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String json = "{\"metadata\":{\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true," + "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); @@ -2218,12 +2210,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]}," + "\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); assertNull(createdEPerson); @@ -2253,12 +2244,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"password\":\"somePassword\"," + "\"type\":\"eperson\"}"; - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/epersons") + getClient().perform(post("/api/eperson/epersons") .param("token", forgotPasswordToken) .content(json) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().isBadRequest()); EPerson createdEPerson = ePersonService.findByEmail(context, newEmail); assertNull(createdEPerson); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java index d3147d4611..f50b4c9d65 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -78,4 +78,30 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT registrationDataDAO.delete(context, registrationData); } } + + @Test + public void forgotPasswordTest() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty("user.registration", false); + context.restoreAuthSystemState(); + + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(0, registrationDataList.size()); + + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java index a9bbdce3db..754804edf4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java @@ -62,12 +62,9 @@ public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationT SiteRest SiteRest = siteConverter.convert(site, Projection.DEFAULT); String siteUri = utils.linkToSingleResource(SiteRest, "self").getHref(); - // access the authorization for the admin user - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + getClient().perform(get("/api/authz/authorizations/search/objectAndFeature") .param("uri", siteUri) - .param("eperson", admin.getID().toString()) .param("feature", epersonRegistrationFeature.getName())) .andExpect(status().isOk()); } @@ -82,12 +79,9 @@ public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationT context.turnOffAuthorisationSystem(); configurationService.setProperty("user.registration", false); context.restoreAuthSystemState(); - // access the authorization for the admin user - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + getClient().perform(get("/api/authz/authorizations/search/objectAndFeature") .param("uri", siteUri) - .param("eperson", admin.getID().toString()) .param("feature", epersonRegistrationFeature.getName())) .andExpect(status().isNoContent()); @@ -104,12 +98,9 @@ public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationT SiteRest SiteRest = siteConverter.convert(site, Projection.DEFAULT); String siteUri = utils.linkToSingleResource(SiteRest, "self").getHref(); - // access the authorization for the admin user - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + getClient().perform(get("/api/authz/authorizations/search/objectAndFeature") .param("uri", siteUri) - .param("eperson", admin.getID().toString()) .param("feature", epersonRegistrationFeature.getName())) .andExpect(status().isOk()); @@ -118,9 +109,8 @@ public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationT configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY); context.restoreAuthSystemState(); - getClient(adminToken).perform(get("/api/authz/authorizations/search/objectAndFeature") + getClient().perform(get("/api/authz/authorizations/search/objectAndFeature") .param("uri", siteUri) - .param("eperson", admin.getID().toString()) .param("feature", epersonRegistrationFeature.getName())) .andExpect(status().isNoContent()); From 8ff77a9e6d289e06e133d6e0dfd694337140f4a0 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 11 May 2020 11:51:38 +0200 Subject: [PATCH 050/465] [Task 70808] added javadoc --- .../main/java/org/dspace/eperson/service/AccountService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java b/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java index 23d46ea00a..45fa6d26b1 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java @@ -47,5 +47,10 @@ public interface AccountService { public void deleteToken(Context context, String token) throws SQLException; + /** + * This method verifies that a certain String adheres to the password rules for DSpace + * @param password The String to be checked + * @return A boolean indicating whether or not the given String adheres to the password rules + */ public boolean verifyPasswordStructure(String password); } From 60c4a0777ff57b9cc634e9eaacfea3937a685708 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 18 May 2020 17:50:01 +0200 Subject: [PATCH 051/465] Implement feedback --- .../processor/BitstreamEventProcessor.java | 9 ++- .../processor/ExportEventProcessor.java | 71 ++++++++----------- .../export/processor/ItemEventProcessor.java | 2 +- .../export/service/OpenUrlService.java | 4 +- ...2020.01.08__DS-626-statistics-tracker.sql} | 4 +- ...2020.01.08__DS-626-statistics-tracker.sql} | 4 +- ...2020.01.08__DS-626-statistics-tracker.sql} | 4 +- .../test/data/dspaceFolder/config/local.cfg | 9 +-- .../export/ITExportUsageEventListener.java | 37 ++++++++-- .../BitstreamEventProcessorTest.java | 37 +++++++--- .../processor/ExportEventProcessorTest.java | 20 +++--- .../processor/ItemEventProcessorTest.java | 26 +++++-- 12 files changed, 139 insertions(+), 88 deletions(-) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.0_2020.01.08__Statistics-harvester.sql => V7.0_2020.01.08__DS-626-statistics-tracker.sql} (91%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/{V7.0_2020.01.08__Statistics-harvester.sql => V7.0_2020.01.08__DS-626-statistics-tracker.sql} (91%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.0_2020.01.08__Statistics-harvester.sql => V7.0_2020.01.08__DS-626-statistics-tracker.sql} (91%) 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 b32f1bd6f4..e0dafe82c2 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 @@ -30,6 +30,7 @@ public class BitstreamEventProcessor extends ExportEventProcessor { /** * Creates a new BitstreamEventProcessor that will set the params and obtain the parent item of the bitstream + * * @param context * @param request * @param bitstream @@ -44,6 +45,7 @@ public class BitstreamEventProcessor extends ExportEventProcessor { /** * Returns the parent item of the bitsream + * * @return parent item of the bitstream * @throws SQLException */ @@ -68,6 +70,7 @@ public class BitstreamEventProcessor extends ExportEventProcessor { * 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 */ @@ -81,7 +84,8 @@ public class BitstreamEventProcessor extends ExportEventProcessor { /** * Adds additional item and bitstream data to the url - * @param string to which the additional data needs to be added + * + * @param string to which the additional data needs to be added * @param item * @param bitstream * @return the string with additional data @@ -102,13 +106,14 @@ public class BitstreamEventProcessor extends ExportEventProcessor { /** * 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")); + StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.ui.url")); String identifier; if (item != null && item.getHandle() != null) { 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 88291268ff..e703af7872 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 @@ -30,6 +30,7 @@ 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.core.Utils; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; @@ -44,14 +45,14 @@ public abstract class ExportEventProcessor { private static Logger log = Logger.getLogger(ExportEventProcessor.class); /* The metadata field which is to be checked for */ - protected String trackerType; + protected String trackerTypeMetadataField; /* 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; + protected List trackerTypeMetadataValues; /* The base url of the tracker */ protected String baseUrl; @@ -61,19 +62,17 @@ public abstract class ExportEventProcessor { 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 final static String UTF_8 = CharEncoding.UTF_8; + protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + protected EntityService entityService = ContentServiceFactory.getInstance().getEntityService(); + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected OpenUrlService openUrlService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlService(); + protected Context context; protected HttpServletRequest request; - protected ItemService itemService; /** * Creates a new ExportEventProcessor based on the params and initializes the services @@ -84,7 +83,7 @@ public abstract class ExportEventProcessor { ExportEventProcessor(Context context, HttpServletRequest request) { this.context = context; this.request = request; - initServices(); + initProperties(); } /** @@ -142,13 +141,16 @@ public abstract class ExportEventProcessor { .append(URLEncoder.encode(clientIP, UTF_8)); data.append("&").append(URLEncoder.encode("req_dat", UTF_8)).append("=") .append(URLEncoder.encode(clientUA, UTF_8)); + + String hostName = Utils.getHostName(configurationService.getProperty("dspace.ui.url")); + data.append("&").append(URLEncoder.encode("rft.artnum", UTF_8)).append("="). - append(URLEncoder.encode("oai:" + configurationService.getProperty("dspace.hostname") + ":" + item + append(URLEncoder.encode("oai:" + 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)); + .append(URLEncoder.encode(hostName, UTF_8)); data.append("&").append(URLEncoder.encode("url_tim", UTF_8)).append("=") .append(URLEncoder.encode(getCurrentDateString(), UTF_8)); @@ -214,15 +216,19 @@ public abstract class ExportEventProcessor { * @return whether the item should be processed */ protected boolean shouldProcessItemType(Item item) { - if (trackerType != null && trackerValues != null) { + if (trackerTypeMetadataField != null && trackerTypeMetadataValues != null) { + + // Contains the schema, element and if present qualifier of the metadataField + String[] metadataFieldSplit = trackerTypeMetadataField.split("\\."); + List types = itemService - .getMetadata(item, trackerType.split("\\.")[0], trackerType.split("\\.")[1], - trackerType.split("\\.").length == 2 ? null : trackerType.split("\\.")[2], Item.ANY); + .getMetadata(item, metadataFieldSplit[0], metadataFieldSplit[1], + metadataFieldSplit.length == 2 ? null : metadataFieldSplit[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())) { + if (trackerTypeMetadataValues.contains(type.getValue().toLowerCase())) { //We have found no type so process this item return false; } @@ -241,34 +247,19 @@ public abstract class ExportEventProcessor { /** * Initializes services and params obtained from DSpace config */ - private void initServices() { + private void initProperties() { 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"); + if (trackerTypeMetadataField == null) { + trackerTypeMetadataField = configurationService.getProperty("stats.tracker.type-field"); String[] metadataValues = configurationService.getArrayProperty("stats.tracker.type-value"); if (metadataValues.length > 0) { - trackerValues = new ArrayList<>(); + trackerTypeMetadataValues = new ArrayList<>(); for (String metadataValue : metadataValues) { - trackerValues.add(metadataValue.toLowerCase()); + trackerTypeMetadataValues.add(metadataValue.toLowerCase()); } } else { - trackerValues = null; + trackerTypeMetadataValues = null; } if (StringUtils.equals(configurationService.getProperty("stats.tracker.environment"), "production")) { @@ -288,8 +279,8 @@ public abstract class ExportEventProcessor { } } catch (Exception e) { log.error("Unknown error resolving configuration for the export usage event.", e); - trackerType = null; - trackerValues = null; + trackerTypeMetadataField = null; + trackerTypeMetadataValues = 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 index c8b809ab5d..4dc493c1ff 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 @@ -73,7 +73,7 @@ public class ItemEventProcessor extends ExportEventProcessor { * @return item info */ private String getItemInfo(final Item item) { - StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.url")); + StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.ui.url")); sb.append("/handle/").append(item.getHandle()); return sb.toString(); 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 index 0f8376edbf..881cfb62d3 100644 --- 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 @@ -13,7 +13,7 @@ import java.sql.SQLException; import org.dspace.core.Context; /** - * The Service responsible for processing urls to be send to IRUS + * The Service responsible for processing urls */ public interface OpenUrlService { /** @@ -26,7 +26,7 @@ public interface OpenUrlService { void processUrl(Context c, String urlStr) throws SQLException; /** - * Will process all urls stored in the database and try contacting IRUS again + * Will process all urls stored in the database and try contacting them again * @param 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__DS-626-statistics-tracker.sql similarity index 91% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.01.08__Statistics-harvester.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.01.08__DS-626-statistics-tracker.sql index 83fe1dcbb5..48d182af61 100644 --- 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__DS-626-statistics-tracker.sql @@ -20,10 +20,10 @@ CREATE SEQUENCE openurltracker_seq; -CREATE TABLE OpenUrlTracker +CREATE TABLE openurltracker ( tracker_id INTEGER, tracker_url VARCHAR(1000), uploaddate DATE, - CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) + 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__DS-626-statistics-tracker.sql similarity index 91% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__Statistics-harvester.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql index 761361ca7d..a108fd74b4 100644 --- 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__DS-626-statistics-tracker.sql @@ -20,10 +20,10 @@ CREATE SEQUENCE openurltracker_seq; -CREATE TABLE OpenUrlTracker +CREATE TABLE openurltracker ( tracker_id NUMBER, tracker_url VARCHAR2(1000), uploaddate DATE, - CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) + 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__DS-626-statistics-tracker.sql similarity index 91% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__Statistics-harvester.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.01.08__DS-626-statistics-tracker.sql index 83fe1dcbb5..48d182af61 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__DS-626-statistics-tracker.sql @@ -20,10 +20,10 @@ CREATE SEQUENCE openurltracker_seq; -CREATE TABLE OpenUrlTracker +CREATE TABLE openurltracker ( tracker_id INTEGER, tracker_url VARCHAR(1000), uploaddate DATE, - CONSTRAINT OpenUrlTracker_PK PRIMARY KEY (tracker_id) + CONSTRAINT openurltracker_PK PRIMARY KEY (tracker_id) ); \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 735d9dfec3..a9d6e268f8 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -107,11 +107,4 @@ plugin.sequence.java.util.Collection = \ java.util.ArrayList, \ 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 + java.util.TreeSet \ 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 b5b844f359..bc377a5850 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 @@ -24,6 +24,7 @@ import java.util.List; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.codec.CharEncoding; import org.apache.log4j.Logger; import org.dspace.AbstractIntegrationTest; import org.dspace.authorize.AuthorizeException; @@ -88,11 +89,15 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { ExportUsageEventListener exportUsageEventListener; private Item item; + private Item itemNotToBeProcessed; private Bitstream bitstream; + private Bitstream bitstreamNotToBeProcessed; private EntityType entityType; private Community community; private Collection collection; + private String encodedUrl; + /** * Initializes the test by setting up all objects needed to create a test item @@ -100,6 +105,12 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { @Before() public void init() { super.init(); + + configurationService.setProperty("stats.tracker.enabled", true); + configurationService.setProperty("stats.tracker.type-field", "dc.type"); + configurationService.setProperty("stats.tracker.type-value", "Excluded type"); + + context.turnOffAuthorisationSystem(); try { exportUsageEventListener.configurationService = configurationService; @@ -113,6 +124,20 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { bitstream = itemService.createSingleBitstream(context, new FileInputStream(f), item); itemService.update(context, item); + itemNotToBeProcessed = installItemService + .installItem(context, workspaceItemService.create(context, collection, false)); + itemService.addMetadata(context, itemNotToBeProcessed, "relationship", "type", null, null, "Publication"); + itemService.addMetadata(context, itemNotToBeProcessed, "dc", "type", null, null, "Excluded type"); + File itemNotToBeProcessedFile = new File(testProps.get("test.bitstream").toString()); + bitstreamNotToBeProcessed = itemService.createSingleBitstream(context, + new FileInputStream(itemNotToBeProcessedFile), + itemNotToBeProcessed); + itemService.update(context, itemNotToBeProcessed); + + String dspaceUrl = configurationService.getProperty("dspace.ui.url"); + encodedUrl = URLEncoder.encode(dspaceUrl, CharEncoding.UTF_8); + + } catch (Exception e) { log.error(e.getMessage(), e); } finally { @@ -185,7 +210,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%2Fhandle%2F" + URLEncoder + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUrl + "%2Fhandle%2F" + URLEncoder .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); @@ -220,7 +245,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%2Fhandle%2F" + URLEncoder + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUrl + "%2Fhandle%2F" + URLEncoder .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; boolean isMatch = matchesString(all.get(0).getUrl(), regex); @@ -241,7 +266,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { HttpServletRequest request = mock(HttpServletRequest.class); UsageEvent usageEvent = mock(UsageEvent.class); - when(usageEvent.getObject()).thenReturn(item); + when(usageEvent.getObject()).thenReturn(itemNotToBeProcessed); when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new Context()); @@ -281,7 +306,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%2Fbitstream%2Fhandle%2F" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUrl + "%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); @@ -316,7 +341,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%2Fbitstream%2Fhandle%2F" + + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUrl + "%2Fbitstream%2Fhandle%2F" + URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; @@ -339,7 +364,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { when(request.getHeader(anyString())).thenReturn(null); UsageEvent usageEvent = mock(UsageEvent.class); - when(usageEvent.getObject()).thenReturn(bitstream); + when(usageEvent.getObject()).thenReturn(bitstreamNotToBeProcessed); when(usageEvent.getRequest()).thenReturn(request); when(usageEvent.getContext()).thenReturn(new 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 index 12a693b3d8..ff6f7de5d6 100644 --- 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 @@ -9,18 +9,21 @@ 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.net.URLEncoder; import java.util.UUID; +import org.apache.commons.codec.CharEncoding; import org.dspace.AbstractDSpaceTest; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -36,26 +39,42 @@ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { @Mock private Bitstream bitstream = mock(Bitstream.class); - @Mock - private ConfigurationService configurationService = mock(ConfigurationService.class); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + @InjectMocks BitstreamEventProcessor bitstreamEventProcessor = mock(BitstreamEventProcessor.class, CALLS_REAL_METHODS); + private String encodedUrl; + + + @Before + public void setUp() { + configurationService.setProperty("stats.tracker.enabled", true); + + String dspaceUrl = configurationService.getProperty("dspace.ui.url"); + try { + encodedUrl = URLEncoder.encode(dspaceUrl, CharEncoding.UTF_8); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("Error occurred in setup()", e); + } + + } + @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" + + is("existing-string&svc_dat=" + encodedUrl + "%2Fbitstream%2Fhandle%2F123456789%2F1%2F%3Fsequence" + + "%3D0" + "&rft_dat=Request")); } @@ -66,7 +85,6 @@ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { */ 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")); @@ -74,8 +92,8 @@ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { 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" + + is("existing-string&svc_dat=" + encodedUrl + "%2Fbitstream%2Fitem%2Fd84c8fa8-50e2-4267-98f4" + + "-00954ea89c94%2F%3Fsequence%3D0" + "&rft_dat=Request")); } @@ -86,12 +104,11 @@ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { */ 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" + + assertThat(result, is("existing-string&svc_dat=" + encodedUrl + "%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 index eaebe1bdd7..8b5acfa5f6 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 @@ -30,6 +30,8 @@ import org.dspace.content.service.EntityService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -48,8 +50,8 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { private HttpServletRequest request = mock(HttpServletRequest.class); @Mock private Item item = mock(Item.class); - @Mock - private ConfigurationService configurationService = mock(ConfigurationService.class); + + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @Mock private ItemService itemService = mock(ItemService.class); @Mock @@ -59,6 +61,10 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { @InjectMocks ExportEventProcessor exportEventProcessor = mock(ExportEventProcessor.class); + @Before + public void setUp() { + configurationService.setProperty("stats.tracker.enabled", true); + } @Test /** @@ -75,8 +81,6 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { 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"); @@ -271,12 +275,12 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { String itemField = "dc.type"; - exportEventProcessor.trackerType = itemField; + exportEventProcessor.trackerTypeMetadataField = itemField; List typeList = new ArrayList<>(); typeList.add("type1"); typeList.add("type2"); - exportEventProcessor.trackerValues = typeList; + exportEventProcessor.trackerTypeMetadataValues = typeList; MetadataValue metadataValue = mock(MetadataValue.class); when(metadataValue.getValue()).thenReturn("type2"); @@ -301,12 +305,12 @@ public class ExportEventProcessorTest extends AbstractDSpaceTest { String itemField = "dc.type"; - exportEventProcessor.trackerType = itemField; + exportEventProcessor.trackerTypeMetadataField = itemField; List typeList = new ArrayList<>(); typeList.add("type1"); typeList.add("type2"); - exportEventProcessor.trackerValues = typeList; + exportEventProcessor.trackerTypeMetadataValues = typeList; MetadataValue metadataValue = mock(MetadataValue.class); when(metadataValue.getValue()).thenReturn("type3"); 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 index df572de69e..c42438d78a 100644 --- 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 @@ -9,16 +9,19 @@ 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.net.URLEncoder; +import org.apache.commons.codec.CharEncoding; import org.dspace.AbstractDSpaceTest; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -30,26 +33,39 @@ public class ItemEventProcessorTest extends AbstractDSpaceTest { @Mock private Item item = mock(Item.class); - @Mock - private ConfigurationService configurationService = mock(ConfigurationService.class); + + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @InjectMocks ItemEventProcessor itemEventProcessor = mock(ItemEventProcessor.class, CALLS_REAL_METHODS); + private String encodedUrl; + + @Before + public void setUp() { + configurationService.setProperty("stats.tracker.enabled", true); + + String dspaceUrl = configurationService.getProperty("dspace.ui.url"); + try { + encodedUrl = URLEncoder.encode(dspaceUrl, CharEncoding.UTF_8); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("Error occurred in setup()", e); + } + } + @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")); + is("existing-string&svc_dat=" + encodedUrl + "%2Fhandle%2F123456789%2F1&rft_dat=Investigation")); } From d3cc506b260470125c4690e73bd3f1fbca86f139 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 19 May 2020 13:42:24 +0200 Subject: [PATCH 052/465] [Task 70937] removed the canLogin parameter from the AuthorizeUtil#authorizeUpdatePassword method --- .../src/main/java/org/dspace/app/util/AuthorizeUtil.java | 8 ++++---- .../org/dspace/app/rest/RegistrationRestController.java | 2 +- .../patch/operation/EPersonPasswordReplaceOperation.java | 2 +- 3 files changed, 6 insertions(+), 6 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 d5dbf5a514..92f4fc9311 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 @@ -28,6 +28,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.services.factory.DSpaceServicesFactory; @@ -555,13 +556,12 @@ public class AuthorizeUtil { * with the given email and canLogin property * @param context The relevant DSpace context * @param email The email to be checked - * @param canLogin The boolean canLogin property * @return A boolean indicating if the password can be updated or not */ - public static boolean authorizeUpdatePassword(Context context, String email, boolean canLogin) { + public static boolean authorizeUpdatePassword(Context context, String email) { try { - if (EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email) != null - && canLogin) { + EPerson eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email); + if (eperson != null && eperson.canLogIn()) { return true; } } catch (SQLException e) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java index 741efa995b..34c5a8055c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java @@ -95,7 +95,7 @@ public class RegistrationRestController { } EPerson eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); if (eperson != null) { - if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail(), eperson.canLogIn())) { + if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail())) { throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " + eperson.getEmail()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java index 5bac19ba96..ae02200d98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java @@ -62,7 +62,7 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { if (StringUtils.isNotBlank(token)) { patchWithToken(context,eperson, token, operation); } - if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail(), eperson.canLogIn())) { + if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail())) { throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " + eperson.getEmail()); } From 2a0971d8fbb4f27e07efe73476243c3e3c03133c Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 19 May 2020 14:27:40 +0200 Subject: [PATCH 053/465] [Task 70937] fixes after merge --- .../src/main/java/org/dspace/app/util/AuthorizeUtil.java | 3 +-- .../java/org/dspace/app/rest/RegistrationRestController.java | 4 ++-- 2 files changed, 3 insertions(+), 4 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 d2089d6170..521eff6fc1 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 @@ -30,11 +30,10 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.storedcomponents.CollectionRole; import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java index 34c5a8055c..c79c2a0969 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java @@ -31,7 +31,7 @@ import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; -import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.RepresentationModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; @@ -78,7 +78,7 @@ public class RegistrationRestController { * @throws AuthorizeException If something goes wrong */ @RequestMapping(method = RequestMethod.POST) - public ResponseEntity register(HttpServletRequest request, HttpServletResponse response) + public ResponseEntity> register(HttpServletRequest request, HttpServletResponse response) throws SQLException, IOException, MessagingException, AuthorizeException { Context context = ContextUtil.obtainContext(request); From 9ef1f5f8eb0f0b20c8dd907749d6f83743a84c21 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 27 May 2020 16:42:22 +0200 Subject: [PATCH 054/465] Update bitstream link --- .../processor/BitstreamEventProcessor.java | 42 ++++----------- .../export/ITExportUsageEventListener.java | 21 ++++---- .../BitstreamEventProcessorTest.java | 51 +++---------------- 3 files changed, 28 insertions(+), 86 deletions(-) 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 e0dafe82c2..78138e8150 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 @@ -13,7 +13,6 @@ 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; @@ -77,7 +76,7 @@ public class BitstreamEventProcessor extends ExportEventProcessor { public void processEvent() throws SQLException, IOException { if (shouldProcessItem(item)) { String baseParam = getBaseParameters(item); - String fullParam = addObjectSpecificData(baseParam, item, bitstream); + String fullParam = addObjectSpecificData(baseParam, bitstream); processObject(fullParam); } } @@ -86,16 +85,15 @@ public class BitstreamEventProcessor extends ExportEventProcessor { * 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) + protected String addObjectSpecificData(final String string, Bitstream bitstream) throws UnsupportedEncodingException { StringBuilder data = new StringBuilder(string); - String bitstreamInfo = getBitstreamInfo(item, bitstream); + String bitstreamInfo = getBitstreamInfo(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("=") @@ -107,39 +105,19 @@ public class BitstreamEventProcessor extends ExportEventProcessor { /** * Get Bitstream info used for the url * - * @param item * @param bitstream * @return bitstream info */ - private String getBitstreamInfo(final Item item, final Bitstream bitstream) { + private String getBitstreamInfo(final Bitstream bitstream) { - StringBuilder sb = new StringBuilder(configurationService.getProperty("dspace.ui.url")); + String dspaceRestUrl = configurationService.getProperty("dspace.server.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(); - } + StringBuilder sb = new StringBuilder(); - - 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()); + sb.append(dspaceRestUrl); + sb.append("/api/core/bitstreams/"); + sb.append(bitstream.getID()); + sb.append("/content"); return sb.toString(); } 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 bc377a5850..e5ec49bc81 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 @@ -97,6 +97,7 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { private Collection collection; private String encodedUrl; + private String encodedUIUrl; /** @@ -130,12 +131,14 @@ public class ITExportUsageEventListener extends AbstractIntegrationTest { itemService.addMetadata(context, itemNotToBeProcessed, "dc", "type", null, null, "Excluded type"); File itemNotToBeProcessedFile = new File(testProps.get("test.bitstream").toString()); bitstreamNotToBeProcessed = itemService.createSingleBitstream(context, - new FileInputStream(itemNotToBeProcessedFile), - itemNotToBeProcessed); + new FileInputStream(itemNotToBeProcessedFile), + itemNotToBeProcessed); itemService.update(context, itemNotToBeProcessed); - String dspaceUrl = configurationService.getProperty("dspace.ui.url"); + String dspaceUrl = configurationService.getProperty("dspace.server.url"); encodedUrl = URLEncoder.encode(dspaceUrl, CharEncoding.UTF_8); + String dspaceUIUrl = configurationService.getProperty("dspace.ui.url"); + encodedUIUrl = URLEncoder.encode(dspaceUIUrl, CharEncoding.UTF_8); } catch (Exception e) { @@ -210,7 +213,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=" + encodedUrl + "%2Fhandle%2F" + URLEncoder + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUIUrl + "%2Fhandle%2F" + URLEncoder .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); @@ -245,7 +248,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=" + encodedUrl + "%2Fhandle%2F" + URLEncoder + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUIUrl + "%2Fhandle%2F" + URLEncoder .encode(item.getHandle(), "UTF-8") + "&rft_dat=Investigation"; boolean isMatch = matchesString(all.get(0).getUrl(), regex); @@ -306,8 +309,8 @@ 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=" + encodedUrl + "%2Fbitstream%2Fhandle%2F" + - URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUrl + "%2Fapi%2Fcore%2Fbitstreams" + + "%2F" + bitstream.getID() + "%2Fcontent" + "&rft_dat=Request"; boolean isMatch = matchesString(String.valueOf(testProcessedUrls.get(0)), regex); @@ -341,8 +344,8 @@ 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=" + encodedUrl + "%2Fbitstream%2Fhandle%2F" + - URLEncoder.encode(item.getHandle(), "UTF-8") + "%2F%3Fsequence%3D\\d+" + "&rft_dat=Request"; + "=localhost&url_tim=" + ".*" + "?&svc_dat=" + encodedUrl + "%2Fapi%2Fcore%2Fbitstreams" + + "%2F" + bitstream.getID() + "%2Fcontent" + "&rft_dat=Request"; boolean isMatch = matchesString(all.get(0).getUrl(), regex); 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 index ff6f7de5d6..632b56f5f0 100644 --- 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 @@ -20,7 +20,6 @@ import java.util.UUID; import org.apache.commons.codec.CharEncoding; import org.dspace.AbstractDSpaceTest; import org.dspace.content.Bitstream; -import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.Before; @@ -33,9 +32,6 @@ import org.mockito.Mock; */ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { - @Mock - private Item item = mock(Item.class); - @Mock private Bitstream bitstream = mock(Bitstream.class); @@ -52,7 +48,7 @@ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { public void setUp() { configurationService.setProperty("stats.tracker.enabled", true); - String dspaceUrl = configurationService.getProperty("dspace.ui.url"); + String dspaceUrl = configurationService.getProperty("dspace.server.url"); try { encodedUrl = URLEncoder.encode(dspaceUrl, CharEncoding.UTF_8); } catch (UnsupportedEncodingException e) { @@ -68,50 +64,15 @@ public class BitstreamEventProcessorTest extends AbstractDSpaceTest { public void testAddObectSpecificData() throws UnsupportedEncodingException { bitstreamEventProcessor.configurationService = configurationService; - when(item.getHandle()).thenReturn("123456789/1"); + when(bitstream.getID()).thenReturn(UUID.fromString("455bd3cf-31d3-40db-b283-4106c47fc025")); - String result = bitstreamEventProcessor.addObjectSpecificData("existing-string", item, bitstream); + + String result = bitstreamEventProcessor.addObjectSpecificData("existing-string", bitstream); assertThat(result, - is("existing-string&svc_dat=" + encodedUrl + "%2Fbitstream%2Fhandle%2F123456789%2F1%2F%3Fsequence" + - "%3D0" + - "&rft_dat=Request")); + is("existing-string&svc_dat=" + encodedUrl + "%2Fapi%2Fcore%2Fbitstreams%2F455bd3cf-31d3-40db" + + "-b283-4106c47fc025%2Fcontent&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(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=" + encodedUrl + "%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(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=" + encodedUrl + "%2Fbitstream%2Fid%2F" + - "1a0c4e40-969d-467b-8f01-9b7edab8fd1a%2F%3Fsequence%3D0&rft_dat=Request")); - - } - - } From 4ea4f9112948d28858fb9b8f727da75fe4312032 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 3 Jun 2020 14:42:40 +0200 Subject: [PATCH 055/465] [Task 71213] applied feedback to the new registration creation functionality --- .../org/dspace/app/util/AuthorizeUtil.java | 6 +- .../dspace/eperson/AccountServiceImpl.java | 3 - .../app/rest/RegistrationRestController.java | 114 ------------------ .../app/rest/model/RegistrationRest.java | 4 +- .../repository/EPersonRestRepository.java | 35 ++++-- .../RegistrationRestRepository.java | 67 ++++++++++ .../EPersonPasswordReplaceOperation.java | 14 +-- .../app/rest/EPersonRestRepositoryIT.java | 63 +++++++--- 8 files changed, 150 insertions(+), 156 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java 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 521eff6fc1..56891f961b 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 @@ -34,6 +34,7 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.storedcomponents.CollectionRole; import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; @@ -637,7 +638,10 @@ public class AuthorizeUtil { try { EPerson eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email); if (eperson != null && eperson.canLogIn()) { - return true; + HttpServletRequest request = new DSpace().getRequestService().getCurrentRequest() + .getHttpServletRequest(); + return AuthenticateServiceFactory.getInstance().getAuthenticationService() + .allowSetPassword(context, request, null); } } catch (SQLException e) { log.error("Something went wrong trying to retrieve EPerson for email: " + email, e); diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index 1aeaeaef8e..40da31a0f9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -164,9 +164,6 @@ public class AccountServiceImpl implements AccountService { @Override public boolean verifyPasswordStructure(String password) { - if (StringUtils.isBlank(password)) { - return false; - } if (StringUtils.length(password) < 6) { return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java deleted file mode 100644 index c79c2a0969..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RegistrationRestController.java +++ /dev/null @@ -1,114 +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.app.rest; - -import java.io.IOException; -import java.sql.SQLException; -import javax.mail.MessagingException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.authorization.AuthorizationFeatureService; -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.RegistrationRest; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.util.AuthorizeUtil; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.service.SiteService; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.service.AccountService; -import org.dspace.eperson.service.EPersonService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.ControllerUtils; -import org.springframework.hateoas.RepresentationModel; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -/** - * This will be the Controller class that handles calls to the /api/eperson/registrations endpoints - */ -@RestController -@RequestMapping("/api/" + RegistrationRest.CATEGORY + "/" + RegistrationRest.NAME_PLURAL) -public class RegistrationRestController { - - @Autowired - private AuthorizationFeatureService authorizationFeatureService; - - @Autowired - private SiteService siteService; - - @Autowired - private ConverterService converterService; - - @Autowired - private AccountService accountService; - - @Autowired - private EPersonService ePersonService; - - /** - * This method will be used to either register a new user or to send forgotten password info in a mail. - * It can be called by doing a POST request to the /api/eperson/registrations endpoint. - * It'll create a RegistrationRest object from the inputstream in the request and it'll check whether the email - * defined in that object is in the DB or not. - * If it is in the db then we'll send the forgotten password info, if it wasn't in the database then we'll send - * registration info. - * - * @param request The current request - * @param response The current response - * @return An empty response containing a 201 status code - * @throws SQLException If something goes wrong - * @throws IOException If something goes wrong - * @throws MessagingException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - @RequestMapping(method = RequestMethod.POST) - public ResponseEntity> register(HttpServletRequest request, HttpServletResponse response) - throws SQLException, IOException, MessagingException, AuthorizeException { - - Context context = ContextUtil.obtainContext(request); - ObjectMapper mapper = new ObjectMapper(); - RegistrationRest registrationRest; - try { - ServletInputStream input = request.getInputStream(); - registrationRest = mapper.readValue(input, RegistrationRest.class); - } catch (IOException e1) { - throw new UnprocessableEntityException("Error parsing request body.", e1); - } - if (StringUtils.isBlank(registrationRest.getEmail())) { - throw new UnprocessableEntityException("The email cannot be omitted from the Registration endpoint"); - } - EPerson eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); - if (eperson != null) { - if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail())) { - throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " + - eperson.getEmail()); - } - accountService.sendForgotPasswordInfo(context, registrationRest.getEmail()); - } else { - if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) { - throw new AccessDeniedException( - "Registration is disabled, you are not authorized to create a new Authorization"); - } - accountService.sendRegistrationInfo(context, registrationRest.getEmail()); - } - context.complete(); - return ControllerUtils.toEmptyResponse(HttpStatus.CREATED); - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java index 69ed1d5653..e8397f8ca7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -10,7 +10,7 @@ package org.dspace.app.rest.model; import java.util.UUID; import com.fasterxml.jackson.annotation.JsonProperty; -import org.dspace.app.rest.RegistrationRestController; +import org.dspace.app.rest.RestResourceController; /** @@ -66,7 +66,7 @@ public class RegistrationRest extends RestAddressableModel { @Override public Class getController() { - return RegistrationRestController.class; + return RestResourceController.class; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 4226ff6ab4..6d5e51f961 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -96,8 +96,8 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository { + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistrationRestRepository.class); + + @Autowired + private EPersonService ePersonService; + @Autowired private AccountService accountService; + @Autowired + private RequestService requestService; + @Autowired private RegistrationDataService registrationDataService; @@ -47,6 +68,52 @@ public class RegistrationRestRepository extends DSpaceRestRepository getDomainClass() { return RegistrationRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java index ae02200d98..1a3201abdd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java @@ -57,15 +57,15 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { checkOperationValue(operation.getValue()); if (supports(object, operation)) { EPerson eperson = (EPerson) object; - String token = requestService.getCurrentRequest().getHttpServletRequest().getParameter("token"); - checkModelForExistingValue(eperson); - if (StringUtils.isNotBlank(token)) { - patchWithToken(context,eperson, token, operation); - } if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail())) { throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " + eperson.getEmail()); } + String token = requestService.getCurrentRequest().getHttpServletRequest().getParameter("token"); + checkModelForExistingValue(eperson); + if (StringUtils.isNotBlank(token)) { + verifyAndDeleteToken(context, eperson, token, operation); + } ePersonService.setPassword(eperson, (String) operation.getValue()); return object; } else { @@ -73,7 +73,7 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { } } - private void patchWithToken(Context context, EPerson eperson, String token, Operation operation) { + private void verifyAndDeleteToken(Context context, EPerson eperson, String token, Operation operation) { try { EPerson ePersonFromToken = accountService.getEPerson(context, token); if (ePersonFromToken == null) { @@ -86,7 +86,7 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { } accountService.deleteToken(context, token); } catch (SQLException | AuthorizeException e) { - log.error(e.getMessage(), e); + log.error("Failed to verify or delete the token for an EPerson patch", e); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 21b886ec45..ba81312f3e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -15,7 +15,9 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -156,11 +158,16 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { dataFull.setCanLogIn(true); dataFull.setMetadata(metadataRest); + context.restoreAuthSystemState(); + getClient().perform(post("/api/eperson/epersons") .content(mapper.writeValueAsBytes(data)) .contentType(contentType) .param("projection", "full")) .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/eperson/epersons/search/byEmail") + .param("email", data.getEmail())) + .andExpect(status().isNoContent()); } @Test @@ -1857,12 +1864,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); - assertFalse(oldPassword.equals(newPasswordHash)); + assertNotEquals(oldPassword, newPasswordHash); assertTrue(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, tokenForEPerson); - context.restoreAuthSystemState(); + assertNull(registrationDataService.findByToken(context, tokenForEPerson)); } @@ -1886,7 +1891,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); - String token = getAuthToken(admin.getEmail(), password); + String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) @@ -1896,10 +1901,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isForbidden()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); - assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); - assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); - assertTrue(StringUtils.equals(registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(), - tokenForEPerson)); + assertEquals(oldPassword.getHashString(),newPasswordHash.getHashString()); + assertNotNull(registrationDataService.findByEmail(context, ePerson.getEmail())); + assertEquals(registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(), tokenForEPerson); context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, tokenForEPerson); @@ -1936,7 +1940,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); String tokenForEPersonTwo = registrationDataService.findByEmail(context, ePersonTwo.getEmail()).getToken(); - String token = getAuthToken(admin.getEmail(), password); + String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) @@ -1946,8 +1950,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isForbidden()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); - assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); - assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + assertEquals(oldPassword.getHashString(),newPasswordHash.getHashString()); + assertNotNull(registrationDataService.findByEmail(context, ePerson.getEmail())); context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, tokenForEPerson); @@ -1959,7 +1963,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { public void patchReplaceEmailWithTokenFail() throws Exception { context.turnOffAuthorisationSystem(); - String originalEmail = "Johndoe@fake-email.com"; + String originalEmail = "johndoe@fake-email.com"; EPerson ePerson = EPersonBuilder.createEPerson(context) .withNameInMetadata("John", "Doe") .withEmail(originalEmail) @@ -1976,7 +1980,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); - String token = getAuthToken(admin.getEmail(), password); + String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) @@ -1986,9 +1990,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isForbidden()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); - assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); - assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); - assertTrue(StringUtils.equalsIgnoreCase(ePerson.getEmail(), originalEmail)); + assertEquals(oldPassword.getHashString(),newPasswordHash.getHashString()); + assertNotNull(registrationDataService.findByEmail(context, ePerson.getEmail())); + assertEquals(ePerson.getEmail(), originalEmail); context.turnOffAuthorisationSystem(); registrationDataService.delete(context, registrationDataService.findByEmail(context, ePerson.getEmail())); @@ -2026,7 +2030,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - String token = getAuthToken(admin.getEmail(), password); + String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) @@ -2060,6 +2064,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"password\":\"somePassword\"," + "\"type\":\"eperson\"}"; @@ -2110,6 +2116,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2162,6 +2170,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2223,7 +2233,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterTokenTwo = registrationDataService.findByEmail(context, newRegisterEmailTwo).getToken(); - + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmailTwo + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2259,6 +2270,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2292,6 +2305,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":false,\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2325,6 +2340,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]},\"selfRegistered\":true," + "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2357,6 +2374,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true," + "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; @@ -2389,6 +2408,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]}," + "\"type\":\"eperson\"}"; @@ -2423,6 +2444,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String forgotPasswordToken = registrationDataService.findByEmail(context, eperson.getEmail()).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"password\":\"somePassword\"," + "\"type\":\"eperson\"}"; @@ -2457,6 +2480,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); + // We need to create this json manually to support actually setting the password to a value. + // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; From fdaddd934fe140c60a3db77a1c79a363b369ecb6 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 8 Jun 2020 16:24:19 +0200 Subject: [PATCH 056/465] [Task 71271] applied fixes after merge and applied community feedback to the new registration endpoints --- .../org/dspace/app/util/AuthorizeUtil.java | 4 + .../repository/EPersonRestRepository.java | 5 +- .../EPersonRestPermissionEvaluatorPlugin.java | 29 +-- .../app/rest/EPersonRestRepositoryIT.java | 235 +++++++++--------- .../app/rest/ProcessRestRepositoryIT.java | 7 + .../rest/RegistrationRestControllerIT.java | 18 +- 6 files changed, 166 insertions(+), 132 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 56891f961b..70a0d7e2e9 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 @@ -621,6 +621,10 @@ public class AuthorizeUtil { throws SQLException { if (DSpaceServicesFactory.getInstance().getConfigurationService() .getBooleanProperty("user.registration", true)) { + // This allowSetPassword is currently the only mthod that would return true only when it's + // actually expected to be returning true. + // For example the LDAP canSelfRegister will return true due to auto-register, while that + // does not imply a new user can register explicitly return AuthenticateServiceFactory.getInstance().getAuthenticationService() .allowSetPassword(context, request, null); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 5486c54fdb..ed878374b7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -42,8 +42,8 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.security.access.AccessDeniedException; import org.springframework.hateoas.Link; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -186,6 +186,9 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository idRef = new AtomicReference(); - String content = mvcResult.getResponse().getContentAsString(); - Map map = mapper.readValue(content, Map.class); - String epersonUuid = String.valueOf(map.get("uuid")); - EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); - assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); - assertNull(registrationDataService.findByToken(context, newRegisterToken)); - context.turnOffAuthorisationSystem(); - ePersonService.delete(context, createdEPerson); - context.restoreAuthSystemState(); - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); + + String epersonUuid = String.valueOf(idRef.get()); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } } @Test @@ -2135,37 +2138,38 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - MvcResult mvcResult = getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.uuid", not(empty())), - // is it what you expect? EPerson.getName() returns the email... - //hasJsonPath("$.name", is("Doe John")), - hasJsonPath("$.email", is(newRegisterEmail)), - hasJsonPath("$.type", is("eperson")), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( - matchMetadata("eperson.firstname", "John"), - matchMetadata("eperson.lastname", "Doe") - ))))).andReturn(); + AtomicReference idRef = new AtomicReference(); - String content = mvcResult.getResponse().getContentAsString(); - Map map = mapper.readValue(content, Map.class); - String epersonUuid = String.valueOf(map.get("uuid")); - EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); - assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); - assertNull(registrationDataService.findByToken(context, newRegisterToken)); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.email", is(newRegisterEmail)), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))).andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); - context.turnOffAuthorisationSystem(); - ePersonService.delete(context, createdEPerson); - context.restoreAuthSystemState(); + String epersonUuid = String.valueOf(idRef.get()); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + assertNull(registrationDataService.findByToken(context, newRegisterToken)); - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } } @@ -2189,37 +2193,42 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - MvcResult mvcResult = getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.uuid", not(empty())), - // is it what you expect? EPerson.getName() returns the email... - //hasJsonPath("$.name", is("Doe John")), - hasJsonPath("$.email", is(newRegisterEmail)), - hasJsonPath("$.type", is("eperson")), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( - matchMetadata("eperson.firstname", "John"), - matchMetadata("eperson.lastname", "Doe") - ))))).andReturn(); + AtomicReference idRef = new AtomicReference(); - String content = mvcResult.getResponse().getContentAsString(); - Map map = mapper.readValue(content, Map.class); - String epersonUuid = String.valueOf(map.get("uuid")); - EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); - assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); - assertNull(registrationDataService.findByToken(context, newRegisterToken)); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.email", is(newRegisterEmail)), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))).andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); - context.turnOffAuthorisationSystem(); - ePersonService.delete(context, createdEPerson); - context.restoreAuthSystemState(); - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); + String epersonUuid = String.valueOf(idRef.get()); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + context.restoreAuthSystemState(); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } } @@ -2499,34 +2508,38 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - MvcResult mvcResult = getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.uuid", not(empty())), - // is it what you expect? EPerson.getName() returns the email... - //hasJsonPath("$.name", is("Doe John")), - hasJsonPath("$.email", is(newRegisterEmail)), - hasJsonPath("$.type", is("eperson")), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( - matchMetadata("eperson.firstname", "John"), - matchMetadata("eperson.lastname", "Doe") - ))))).andReturn(); + AtomicReference idRef = new AtomicReference(); - String content = mvcResult.getResponse().getContentAsString(); - Map map = mapper.readValue(content, Map.class); - String epersonUuid = String.valueOf(map.get("uuid")); - EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); - assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); - assertNull(registrationDataService.findByToken(context, newRegisterToken)); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.uuid", not(empty())), + // is it what you expect? EPerson.getName() returns the email... + //hasJsonPath("$.name", is("Doe John")), + hasJsonPath("$.email", is(newRegisterEmail)), + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") + ))))).andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); - context.turnOffAuthorisationSystem(); - ePersonService.delete(context, createdEPerson); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); + String epersonUuid = String.valueOf(idRef.get()); + EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); + assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); + assertNull(registrationDataService.findByToken(context, newRegisterToken)); + + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } } @Test 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..de63aaf6cf 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 @@ -43,6 +43,13 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { @Before public void setup() throws SQLException { + CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { + try { + processService.delete(context, process); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); parameters.add(new DSpaceCommandLineParameter("-r", "test")); parameters.add(new DSpaceCommandLineParameter("-i", null)); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java index f50b4c9d65..da6f7a94d8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -12,16 +12,19 @@ import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.sql.SQLException; import java.util.Iterator; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.eperson.RegistrationData; import org.dspace.eperson.dao.RegistrationDataDAO; import org.dspace.services.ConfigurationService; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +36,19 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT @Autowired private ConfigurationService configurationService; + + @Before + public void setup() throws SQLException { + CollectionUtils.emptyIfNull(registrationDataDAO.findAll(context, RegistrationData.class)).stream() + .forEach(registrationData -> { + try { + registrationDataDAO.delete(context, registrationData); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + @Test public void registrationFlowTest() throws Exception { List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); @@ -44,7 +60,7 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT getClient().perform(post("/api/eperson/registrations") .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) - .andExpect(status().isCreated()); + .andExpect(status().isCreated()); registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); assertEquals(1, registrationDataList.size()); assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); From e39c2a858281ed9530c9dc44c79fcfb2e1504aa6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 10 Jun 2020 17:00:43 +0200 Subject: [PATCH 057/465] added ITs for Controlled Vocabularies --- .../app/rest/AuthorityRestRepositoryIT.java | 295 ++++++++++++++---- .../app/rest/matcher/VocabularyMatcher.java | 44 +++ 2 files changed, 271 insertions(+), 68 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java index 089f781902..7054f98a01 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java @@ -16,18 +16,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Date; import java.util.UUID; -import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.response.QueryResponse; -import org.dspace.app.rest.matcher.AuthorityEntryMatcher; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.matcher.VocabularyMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authority.PersonAuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; +import org.dspace.content.Collection; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -93,24 +92,137 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest AuthorityServiceFactory.getInstance().getAuthorityIndexingService().commit(); } + @Test + public void findAllTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder( + VocabularyMatcher.matchProperties("srsc", "srsc", false, true), + VocabularyMatcher.matchProperties("common_types", "common_types", true, false), + VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("api/integration/vocabularies"))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + + @Test + public void findOneSRSC_Test() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.contains( + VocabularyMatcher.matchProperties("srsc", "srsc", false, true) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + + @Test + public void findOneCommonTypesTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/common_types")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.contains( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + @Test public void correctSrscQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/srsc/entries") + get("/api/integration/vocabularies/srsc/entries") .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) .param("query", "Research") - .param("size", "1000")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))); + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Family research", + "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", + "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Youth research", + "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Youth research", + "vocabularyEntry") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); + } + + @Test + public void controlledVocabularyEntriesWrongCollectionUUIDTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("collection", UUID.randomUUID().toString()) + .param("query", "Research")) + .andExpect(status().isBadRequest()); + } + + @Test + public void controlledVocabularyEntriesMissingCollectionUUID_Test() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("query", "Research")) + .andExpect(status().isBadRequest()); + } + + @Test + public void controlledVocabularyEntriesWrongMetadataTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) + .param("query", "Research")) + .andExpect(status().isBadRequest()); + } + + @Test + public void notScrollableVocabularyRequiredQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.not.existing") + .param("collection", collection.getID().toString())) + .andExpect(status().isUnprocessableEntity()); } @Test public void noResultsSrscQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/srsc/entries") + get("/api/integration/vocabularies/srsc/entries") .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) .param("query", "Research2") .param("size", "1000")) .andExpect(status().isOk()) @@ -118,27 +230,67 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest } @Test - @Ignore - /** - * This functionality is currently broken, it returns all 22 values - */ - public void correctCommonTypesTest() throws Exception { + public void vocabularyEntriesCommon_typesTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/common_types/entries") + getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); + } + + @Test + public void vocabularyEntriesCommon_typesWithQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) .param("query", "Book") - .param("size", "1000")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularySuggestion") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @Test public void correctSolrQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/SolrAuthorAuthority/entries") + get("/api/integration/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") + .param("collection", collection.getID().toString()) .param("query", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) @@ -147,10 +299,17 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void noResultsSolrQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/SolrAuthorAuthority/entries") + get("/api/integration/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") + .param("collection", collection.getID().toString()) .param("query", "Smith") .param("size", "1000")) .andExpect(status().isOk()) @@ -158,43 +317,64 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest } @Test - public void retrieveSrscValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); + public void findByMetadataAndCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); - // When full projection is requested, response should include expected properties, links, and embeds. - getClient(token).perform( - get("/api/integration/authorities/srsc/entryValues/SCB1922").param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", AuthorityEntryMatcher.matchFullEmbeds())) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.contains( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } @Test - public void noResultsSrscValueTest() throws Exception { + public void findByMetadataAndCollectionUnprocessableEntityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/srsc/entryValues/DOESNTEXIST")) - .andExpect(status().isNotFound()); + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.not.exist") + .param("collection", collection.getID().toString())) + .andExpect(status().isUnprocessableEntity()); + + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.type") + .param("collection", UUID.randomUUID().toString())) + .andExpect(status().isUnprocessableEntity()); } @Test - public void retrieveCommonTypesValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/common_types/entryValues/Book").param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))) - ; + public void findByMetadataAndCollectionBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); - } + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); - @Test - public void retrieveCommonTypesWithSpaceValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/common_types/entryValues/Learning+Object")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + //missing metadata + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("collection", collection.getID().toString())) + .andExpect(status().isBadRequest()); + + //missing collection + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.type")) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -209,25 +389,4 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest is("http://localhost/api/authz/authorization/search")) ))); } - - @Test - public void retrieveSolrValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - - SolrQuery query = new SolrQuery(); - query.setQuery("*:*"); - QueryResponse queryResponse = AuthorityServiceFactory.getInstance().getAuthoritySearchService().search(query); - String id = String.valueOf(queryResponse.getResults().get(0).getFieldValue("id")); - - getClient(token).perform( - get("/api/integration/authorities/SolrAuthorAuthority/entryValues/" + id)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); - } - - @Override - public void destroy() throws Exception { - AuthorityServiceFactory.getInstance().getAuthorityIndexingService().cleanIndex(); - super.destroy(); - } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java new file mode 100644 index 0000000000..6e23560911 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.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.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; + +/** + * + * + * @author mykhaylo + * + */ +public class VocabularyMatcher { + + private VocabularyMatcher() {} + + public static Matcher matchProperties(String id, String name, + boolean scrollable, boolean hierarchical) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.name", is(name)), + hasJsonPath("$.scrollable", is(scrollable)), + hasJsonPath("$.hierarchical", is(hierarchical)), + hasJsonPath("$.type", is("vocabulary")) + ); + } + + public static Matcher matchVocabularyEntry(String display, String value, String type) { + return allOf( + hasJsonPath("$.display", is(display)), + hasJsonPath("$.value", is(value)), + hasJsonPath("$.type", is(type)) + ); + } +} From 5f6775a1223cf82cb7f34b5ab2e0883b9429e286 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 10 Jun 2020 21:30:10 +0200 Subject: [PATCH 058/465] added ITs for Vocabulary Entries --- .../app/rest/AuthorityVocabularyEntryIT.java | 158 ++++++++++++++++++ .../rest/matcher/AuthorityEntryMatcher.java | 8 + 2 files changed, 166 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java new file mode 100644 index 0000000000..b234ff3c90 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java @@ -0,0 +1,158 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.UUID; + +import org.dspace.app.rest.matcher.AuthorityEntryMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTest { + + @Test + public void findOneTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("srsc:SCB110"))) + .andExpect(jsonPath("$.value", is("Religion/Theology"))) + .andExpect(jsonPath("$.selectable", is(true))) + .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) + .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) + .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); + } + + @Test + public void findOneBadRequestTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()); + } + + public void findOneUnauthorizedTest() throws Exception { + getClient().perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void srscSearchTopTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); + } + + @Test + public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "SCB14")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + } + + @Test + public void srscSearchTopPaginationTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc") + .param("page", "0") + .param("size", "5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") + ))) + .andExpect(jsonPath("$.page.totalElements", is(12))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(0))); + + //second page + getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + .param("page", "1") + .param("size", "5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY") + ))) + .andExpect(jsonPath("$.page.totalElements", is(12))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(1))); + + // third page + getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + .param("page", "2") + .param("size", "5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + ))) + .andExpect(jsonPath("$.page.totalElements", is(12))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(2))); + } + + @Test + public void searchTopBadRequestTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()); + } + + @Test + public void searchTopUnauthorizedTest() throws Exception { + getClient().perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void retrieveSrscValueTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB1922") + .param("projection", "full")) + .andExpect(status().isOk()); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java index 5758d3ee65..519e77d05e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java @@ -51,4 +51,12 @@ public class AuthorityEntryMatcher { "authorityEntries" ); } + + public static Matcher matchAuthority(String id, String value) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.value", is(value)), + hasJsonPath("$.type", is("vocabularyEntryDetail")) + ); + } } From 80f7bad4944924b6644d02cfeb9e99e4529cce5b Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 11 Jun 2020 10:23:50 +0200 Subject: [PATCH 059/465] [Task 71348] fixed the patch replace password for eperson with a token to now be available to anonymous calls --- .../EPersonPasswordReplaceOperation.java | 1 + .../EPersonRestPermissionEvaluatorPlugin.java | 14 ++++++++++- .../app/rest/EPersonRestRepositoryIT.java | 23 ++++++++----------- ...rsonRestPermissionEvaluatorPluginTest.java | 19 ++++++++++++--- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java index 1a3201abdd..5a30f26fc1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/EPersonPasswordReplaceOperation.java @@ -84,6 +84,7 @@ public class EPersonPasswordReplaceOperation extends PatchOperation { throw new AccessDeniedException("The token in the parameter belongs to a different EPerson" + " than the uri indicates"); } + context.setCurrentUser(ePersonFromToken); accountService.deleteToken(context, token); } catch (SQLException | AuthorizeException e) { log.error("Failed to verify or delete the token for an EPerson patch", e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java index 92b0e433fb..6d670772ac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java @@ -10,7 +10,9 @@ package org.dspace.app.rest.security; import java.io.Serializable; import java.util.List; import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.operation.DSpaceObjectMetadataPatchUtils; @@ -82,6 +84,17 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv public boolean hasPatchPermission(Authentication authentication, Serializable targetId, String targetType, Patch patch) { + List operations = patch.getOperations(); + // If it's a password replace action, we can allow anon through provided that there's a token present + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest httpServletRequest = currentRequest.getHttpServletRequest(); + if (operations.size() > 0 && StringUtils.equalsIgnoreCase(operations.get(0).getOp(), "replace") + && StringUtils.equalsIgnoreCase(operations.get(0).getPath(), "/password") + && StringUtils.isNotBlank(httpServletRequest.getParameter("token"))) { + return true; + } + } /** * First verify that the user has write permission on the eperson. */ @@ -89,7 +102,6 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv return false; } - List operations = patch.getOperations(); /** * The entire Patch request should be denied if it contains operations that are diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index fbd0f071ec..ec2167915b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -1865,10 +1865,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); - String token = getAuthToken(admin.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password - getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) .param("token", tokenForEPerson)) @@ -1902,14 +1901,13 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); - String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password - getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) .param("token", "RandomToken")) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); assertEquals(oldPassword.getHashString(),newPasswordHash.getHashString()); @@ -1951,14 +1949,13 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); String tokenForEPersonTwo = registrationDataService.findByEmail(context, ePersonTwo.getEmail()).getToken(); - String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password - getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) .param("token", tokenForEPersonTwo)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); assertEquals(oldPassword.getHashString(),newPasswordHash.getHashString()); @@ -1991,14 +1988,13 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String tokenForEPerson = registrationDataService.findByEmail(context, ePerson.getEmail()).getToken(); - String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password - getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) .param("token", tokenForEPerson)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); assertEquals(oldPassword.getHashString(),newPasswordHash.getHashString()); @@ -2041,14 +2037,13 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); accountService.sendRegistrationInfo(context, ePerson.getEmail()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - String token = getAuthToken(eperson.getEmail(), password); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); // updates password - getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) .param("token", newRegisterToken)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java index 21fcd60b38..9ff264a1c7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java @@ -19,27 +19,40 @@ import java.util.List; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.services.RequestService; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.security.core.Authentication; +import org.springframework.test.util.ReflectionTestUtils; /** * This class verifies that {@link EPersonRestPermissionEvaluatorPlugin} properly * evaluates Patch requests. */ +@RunWith(MockitoJUnitRunner.class) public class EPersonRestPermissionEvaluatorPluginTest { + @InjectMocks private EPersonRestPermissionEvaluatorPlugin ePersonRestPermissionEvaluatorPlugin; private Authentication authentication; + @Mock + private RequestService requestService; + @Before public void setUp() throws Exception { ePersonRestPermissionEvaluatorPlugin = spy(EPersonRestPermissionEvaluatorPlugin.class); authentication = mock(Authentication.class); DSpaceRestPermission restPermission = DSpaceRestPermission.convert("WRITE"); when(ePersonRestPermissionEvaluatorPlugin - .hasDSpacePermission(authentication, null, null, restPermission)).thenReturn(true); + .hasDSpacePermission(authentication, null, null, restPermission)).thenReturn(true); + ReflectionTestUtils.setField(ePersonRestPermissionEvaluatorPlugin, "requestService", requestService); + when(requestService.getCurrentRequest()).thenReturn(null); } @Test @@ -52,7 +65,7 @@ public class EPersonRestPermissionEvaluatorPluginTest { ops.add(canLoginOperation); Patch patch = new Patch(ops); assertFalse(ePersonRestPermissionEvaluatorPlugin - .hasPatchPermission(authentication, null, null, patch)); + .hasPatchPermission(authentication, null, null, patch)); } @@ -64,7 +77,7 @@ public class EPersonRestPermissionEvaluatorPluginTest { ops.add(passwordOperation); Patch patch = new Patch(ops); assertTrue(ePersonRestPermissionEvaluatorPlugin - .hasPatchPermission(authentication, null, null, patch)); + .hasPatchPermission(authentication, null, null, patch)); } From 3aba1b7545546a068f311adf7f71695699df84d9 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 11 Jun 2020 15:08:03 +0200 Subject: [PATCH 060/465] Added a way to ignore write only properties in REST objects for tests and rewrote EPersonRestRepositoryIT tests to use this --- .../app/rest/EPersonRestRepositoryIT.java | 216 +++++++++++++----- .../jackson/IgnoreJacksonWriteOnlyAccess.java | 24 ++ 2 files changed, 180 insertions(+), 60 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index ec2167915b..ebbb26ad7a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -43,6 +43,7 @@ import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.jackson.IgnoreJacksonWriteOnlyAccess; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.HalMatcher; @@ -2127,18 +2128,25 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + - "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); AtomicReference idRef = new AtomicReference(); + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf( @@ -2182,18 +2190,28 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"email\":\"" + newRegisterEmail + - "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; - + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + ePersonRest.setSelfRegistered(true); AtomicReference idRef = new AtomicReference(); + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); + + try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf( @@ -2250,15 +2268,25 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterTokenTwo = registrationDataService.findByEmail(context, newRegisterEmailTwo).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmailTwo + - "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmailTwo); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2287,15 +2315,25 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + - "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", "randomToken") - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2322,15 +2360,26 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":false,\"email\":\"" + newRegisterEmail + - "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + ePersonRest.setSelfRegistered(false); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2357,14 +2406,23 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]},\"selfRegistered\":true," + - "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + ePersonRest.setSelfRegistered(true); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2391,14 +2449,23 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true," + - "\"email\":\"" + newRegisterEmail + "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + ePersonRest.setSelfRegistered(true); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2425,15 +2492,24 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]}," + - "\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2461,15 +2537,25 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String forgotPasswordToken = registrationDataService.findByEmail(context, eperson.getEmail()).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"selfRegistered\":true,\"password\":\"somePassword\"," + - "\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + ePersonRest.setSelfRegistered(true); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); getClient().perform(post("/api/eperson/epersons") .param("token", forgotPasswordToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); @@ -2497,18 +2583,28 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"email\":\"" + newRegisterEmail + - "\",\"password\":\"somePassword\",\"type\":\"eperson\"}"; + + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setEmail(newRegisterEmail); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); + + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); AtomicReference idRef = new AtomicReference(); try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java new file mode 100644 index 0000000000..577ed6e4d5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java @@ -0,0 +1,24 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.jackson; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; + +public class IgnoreJacksonWriteOnlyAccess extends JacksonAnnotationIntrospector { + + @Override + public JsonProperty.Access findPropertyAccess(Annotated m) { + JsonProperty.Access access = super.findPropertyAccess(m); + if (access == JsonProperty.Access.WRITE_ONLY) { + return JsonProperty.Access.AUTO; + } + return access; + } +} From 48a86399023fa6c1afa4f5382346ec6eccf99b9a Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Fri, 12 Jun 2020 13:56:38 +0200 Subject: [PATCH 061/465] 71342: Authorization for Downloads of restricted Bitstreams #1 --- .../rest/AuthenticationRestController.java | 27 ++++++++ .../AuthenticationTokenConverter.java | 30 +++++++++ .../AuthenticationTokenHalLinkFactory.java | 42 +++++++++++++ .../rest/model/AuthenticationTokenRest.java | 44 +++++++++++++ .../hateoas/AuthenticationTokenResource.java | 20 ++++++ .../rest/security/CustomLogoutHandler.java | 4 +- .../security/RestAuthenticationService.java | 2 + .../rest/security/jwt/JWTTokenHandler.java | 61 ++++++++++++++++--- ...JWTTokenRestAuthenticationServiceImpl.java | 34 +++++++++-- .../security/jwt/SessionJWTTokenHandler.java | 47 ++++++++++++++ .../jwt/ShortLivedJWTTokenHandler.java | 47 ++++++++++++++ .../rest/AuthenticationRestControllerIT.java | 17 ++++++ .../security/jwt/JWTTokenHandlerTest.java | 28 ++++----- dspace/config/modules/authentication.cfg | 44 ++++++++++--- 14 files changed, 410 insertions(+), 37 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationTokenResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index 68f9085e21..f1f8d495c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -16,9 +16,11 @@ import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.AuthenticationStatusRest; +import org.dspace.app.rest.model.AuthenticationTokenRest; import org.dspace.app.rest.model.AuthnRest; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource; +import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource; import org.dspace.app.rest.model.hateoas.AuthnResource; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.security.RestAuthenticationService; @@ -32,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -118,6 +121,30 @@ public class AuthenticationRestController implements InitializingBean { "valid."); } + /** + * This method will generate a short lived token to be used for bitstream downloads. + * + * curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo" + * + * Example: + *
+     * {@code
+     * curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
+     * }
+     * 
+ * @param request The StandardMultipartHttpServletRequest + * @return The created short lived token + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(value = "/shortlivedtokens", method = RequestMethod.POST) + public AuthenticationTokenResource shortLivedLogin(HttpServletRequest request) { + Projection projection = utils.obtainProjection(); + String shortLivedToken = + restAuthenticationService.getShortLivedAuthenticationToken(ContextUtil.obtainContext(request), request); + AuthenticationTokenRest authenticationTokenRest = converter.toRest(shortLivedToken, projection); + return converter.toResource(authenticationTokenRest); + } + @RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE }) public ResponseEntity login() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java new file mode 100644 index 0000000000..a9f9d7208b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.AuthenticationTokenRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * This is the converter from the AuthenticationToken string to tge REST data model + */ +@Component +public class AuthenticationTokenConverter implements DSpaceConverter { + @Override + public AuthenticationTokenRest convert(String modelObject, Projection projection) { + AuthenticationTokenRest token = new AuthenticationTokenRest(); + token.setToken(modelObject); + return token; + } + + @Override + public Class getModelClass() { + return String.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java new file mode 100644 index 0000000000..180a3b7e47 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.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.link; + +import java.util.LinkedList; + +import org.dspace.app.rest.AuthenticationRestController; +import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; + +/** + * This class adds the self link to the AuthenticationTokenResource. + */ +@Component +public class AuthenticationTokenHalLinkFactory + extends HalLinkFactory { + + @Override + protected void addLinks(AuthenticationTokenResource halResource, Pageable pageable, LinkedList list) + throws Exception { + + list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().shortLivedLogin(null))); + } + + @Override + protected Class getControllerClass() { + return AuthenticationRestController.class; + } + + @Override + protected Class getResourceClass() { + return AuthenticationTokenResource.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java new file mode 100644 index 0000000000..130b5cc162 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.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.app.rest.model; + +import org.dspace.app.rest.RestResourceController; + +/** + * The authentication token REST HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + */ +public class AuthenticationTokenRest extends RestAddressableModel { + public static final String NAME = "shortlivedtoken"; + public static final String CATEGORY = "authn"; + + private String token; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationTokenResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationTokenResource.java new file mode 100644 index 0000000000..e46831b2f7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationTokenResource.java @@ -0,0 +1,20 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.AuthenticationTokenRest; + +/** + * Token resource, wraps the AuthenticationToken object + */ +public class AuthenticationTokenResource extends HALResource { + + public AuthenticationTokenResource(AuthenticationTokenRest content) { + super(content); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java index 204eda62dc..19b3e2f4f0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java @@ -10,7 +10,7 @@ package org.dspace.app.rest.security; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.dspace.app.rest.security.jwt.JWTTokenHandler; +import org.dspace.app.rest.security.jwt.SessionJWTTokenHandler; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.core.Context; import org.slf4j.Logger; @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; @Component public class CustomLogoutHandler implements LogoutHandler { - private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); + private static final Logger log = LoggerFactory.getLogger(SessionJWTTokenHandler.class); @Autowired private RestAuthenticationService restAuthenticationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java index b1a47336ba..622070e77c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java @@ -28,6 +28,8 @@ public interface RestAuthenticationService { void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response, DSpaceAuthentication authentication, boolean addCookie) throws IOException; + String getShortLivedAuthenticationToken(Context context, HttpServletRequest request); + EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context); boolean hasAuthenticationData(HttpServletRequest request); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 2f55653a79..fa090df7ed 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -46,17 +46,16 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; -import org.springframework.stereotype.Component; /** * Class responsible for creating and parsing JSON Web Tokens (JWTs), supports both JWS and JWE - * https://jwt.io/ + * https://jwt.io/ . This abstract class needs to be extended with a class providing the + * configuration keys for the particular type of token. * * @author Frederic Van Reet (frederic dot vanreet at atmire dot com) * @author Tom Desair (tom dot desair at atmire dot com) */ -@Component -public class JWTTokenHandler implements InitializingBean { +public abstract class JWTTokenHandler implements InitializingBean { private static final int MAX_CLOCK_SKEW_SECONDS = 60; private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); @@ -86,15 +85,57 @@ public class JWTTokenHandler implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - this.jwtKey = getSecret("jwt.token.secret"); - this.encryptionKey = getSecret("jwt.encryption.secret").getBytes(); + this.jwtKey = + getSecret(getTokenSecretConfigurationKey()); + this.encryptionKey = + getSecret(getEncryptionSecretConfigurationKey()).getBytes(); - this.expirationTime = configurationService.getLongProperty("jwt.token.expiration", 30) * 60 * 1000; - this.includeIP = configurationService.getBooleanProperty("jwt.token.include.ip", true); - this.encryptionEnabled = configurationService.getBooleanProperty("jwt.encryption.enabled", false); - this.compressionEnabled = configurationService.getBooleanProperty("jwt.compression.enabled", false); + this.expirationTime = + configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 30); + this.includeIP = + configurationService.getBooleanProperty(getTokenIncludeIPConfigurationKey(), true); + this.encryptionEnabled = + configurationService.getBooleanProperty(getEncryptionEnabledConfigurationKey(), false); + this.compressionEnabled = + configurationService.getBooleanProperty(getCompressionEnabledConfigurationKey(), false); } + /** + * Get the configuration property key for the token secret. + * @return the configuration property key + */ + protected abstract String getTokenSecretConfigurationKey(); + + /** + * Get the configuration property key for the encryption secret. + * @return the configuration property key + */ + protected abstract String getEncryptionSecretConfigurationKey(); + + /** + * Get the configuration property key for the expiration time. + * @return the configuration property key + */ + protected abstract String getTokenExpirationConfigurationKey(); + + /** + * Get the configuration property key for the include ip. + * @return the configuration property key + */ + protected abstract String getTokenIncludeIPConfigurationKey(); + + /** + * Get the configuration property key for the encryption enable setting. + * @return the configuration property key + */ + protected abstract String getEncryptionEnabledConfigurationKey(); + + /** + * Get the configuration property key for the compression enable setting. + * @return the configuration property key + */ + protected abstract String getCompressionEnabledConfigurationKey(); + /** * Retrieve EPerson from a JSON Web Token (JWT) * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index 3dbab09174..e480154781 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -49,7 +49,10 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication private static final String AUTHORIZATION_TYPE = "Bearer"; @Autowired - private JWTTokenHandler jwtTokenHandler; + private SessionJWTTokenHandler sessionJWTTokenHandler; + + @Autowired + private ShortLivedJWTTokenHandler shortLivedJWTTokenHandler; @Autowired private EPersonService ePersonService; @@ -71,7 +74,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication List groups = authenticationService.getSpecialGroups(context, request); - String token = jwtTokenHandler.createTokenForEPerson(context, request, + String token = sessionJWTTokenHandler.createTokenForEPerson(context, request, authentication.getPreviousLoginDate(), groups); addTokenToResponse(response, token, addCookie); @@ -84,11 +87,34 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication } } + /** + * Create a short-lived token for bitstream downloads + * @param context The context for which to create the token + * @param request The request for which to create the token + * @return The token with a short lifespan + */ + @Override + public String getShortLivedAuthenticationToken(Context context, HttpServletRequest request) { + String token = null; + try { + List groups = authenticationService.getSpecialGroups(context, request); + token = shortLivedJWTTokenHandler.createTokenForEPerson(context, request, null, groups); + context.commit(); + return token; + } catch (JOSEException e) { + log.error("JOSE Exception", e); + } catch (SQLException e) { + log.error("SQL error when adding authentication", e); + } + + return token; + } + @Override public EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context) { String token = getToken(request); try { - EPerson ePerson = jwtTokenHandler.parseEPersonFromToken(token, request, context); + EPerson ePerson = sessionJWTTokenHandler.parseEPersonFromToken(token, request, context); return ePerson; } catch (JOSEException e) { log.error("Jose error", e); @@ -111,7 +137,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication Context context) throws Exception { String token = getToken(request); invalidateAuthenticationCookie(response); - jwtTokenHandler.invalidateToken(token, request, context); + sessionJWTTokenHandler.invalidateToken(token, request, context); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java new file mode 100644 index 0000000000..4d70c4b10d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java @@ -0,0 +1,47 @@ +/** + * 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.security.jwt; + +import org.springframework.stereotype.Component; + +/** + * Class responsible for creating and parsing JSON Web Tokens (JWTs), supports both JWS and JWE + * https://jwt.io/ + */ +@Component +public class SessionJWTTokenHandler extends JWTTokenHandler { + @Override + protected String getTokenSecretConfigurationKey() { + return "jwt.session.token.secret"; + } + + @Override + protected String getEncryptionSecretConfigurationKey() { + return "jwt.session.encryption.secret"; + } + + @Override + protected String getTokenExpirationConfigurationKey() { + return "jwt.session.token.expiration"; + } + + @Override + protected String getTokenIncludeIPConfigurationKey() { + return "jwt.session.token.include.ip"; + } + + @Override + protected String getEncryptionEnabledConfigurationKey() { + return "jwt.session.encryption.enabled"; + } + + @Override + protected String getCompressionEnabledConfigurationKey() { + return "jwt.session.compression.enabled"; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java new file mode 100644 index 0000000000..6610d6f50a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -0,0 +1,47 @@ +/** + * 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.security.jwt; + +import org.springframework.stereotype.Component; + +/** + * Class responsible for creating and parsing JSON Web Tokens (JWTs) used for bitstream + * dowloads, supports both JWS and JWE https://jwt.io/ . + */ +@Component +public class ShortLivedJWTTokenHandler extends JWTTokenHandler { + @Override + protected String getTokenSecretConfigurationKey() { + return "jwt.shortLived.token.secret"; + } + + @Override + protected String getEncryptionSecretConfigurationKey() { + return "jwt.shortLived.encryption.secret"; + } + + @Override + protected String getTokenExpirationConfigurationKey() { + return "jwt.shortLived.token.expiration"; + } + + @Override + protected String getTokenIncludeIPConfigurationKey() { + return "jwt.shortLived.token.include.ip"; + } + + @Override + protected String getEncryptionEnabledConfigurationKey() { + return "jwt.shortLived.encryption.enabled"; + } + + @Override + protected String getCompressionEnabledConfigurationKey() { + return "jwt.shortLived.compression.enabled"; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 5a65447858..41d397f61b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -11,6 +11,7 @@ import static java.lang.Thread.sleep; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertNotEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -30,6 +31,7 @@ import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -757,4 +759,19 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isUnauthorized()); } + + @Test + public void testShortLivedToken() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/authn/shortlivedtokens")) + .andExpect(jsonPath("$.token", notNullValue())) + .andExpect(jsonPath("$.type", is("shortlivedtoken"))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authn/shortlivedtokens"))); + } + + @Test + public void testShortLivedTokenNotAuthenticated() throws Exception { + getClient().perform(post("/api/authn/shortlivedtokens")) + .andExpect(status().isUnauthorized()); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java index aeda2d0e18..94fb653e6c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java @@ -46,7 +46,7 @@ public class JWTTokenHandlerTest { @InjectMocks @Spy - JWTTokenHandler jwtTokenHandler; + SessionJWTTokenHandler sessionJWTTokenHandler; @Mock private Context context; @@ -87,7 +87,7 @@ public class JWTTokenHandlerTest { @Test public void testJWTNoEncryption() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); - String token = jwtTokenHandler + String token = sessionJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); SignedJWT signedJWT = SignedJWT.parse(token); String personId = (String) signedJWT.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID); @@ -96,11 +96,11 @@ public class JWTTokenHandlerTest { @Test(expected = ParseException.class) public void testJWTEncrypted() throws Exception { - when(jwtTokenHandler.isEncryptionEnabled()).thenReturn(true); + when(sessionJWTTokenHandler.isEncryptionEnabled()).thenReturn(true); Date previous = new Date(System.currentTimeMillis() - 10000000000L); StringKeyGenerator keyGenerator = KeyGenerators.string(); - when(jwtTokenHandler.getEncryptionKey()).thenReturn(keyGenerator.generateKey().getBytes()); - String token = jwtTokenHandler + when(sessionJWTTokenHandler.getEncryptionKey()).thenReturn(keyGenerator.generateKey().getBytes()); + String token = sessionJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); SignedJWT signedJWT = SignedJWT.parse(token); } @@ -108,12 +108,12 @@ public class JWTTokenHandlerTest { //temporary set a negative expiration time so the token is invalid immediately @Test public void testExpiredToken() throws Exception { - when(jwtTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); + when(sessionJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); - String token = jwtTokenHandler + String token = sessionJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); - EPerson parsed = jwtTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); + EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); assertEquals(null, parsed); } @@ -121,17 +121,17 @@ public class JWTTokenHandlerTest { //Try if we can change the expiration date @Test public void testTokenTampering() throws Exception { - when(jwtTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); + when(sessionJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); - String token = jwtTokenHandler + String token = sessionJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().claim("eid", "epersonID").expirationTime( new Date(System.currentTimeMillis() + 99999999)).build(); String tamperedPayload = new String(Base64.getUrlEncoder().encode(jwtClaimsSet.toString().getBytes())); String[] splitToken = token.split("\\."); String tamperedToken = splitToken[0] + "." + tamperedPayload + "." + splitToken[2]; - EPerson parsed = jwtTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context); + EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context); assertEquals(null, parsed); } @@ -139,12 +139,12 @@ public class JWTTokenHandlerTest { public void testInvalidatedToken() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); // create a new token - String token = jwtTokenHandler + String token = sessionJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); // immediately invalidate it - jwtTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); + sessionJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); // Check if it is still valid by trying to parse the EPerson from it (should return null) - EPerson parsed = jwtTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); + EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); assertEquals(null, parsed); } diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index d5d010af6d..3a5e618721 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -57,26 +57,56 @@ plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authen # Server key part that is a part of the key used to sign the authentication tokens. # If this property is not set or empty, DSpace will generate a random key on startup. # IF YOU ARE RUNNING DSPACE IN A CLUSTER, you need to set a value for this property here or as an environment variable -# jwt.token.secret = +# jwt.session.token.secret = # This property enables/disables encryption of the payload in a stateless token. Enabling this makes the data encrypted # and unreadable by the receiver, but makes the token larger in size. false by default -jwt.encryption.enabled = false +jwt.session.encryption.enabled = false # Encryption key to use when JWT token encryption is enabled (JWE). Note that encrypting tokens might required additional # configuration in the REST clients -# jwt.encryption.secret = +# jwt.session.encryption.secret = # This enables compression of the payload of a jwt, enabling this will make the jwt token a little smaller at the cost # of some performance, this setting WILL ONLY BE used when encrypting the jwt. -jwt.compression.enabled = true +jwt.session.compression.enabled = true -# Expiration time of a token in minutes -jwt.token.expiration = 30 +# Expiration time of a token in milliseconds +jwt.session.token.expiration = 1800000 # Restrict tokens to a specific ip-address to prevent theft/session hijacking. This is achieved by making the ip-address # a part of the JWT siging key. If this property is set to false then the ip-address won't be used as part of # the signing key of a jwt token and tokens can be shared over multiple ip-addresses. # For security reasons, this defaults to true -jwt.token.include.ip = true +jwt.session.token.include.ip = true + +#---------------------------------------------------------------# +#---Stateless JWT Authentication for downloads of bitstreams----# +#---------------------------------------------------------------# + +# Server key part that is a part of the key used to sign the authentication tokens. +# If this property is not set or empty, DSpace will generate a random key on startup. +# IF YOU ARE RUNNING DSPACE IN A CLUSTER, you need to set a value for this property here or as an environment variable +# jwt.shortLived.token.secret = + +# This property enables/disables encryption of the payload in a stateless token. Enabling this makes the data encrypted +# and unreadable by the receiver, but makes the token larger in size. false by default +jwt.shortLived.encryption.enabled = false + +# Encryption key to use when JWT token encryption is enabled (JWE). Note that encrypting tokens might required additional +# configuration in the REST clients +# jwt.shortLived.encryption.secret = + +# This enables compression of the payload of a jwt, enabling this will make the jwt token a little smaller at the cost +# of some performance, this setting WILL ONLY BE used when encrypting the jwt. +jwt.shortLived.compression.enabled = true + +# Expiration time of a token in milliseconds +jwt.shortLived.token.expiration = 2000 + +# Restrict tokens to a specific ip-address to prevent theft/session hijacking. This is achieved by making the ip-address +# a part of the JWT siging key. If this property is set to false then the ip-address won't be used as part of +# the signing key of a jwt token and tokens can be shared over multiple ip-addresses. +# For security reasons, this defaults to true +jwt.shortLived.token.include.ip = true From 1765d2a161f6440d54f3f2ed808ec274e9d8c84c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 15 Jun 2020 11:53:50 +0200 Subject: [PATCH 062/465] added tests --- .../app/rest/AuthorityVocabularyEntryIT.java | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java index b234ff3c90..c6e6e66b6a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java @@ -49,6 +49,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchTopTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) @@ -67,13 +68,31 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); + + getClient(tokenEPerson).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @Test public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") - .param("vocabulary", "SCB14")) + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), @@ -155,4 +174,89 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()); } + @Test + public void srscSearchByParentFirstLevelPaginationTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + // first page + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("SCB1401", "Algebra, geometry and mathematical analysis"), + AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(0))); + + // second page + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( + AuthorityEntryMatcher.matchAuthority("SCB1409", "Other mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(1))); + } + + @Test + public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), + AuthorityEntryMatcher.matchAuthority("VR140203", "Mathematical statistics"), + AuthorityEntryMatcher.matchAuthority("VR140204", "Optimization, systems theory"), + AuthorityEntryMatcher.matchAuthority("VR140205", "Theoretical computer science") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + } + + @Test + public void srscSearchByParentEmptyTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + } + + @Test + public void srscSearchByParentWrongIdTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + + UUID.randomUUID() + "/children")) + .andExpect(status().isBadRequest()); + } + + @Test + public void srscSearchTopUnauthorizedTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void srscSearchParentByChildrenTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( + AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + + @Test + public void srscSearchParentByChildrenRootTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + } } From 908f4c761aeecf6ccde03230551bd548f98da8b3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Apr 2020 12:52:19 +0200 Subject: [PATCH 063/465] added initial implementantions for languages support --- .../AbstractDSpaceRestRepository.java | 30 ++++++++++++- .../SubmissionFormRestRepository.java | 42 ++++++++++++++----- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java index a64f8af5df..7f815ea8f2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java @@ -7,10 +7,14 @@ */ package org.dspace.app.rest.repository; +import java.util.Locale; + +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.core.Context; +import org.dspace.core.I18nUtil; import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.dspace.utils.DSpace; @@ -33,11 +37,35 @@ public abstract class AbstractDSpaceRestRepository { protected RequestService requestService = new DSpace().getRequestService(); protected Context obtainContext() { + Context context = null; Request currentRequest = requestService.getCurrentRequest(); - return ContextUtil.obtainContext(currentRequest.getServletRequest()); + context = ContextUtil.obtainContext(currentRequest.getServletRequest()); + Locale currentLocale = getLocal(context, currentRequest); + context.setCurrentLocale(currentLocale); + return context; } public RequestService getRequestService() { return requestService; } + + private Locale getLocal(Context context, Request request) { + Locale userLocale = null; + Locale supportedLocale = null; + if (context.getCurrentUser() != null) { + String userLanguage = context.getCurrentUser().getLanguage(); + if (userLanguage != null) { + userLocale = new Locale(userLanguage); + } + } + String locale = request.getHttpServletRequest().getHeader("Accept-Language"); + if (StringUtils.isNotBlank(locale)) { + userLocale = new Locale(locale); + } + if (userLocale == null) { + return I18nUtil.getDefaultLocale(); + } + supportedLocale = I18nUtil.getSupportedLocale(userLocale); + return supportedLocale; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 48856aa163..4552d7ddd5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -7,13 +7,17 @@ */ package org.dspace.app.rest.repository; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import org.dspace.app.rest.model.SubmissionFormRest; import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; import org.dspace.core.Context; +import org.dspace.core.I18nUtil; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; @@ -26,32 +30,50 @@ import org.springframework.stereotype.Component; */ @Component(SubmissionFormRest.CATEGORY + "." + SubmissionFormRest.NAME) public class SubmissionFormRestRepository extends DSpaceRestRepository { - - private DCInputsReader inputReader; + private Map inputReaders; + private DCInputsReader defaultInputReader; public SubmissionFormRestRepository() throws DCInputsReaderException { - inputReader = new DCInputsReader(); + defaultInputReader = new DCInputsReader(); + Locale[] locales = I18nUtil.getSupportedLocales(); + inputReaders = new HashMap(); + for (Locale locale : locales) { + inputReaders.put(locale, new DCInputsReader(I18nUtil.getInputFormsFileName(locale))); + } } @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override - public SubmissionFormRest findOne(Context context, String submitName) { - DCInputSet inputConfig; + public SubmissionFormRest findOne(Context context, String submitName) { try { - inputConfig = inputReader.getInputsByFormName(submitName); + Locale currentLocale = context.getCurrentLocale(); + DCInputsReader inputReader; + if (currentLocale != null) { + inputReader = inputReaders.get(currentLocale); + } else { + inputReader = defaultInputReader; + } + DCInputSet subConfs = inputReader.getInputsByFormName(submitName); + if (subConfs == null) { + return null; + } + return converter.toRest(subConfs, utils.obtainProjection()); } catch (DCInputsReaderException e) { throw new IllegalStateException(e.getMessage(), e); } - if (inputConfig == null) { - return null; - } - return converter.toRest(inputConfig, utils.obtainProjection()); } @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override public Page findAll(Context context, Pageable pageable) { try { + Locale currentLocale = context.getCurrentLocale(); + DCInputsReader inputReader; + if (currentLocale != null) { + inputReader = inputReaders.get(currentLocale); + } else { + inputReader = defaultInputReader; + } long total = inputReader.countInputs(); List subConfs = inputReader.getAllInputs(pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); From 36ef4224cd5cf8c5233dca4c15642e0d43caaee2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Apr 2020 12:57:17 +0200 Subject: [PATCH 064/465] added configurations and submissions forms in italian and --- .../dspaceFolder/config/item-submission.xml | 11 ++ .../config/submission-forms_it.xml | 171 ++++++++++++++++++ .../config/submission-forms_uk.xml | 169 +++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml create mode 100644 dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index de19ef7287..a01cdc1cbc 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -18,6 +18,7 @@ + @@ -108,6 +109,12 @@ org.dspace.submit.step.SampleStep sample + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + @@ -149,6 +156,10 @@ + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml new file mode 100644 index 0000000000..1514824796 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + dc + title + + false + + onebox + Inserisci nome del file + È necessario inserire un titolo principale per questo item + + + + + dc + description + true + + textarea + Inserisci descrizione per questo file + + + + + +
+ + + isAuthorOfPublication + person + true + + Aggiungi un autore + + dc + contributor + author + name + + È richiesto almeno un autore + + + + + dc + title + + false + + onebox + Inserisci titolo principale di questo item + È necessario inserire un titolo principale per questo item + + + + + + + + dc + language + iso + false + + dropdown + Selezionare la lingua del contenuto principale dell'item. Se la lingua non compare + nell'elenco, selezionare "Altro". Se il contenuto non ha davvero una lingua (ad esempio, + se è un set di dati o un'immagine) selezionare "N/A". + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + N/A + + + + Inglese (USA) + en_US + + + Inglese + en + + + Spagnolo + es + + + Tedesco + de + + + Francese + fr + + + Italiano + it + + + Giapponese + ja + + + Cinese + zh + + + Portogallo + pt + + + Ucraino + uk + + + (Altro) + other + + + + +
\ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml new file mode 100644 index 0000000000..88941c34ac --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + dc + title + + false + + onebox + Ввести основний заголовок файла. + Заговолок файла обов'язковий ! + + + + + dc + description + true + + textarea + Ввести опис для цього файла + + + +
+ +
+ + + isAuthorOfPublication + person + true + + Додати автора + + dc + contributor + author + name + + Потрібно ввести хочаб одного автора! + + + + + dc + title + + false + + onebox + Ввести основний заголовок файла + Заговолок файла обов'язковий ! + + + + + + + dc + language + iso + false + + dropdown + Виберiть мову головного змiсту файлу, як що мови немає у списку, + вибрати "Iнша". Як що вмiст вайлу не є текстовим, наприклад + є фотографiєю, тодi вибрати "N/A" + + + + +
+
+ + + + + + + + + + + + + + + + + + + + N/A + + + + Американська (USA) + en_US + + + Англiйська + en + + + Iспанська + es + + + Нiмецька + de + + + Французька + fr + + + Iталiйська + it + + + Японська + ja + + + Китайська + zh + + + Португальська + pt + + + Турецька + tr + + + (Iнша) + other + + + + +
\ No newline at end of file From ec59b7d45e98c6031abe89d82f3aed6eb0ecf14a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 18 Apr 2020 18:17:25 +0200 Subject: [PATCH 065/465] added Filter for set Content-Language Header of the response --- .../ContentLanguageHeaderResponseFilter.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java new file mode 100644 index 0000000000..e5bda29c84 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java @@ -0,0 +1,52 @@ +package org.dspace.app.rest.filter; + +import java.io.IOException; +import java.util.Locale; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.core.I18nUtil; +import org.springframework.stereotype.Component; + +/** + * This filter assures that when the dspace instance supports multiple languages + * they are noted in the Content-Language Header of the response + * + * @author Mykhaylo Boychuk (at 4science.it) + */ +@Component +public class ContentLanguageHeaderResponseFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(request, response); + + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + if (!httpServletResponse.containsHeader("Content-Language")) { + Locale[] locales = I18nUtil.getSupportedLocales(); + StringBuilder locsStr = new StringBuilder(); + for (Locale locale : locales) { + if (locsStr.length() > 0) { + locsStr.append(","); + } + locsStr.append(locale.getLanguage()); + } + httpServletResponse.setHeader("Content-Language", locsStr.toString()); + } + } + + @Override + public void destroy() { + } + +} From 0a37c5906a200b08d53d2584d403b74dae297280 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 18 Apr 2020 18:24:16 +0200 Subject: [PATCH 066/465] refactoring --- .../org/dspace/administer/CreateAdministrator.java | 2 +- .../src/main/java/org/dspace/core/Context.java | 2 +- .../src/main/java/org/dspace/core/I18nUtil.java | 13 +++++-------- .../src/test/java/org/dspace/core/ContextTest.java | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java index a58691e251..983038c812 100644 --- a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java +++ b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java @@ -115,7 +115,7 @@ public final class CreateAdministrator { String lastName = null; char[] password1 = null; char[] password2 = null; - String language = I18nUtil.DEFAULTLOCALE.getLanguage(); + String language = I18nUtil.getDefaultLocale().getLanguage(); while (!dataOK) { System.out.print("E-mail address: "); diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index ecfc29d29d..11e388c727 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -179,7 +179,7 @@ public class Context implements AutoCloseable { } currentUser = null; - currentLocale = I18nUtil.DEFAULTLOCALE; + currentLocale = I18nUtil.getDefaultLocale(); extraLogInfo = ""; ignoreAuth = false; diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index 37e48c4a4f..0d5e95d048 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -37,9 +37,6 @@ import org.dspace.services.factory.DSpaceServicesFactory; public class I18nUtil { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(I18nUtil.class); - // the default Locale of this DSpace Instance - public static final Locale DEFAULTLOCALE = getDefaultLocale(); - // delimiters between elements of UNIX/POSIX locale spec, e.g. en_US.UTF-8 private static final String LOCALE_DELIMITERS = " _."; @@ -127,7 +124,7 @@ public class I18nUtil { return parseLocales(locales); } else { Locale[] availableLocales = new Locale[1]; - availableLocales[0] = DEFAULTLOCALE; + availableLocales[0] = getDefaultLocale(); return availableLocales; } } @@ -148,7 +145,7 @@ public class I18nUtil { Locale supportedLocale = null; String testLocale = ""; if (availableLocales == null) { - supportedLocale = DEFAULTLOCALE; + supportedLocale = getDefaultLocale(); } else { if (!locale.getVariant().equals("")) { testLocale = locale.toString(); @@ -188,7 +185,7 @@ public class I18nUtil { } } if (!isSupported) { - supportedLocale = DEFAULTLOCALE; + supportedLocale = getDefaultLocale(); } } return supportedLocale; @@ -220,7 +217,7 @@ public class I18nUtil { * String of the message */ public static String getMessage(String key) { - return getMessage(key.trim(), DEFAULTLOCALE); + return getMessage(key.trim(), getDefaultLocale()); } /** @@ -233,7 +230,7 @@ public class I18nUtil { */ public static String getMessage(String key, Locale locale) { if (locale == null) { - locale = DEFAULTLOCALE; + locale = getDefaultLocale(); } ResourceBundle.Control control = ResourceBundle.Control.getNoFallbackControl( diff --git a/dspace-api/src/test/java/org/dspace/core/ContextTest.java b/dspace-api/src/test/java/org/dspace/core/ContextTest.java index f5697a72dc..0c29e053ec 100644 --- a/dspace-api/src/test/java/org/dspace/core/ContextTest.java +++ b/dspace-api/src/test/java/org/dspace/core/ContextTest.java @@ -130,7 +130,7 @@ public class ContextTest extends AbstractUnitTest { public void testGetCurrentLocale() { //NOTE: CurrentLocale is not initialized in AbstractUnitTest. So it should be DEFAULTLOCALE assertThat("testGetCurrentLocale 0", context.getCurrentLocale(), notNullValue()); - assertThat("testGetCurrentLocale 1", context.getCurrentLocale(), equalTo(I18nUtil.DEFAULTLOCALE)); + assertThat("testGetCurrentLocale 1", context.getCurrentLocale(), equalTo(I18nUtil.getDefaultLocale())); } /** From efcefef914c20684bdf229f609d4bcf8544267a3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 18 Apr 2020 18:43:06 +0200 Subject: [PATCH 067/465] added ITs for language support --- .../config/submission-forms_uk.xml | 7 +- .../SubmissionFormRestRepository.java | 9 + .../app/rest/SubmissionFormsControllerIT.java | 230 ++++++++++++++++++ 3 files changed, 241 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml index 88941c34ac..49a2ccc1a9 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_uk.xml @@ -47,7 +47,7 @@
-
+ isAuthorOfPublication @@ -86,10 +86,7 @@ false dropdown - Виберiть мову головного змiсту файлу, як що мови немає у списку, - вибрати "Iнша". Як що вмiст вайлу не є текстовим, наприклад - є фотографiєю, тодi вибрати "N/A" - + Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша). Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 4552d7ddd5..434a8dc0fd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -87,4 +87,13 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository getDomainClass() { return SubmissionFormRest.class; } + + public void reload() throws DCInputsReaderException { + this.defaultInputReader = new DCInputsReader(); + Locale[] locales = I18nUtil.getSupportedLocales(); + this.inputReaders = new HashMap(); + for (Locale locale : locales) { + inputReaders.put(locale, new DCInputsReader(I18nUtil.getInputFormsFileName(locale))); + } + } } 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 b17ea47c2d..235764eccd 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 @@ -16,10 +16,18 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Locale; + +import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.matcher.SubmissionFormFieldMatcher; +import org.dspace.app.rest.repository.SubmissionFormRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.util.DCInputsReaderException; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; /** * Integration test to test the /api/config/submissionforms endpoint @@ -27,6 +35,11 @@ import org.junit.Test; */ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTest { + @Autowired + private ConfigurationService configurationService; + @Autowired + private SubmissionFormRestRepository submissionFormRestRepository; + @Test public void findAll() throws Exception { //When we call the root endpoint as anonymous user @@ -185,4 +198,221 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe "creativework.publisher:somepublishername", "periodical", false)))) ; } + + @Test + public void languageSupportTest() throws Exception { + context.turnOffAuthorisationSystem(); + String[] supportedLanguage = {"it","uk"}; + configurationService.setProperty("webui.supported.locales",supportedLanguage); + submissionFormRestRepository.reload(); + + Locale uk = new Locale("uk"); + Locale it = new Locale("it"); + + context.restoreAuthSystemState(); + + String tokenEperson = getAuthToken(eperson.getEmail(), password); + + // user select italian language + getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest").locale(it)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Autore", "\u00C8" + " richiesto almeno un autore", true, + "Aggiungi un autore", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Titolo", + "\u00C8" + " necessario inserire un titolo principale per questo item", false, + "Inserisci titolo principale di questo item", "dc.title")))) + .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("dropdown", "Lingua", null, false, + "Selezionare la lingua del contenuto principale dell'item." + + " Se la lingua non compare nell'elenco, selezionare (Altro)." + + " Se il contenuto non ha davvero una lingua" + + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); + + // user select ukranian language + getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest").locale(uk)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Автор", "Потрібно ввести хочаб одного автора!", + true, "Додати автора", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Заголовок", + "Заговолок файла обов'язковий !", false, + "Ввести основний заголовок файла", "dc.title")))) + .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("dropdown", "Мова", null, false, + "Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)." + + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", + "dc.language.iso")))); + + resetPropertyFile(); + } + + @Test + public void preferLanguageTest() throws Exception { + context.turnOffAuthorisationSystem(); + + String[] supportedLanguage = {"it","uk"}; + configurationService.setProperty("webui.supported.locales",supportedLanguage); + submissionFormRestRepository.reload(); + + EPerson epersonIT = EPersonBuilder.createEPerson(context) + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("it") + .build(); + + EPerson epersonUK = EPersonBuilder.createEPerson(context) + .withEmail("epersonUK@example.com") + .withPassword(password) + .withLanguage("uk") + .build(); + + context.restoreAuthSystemState(); + + String tokenEpersonIT = getAuthToken(epersonIT.getEmail(), password); + String tokenEpersonUK = getAuthToken(epersonUK.getEmail(), password); + + // user with italian prefer language + getClient(tokenEpersonIT).perform(get("/api/config/submissionforms/languagetest")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Autore", "\u00C8" + " richiesto almeno un autore", true, + "Aggiungi un autore", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Titolo", + "\u00C8" + " necessario inserire un titolo principale per questo item", false, + "Inserisci titolo principale di questo item", "dc.title")))) + .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("dropdown", "Lingua", null, false, + "Selezionare la lingua del contenuto principale dell'item." + + " Se la lingua non compare nell'elenco, selezionare (Altro)." + + " Se il contenuto non ha davvero una lingua" + + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); + + // user with ukranian prefer language + getClient(tokenEpersonUK).perform(get("/api/config/submissionforms/languagetest")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Автор", "Потрібно ввести хочаб одного автора!", + true, "Додати автора", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Заголовок", + "Заговолок файла обов'язковий !", false, + "Ввести основний заголовок файла", "dc.title")))) + .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("dropdown", "Мова", null, false, + "Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)." + + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", + "dc.language.iso")))); + + resetPropertyFile(); + } + + @Test + public void userChoiceAnotherLanguageTest() throws Exception { + context.turnOffAuthorisationSystem(); + + String[] supportedLanguage = {"it","uk"}; + configurationService.setProperty("webui.supported.locales",supportedLanguage); + submissionFormRestRepository.reload(); + + Locale it = new Locale("it"); + + EPerson epersonUK = EPersonBuilder.createEPerson(context) + .withEmail("epersonUK@example.com") + .withPassword(password) + .withLanguage("uk") + .build(); + + context.restoreAuthSystemState(); + + String tokenEpersonUK = getAuthToken(epersonUK.getEmail(), password); + + // user prefer ukranian but choice italian language + getClient(tokenEpersonUK).perform(get("/api/config/submissionforms/languagetest").locale(it)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Autore", "\u00C8" + " richiesto almeno un autore", true, + "Aggiungi un autore", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Titolo", + "\u00C8" + " necessario inserire un titolo principale per questo item", false, + "Inserisci titolo principale di questo item", "dc.title")))) + .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("dropdown", "Lingua", null, false, + "Selezionare la lingua del contenuto principale dell'item." + + " Se la lingua non compare nell'elenco, selezionare (Altro)." + + " Se il contenuto non ha davvero una lingua" + + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); + + resetPropertyFile(); + } + + @Test + public void defaultLanguageTest() throws Exception { + context.turnOffAuthorisationSystem(); + + String[] supportedLanguage = {"it","uk"}; + configurationService.setProperty("default.locale","it"); + configurationService.setProperty("webui.supported.locales",supportedLanguage); + submissionFormRestRepository.reload(); + + context.restoreAuthSystemState(); + + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Autore", "\u00C8 richiesto almeno un autore", true, + "Aggiungi un autore", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Titolo", + "\u00C8 necessario inserire un titolo principale per questo item", false, + "Inserisci titolo principale di questo item", "dc.title")))); + + resetPropertyFile(); + } + + private void resetPropertyFile() throws DCInputsReaderException { + configurationService.setProperty("webui.supported.locales",null); + submissionFormRestRepository.reload(); + } } From e2cf32dea33ab26b1f33224400a3b13bf081f972 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sun, 19 Apr 2020 13:27:16 +0200 Subject: [PATCH 068/465] added license --- .../test/data/dspaceFolder/config/submission-forms_it.xml | 4 +--- .../rest/filter/ContentLanguageHeaderResponseFilter.java | 7 +++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml index 1514824796..66ed4a926c 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml @@ -87,9 +87,7 @@ false dropdown - Selezionare la lingua del contenuto principale dell'item. Se la lingua non compare - nell'elenco, selezionare "Altro". Se il contenuto non ha davvero una lingua (ad esempio, - se è un set di dati o un'immagine) selezionare "N/A". + Selezionare la lingua del contenuto principale dell'item. Se la lingua non compare nell'elenco, selezionare (Altro). Se il contenuto non ha davvero una lingua (ad esempio, se è un set di dati o un'immagine) selezionare (N/A). diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java index e5bda29c84..6552ae4f7f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.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.filter; import java.io.IOException; From 60240b19ca4b5b495c5ff26ab06403380c219570 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 22 Apr 2020 12:43:13 +0200 Subject: [PATCH 069/465] added ITs for check Content-Language header --- .../dspace/app/rest/LanguageSupportIT.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java new file mode 100644 index 0000000000..2dc06eca8b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java @@ -0,0 +1,84 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; + +import java.util.Locale; + +import org.dspace.app.rest.builder.EPersonBuilder; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.authority.ChoiceAuthorityServiceImpl; +import org.dspace.core.LegacyPluginServiceImpl; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test class for supported languages + * + * @author Mykhaylo Boychuk (at 4science) + */ +public class LanguageSupportIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + @Autowired + private LegacyPluginServiceImpl legacyPluginService; + @Autowired + private ChoiceAuthorityServiceImpl choiceAuthorityServiceImpl; + + @Test + public void checkDefaultLanguageAnonymousTest() throws Exception { + getClient().perform(get("/api")) + .andExpect(header().stringValues("Content-Language","en")); + } + + @Test + public void checkEnabledMultipleLanguageSupportTest() throws Exception { + context.turnOffAuthorisationSystem(); + String[] supportedLanguage = {"uk","it"}; + configurationService.setProperty("webui.supported.locales",supportedLanguage); + legacyPluginService.clearNamedPluginClasses(); + choiceAuthorityServiceImpl.clearCache(); + + Locale it = new Locale("it"); + + EPerson epersonUK = EPersonBuilder.createEPerson(context) + .withEmail("epersonUK@example.com") + .withPassword(password) + .withLanguage("uk") + .build(); + + EPerson epersonFR = EPersonBuilder.createEPerson(context) + .withEmail("epersonFR@example.com") + .withPassword(password) + .withLanguage("fr") + .build(); + + context.restoreAuthSystemState(); + + String tokenEPersonUK = getAuthToken(epersonUK.getEmail(), password); + String tokenEPersonFR = getAuthToken(epersonFR.getEmail(), password); + + getClient(tokenEPersonUK).perform(get("/api")) + .andExpect(header().stringValues("Content-Language","uk, it")); + + getClient(tokenEPersonUK).perform(get("/api").locale(it)) + .andExpect(header().stringValues("Content-Language","uk, it")); + + getClient(tokenEPersonFR).perform(get("/api").locale(it)) + .andExpect(header().stringValues("Content-Language","en")); + + configurationService.setProperty("webui.supported.locales",null); + legacyPluginService.clearNamedPluginClasses(); + choiceAuthorityServiceImpl.clearCache(); + } +} From d794c1cdf0512ac45f1e9c5a5618646fee6d8c80 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 16 Jun 2020 18:22:56 +0200 Subject: [PATCH 070/465] update tests of Vocabulary --- .../app/rest/AuthorityRestRepositoryIT.java | 38 +++++++-------- .../app/rest/AuthorityVocabularyEntryIT.java | 48 ++++++++++--------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java index 7054f98a01..ec5566ae9f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java @@ -95,7 +95,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void findAllTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies")) + getClient(token).perform(get("/api/submission/vocabularies")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder( VocabularyMatcher.matchProperties("srsc", "srsc", false, true), @@ -103,14 +103,14 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false) ))) .andExpect(jsonPath("$._links.self.href", - Matchers.containsString("api/integration/vocabularies"))) + Matchers.containsString("api/submission/vocabularies"))) .andExpect(jsonPath("$.page.totalElements", is(3))); } @Test public void findOneSRSC_Test() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc")) + getClient(token).perform(get("/api/submission/vocabularies/srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.contains( VocabularyMatcher.matchProperties("srsc", "srsc", false, true) @@ -121,7 +121,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void findOneCommonTypesTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/common_types")) + getClient(token).perform(get("/api/submission/vocabularies/common_types")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.contains( VocabularyMatcher.matchProperties("common_types", "common_types", true, false) @@ -140,7 +140,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/srsc/entries") + get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) .param("query", "Research") @@ -162,7 +162,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void controlledVocabularyEntriesWrongCollectionUUIDTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", UUID.randomUUID().toString()) .param("query", "Research")) @@ -172,7 +172,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void controlledVocabularyEntriesMissingCollectionUUID_Test() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("query", "Research")) .andExpect(status().isBadRequest()); @@ -187,7 +187,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) .param("query", "Research")) @@ -203,7 +203,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.not.existing") .param("collection", collection.getID().toString())) .andExpect(status().isUnprocessableEntity()); @@ -220,7 +220,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/srsc/entries") + get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) .param("query", "Research2") @@ -239,7 +239,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") .param("metadata", "dc.type") .param("collection", collection.getID().toString()) .param("size", "2")) @@ -263,7 +263,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") .param("metadata", "dc.type") .param("collection", collection.getID().toString()) .param("query", "Book") @@ -288,7 +288,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/SolrAuthorAuthority/entries") + get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) .param("query", "Shirasaka") @@ -307,7 +307,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/SolrAuthorAuthority/entries") + get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) .param("query", "Smith") @@ -325,7 +325,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type") .param("collection", collection.getID().toString())) .andExpect(status().isOk()) @@ -345,12 +345,12 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.not.exist") .param("collection", collection.getID().toString())) .andExpect(status().isUnprocessableEntity()); - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type") .param("collection", UUID.randomUUID().toString())) .andExpect(status().isUnprocessableEntity()); @@ -367,12 +367,12 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); //missing metadata - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("collection", collection.getID().toString())) .andExpect(status().isBadRequest()); //missing collection - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type")) .andExpect(status().isUnprocessableEntity()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java index c6e6e66b6a..f78add992b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java @@ -23,11 +23,14 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void findOneTest() throws Exception { + String idAuthority = "srsc:SCB110"; String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is("srsc:SCB110"))) - .andExpect(jsonPath("$.value", is("Religion/Theology"))) + .andExpect(jsonPath("$.value", + is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) + .andExpect(jsonPath("$.display", is("Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) @@ -37,12 +40,13 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void findOneBadRequestTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + UUID.randomUUID().toString())) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID().toString())) .andExpect(status().isBadRequest()); } public void findOneUnauthorizedTest() throws Exception { - getClient().perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + String idAuthority = "srsc:SCB110"; + getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isUnauthorized()); } @@ -50,7 +54,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes public void srscSearchTopTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( @@ -69,7 +73,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); - getClient(tokenEPerson).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenEPerson).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( @@ -92,7 +96,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), @@ -105,7 +109,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchTopPaginationTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc") .param("page", "0") .param("size", "5")) @@ -122,7 +126,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.number", is(0))); //second page - getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") .param("page", "1") .param("size", "5")) .andExpect(status().isOk()) @@ -138,7 +142,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.number", is(1))); // third page - getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") .param("page", "2") .param("size", "5")) .andExpect(status().isOk()) @@ -154,22 +158,22 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void searchTopBadRequestTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", UUID.randomUUID().toString())) .andExpect(status().isBadRequest()); } @Test public void searchTopUnauthorizedTest() throws Exception { - getClient().perform(get("/api/integration/vocabularyEntryDetails/search/top") - .param("vocabulary", "srsc")) + getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc:SCB16")) .andExpect(status().isUnauthorized()); } @Test public void retrieveSrscValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB1922") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "SCB1922") .param("projection", "full")) .andExpect(status().isOk()); } @@ -178,7 +182,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes public void srscSearchByParentFirstLevelPaginationTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); // first page - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") .param("page", "0") .param("size", "2")) .andExpect(status().isOk()) @@ -191,7 +195,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.number", is(0))); // second page - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") .param("page", "1") .param("size", "2")) .andExpect(status().isOk()) @@ -206,7 +210,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), @@ -220,7 +224,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchByParentEmptyTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } @@ -228,7 +232,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchByParentWrongIdTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID() + "/children")) .andExpect(status().isBadRequest()); } @@ -236,7 +240,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchTopUnauthorizedTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isUnauthorized()); } @@ -244,7 +248,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchParentByChildrenTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") @@ -255,7 +259,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchParentByChildrenRootTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } From 8afa698e041b67134c9d92dba46bdf05b8aea80a Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 16 Jun 2020 18:35:04 +0200 Subject: [PATCH 071/465] Initial refactoring for vocabularies (Draft) --- .../org/dspace/content/authority/Choice.java | 2 + .../content/authority/ChoiceAuthority.java | 13 +- .../authority/ChoiceAuthorityServiceImpl.java | 32 +--- .../authority/DSpaceControlledVocabulary.java | 33 ---- ...InputFormSelfRegisterWrapperAuthority.java | 166 ------------------ .../service/ChoiceAuthorityService.java | 20 +-- ...lrServiceMetadataBrowseIndexingPlugin.java | 7 +- .../indexobject/ItemIndexFactoryImpl.java | 6 +- .../AuthorityEntryRestConverter.java | 8 +- .../converter/AuthorityRestConverter.java | 14 +- .../link/AuthorityEntryHalLinkFactory.java | 66 ------- .../model/SubmissionFormInputTypeRest.java | 6 +- ...t.java => VocabularyEntryDetailsRest.java} | 20 +-- .../app/rest/model/VocabularyEntryRest.java | 72 ++++++++ ...AuthorityRest.java => VocabularyRest.java} | 35 ++-- .../model/hateoas/AuthorityEntryResource.java | 8 +- .../rest/model/hateoas/AuthorityResource.java | 13 +- .../AuthorityEntryValueLinkRepository.java | 60 ------- ...ava => VocabularyEntryLinkRepository.java} | 42 +++-- ...ory.java => VocabularyRestRepository.java} | 39 ++-- .../dspace/app/rest/utils/AuthorityUtils.java | 31 +++- .../java/org/dspace/app/rest/utils/Utils.java | 4 +- 22 files changed, 208 insertions(+), 489 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{AuthorityEntryRest.java => VocabularyEntryDetailsRest.java} (76%) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{AuthorityRest.java => VocabularyRest.java} (68%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{AuthorityEntryLinkRepository.java => VocabularyEntryLinkRepository.java} (58%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{AuthorityRestRepository.java => VocabularyRestRepository.java} (54%) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 9b68c75d28..9c092c7e8b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -18,6 +18,8 @@ import java.util.Map; * @see Choices */ public class Choice { + public boolean storeAuthority = true; + /** * Authority key for this value */ diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index d2d06fe983..c77799f83f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -82,16 +82,7 @@ public interface ChoiceAuthority { return false; } - default boolean hasIdentifier() { - return true; + default Integer getPreloadLevel() { + return isHierarchical() ? 0 : null; } - - default public Choice getChoice(String fieldKey, String authKey, String locale) { - Choice result = new Choice(); - result.authority = authKey; - result.label = getLabel(fieldKey, authKey, locale); - result.value = getLabel(fieldKey, authKey, locale); - return result; - } - } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 4cc3f9d6db..0e6836f47b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -149,12 +149,12 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public String getLabel(MetadataValue metadataValue, String locale) { - return getLabel(metadataValue.getMetadataField().toString(), metadataValue.getAuthority(), locale); + public String getLabel(MetadataValue metadataValue, Collection collection, String locale) { + return getLabel(metadataValue.getMetadataField().toString(), collection, metadataValue.getAuthority(), locale); } @Override - public String getLabel(String fieldKey, String authKey, String locale) { + public String getLabel(String fieldKey, Collection collection, String authKey, String locale) { ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); if (ma == null) { throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); @@ -163,7 +163,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public boolean isChoicesConfigured(String fieldKey) { + public boolean isChoicesConfigured(String fieldKey, Collection collection) { return getChoiceAuthorityMap().containsKey(fieldKey); } @@ -178,7 +178,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public List getVariants(MetadataValue metadataValue) { + public List getVariants(MetadataValue metadataValue, Collection collection) { ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString()); if (ma instanceof AuthorityVariantsSupport) { AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma; @@ -189,7 +189,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override - public String getChoiceAuthorityName(String schema, String element, String qualifier) { + public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { String makeFieldKey = makeFieldKey(schema, element, qualifier); if (getChoiceAuthorityMap().containsKey(makeFieldKey)) { for (String key : this.authorities.keySet()) { @@ -370,26 +370,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService return closed; } - @Override - public String getChoiceMetadatabyAuthorityName(String name) { - if (authorities.isEmpty()) { - loadChoiceAuthorityConfigurations(); - } - if (authorities.containsKey(name)) { - return authorities.get(name); - } - return null; - } - - @Override - public Choice getChoice(String fieldKey, String authKey, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); - if (ma == null) { - throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); - } - return ma.getChoice(fieldKey, authKey, locale); - } - @Override public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName) { ChoiceAuthority ma = (ChoiceAuthority) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 097a19eb13..40b1e6b73d 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -211,39 +211,6 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic return true; } - @Override - public Choice getChoice(String fieldKey, String authKey, String locale) { - init(); - log.debug("Getting matches for '" + authKey + "'"); - String xpathExpression = String.format(idTemplate, authKey); - XPath xpath = XPathFactory.newInstance().newXPath(); - try { - Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - if (node != null) { - String[] authorities = new String[1]; - String[] values = new String[1]; - String[] labels = new String[1]; - String[] parent = new String[1]; - String[] note = new String[1]; - readNode(authorities, values, labels, parent, note, 0, node); - - if (values.length > 0) { - Choice choice = new Choice(authorities[0], values[0], labels[0]); - if (StringUtils.isNotBlank(parent[0])) { - choice.extras.put("parent", parent[0]); - } - if (StringUtils.isNotBlank(note[0])) { - choice.extras.put("note", note[0]); - } - return choice; - } - } - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - } - return null; - } - private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes, int i, Node node) { String hierarchy = this.buildString(node); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java deleted file mode 100644 index 8716ef38b9..0000000000 --- a/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java +++ /dev/null @@ -1,166 +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.content.authority; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.app.util.DCInputsReader; -import org.dspace.app.util.DCInputsReaderException; -import org.dspace.content.Collection; - -/** - * This authority is registered automatically by the ChoiceAuthorityService for - * all the metadata that use a value-pair or a vocabulary in the submission-form.xml - * - * It keeps a map of form-name vs ChoiceAuthority to delegate the execution of - * the method to the specific ChoiceAuthority configured for the collection when - * the same metadata have different vocabulary or value-pair on a collection - * basis - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -public class InputFormSelfRegisterWrapperAuthority implements ChoiceAuthority { - - private static Logger log = - org.apache.logging.log4j.LogManager.getLogger(InputFormSelfRegisterWrapperAuthority.class); - - private Map delegates = new HashMap(); - - private static DCInputsReader dci = null; - - private void init() { - try { - if (dci == null) { - dci = new DCInputsReader(); - } - } catch (DCInputsReaderException e) { - log.error("Failed reading DCInputs initialization: ", e); - } - } - - @Override - public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { - String formName; - try { - init(); - if (collection == null) { - Set choices = new HashSet(); - //workaround search in all authority configured - for (ChoiceAuthority ca : delegates.values()) { - Choices tmp = ca.getMatches(field, query, null, start, limit, locale); - if (tmp.total > 0) { - Set mySet = new HashSet(Arrays.asList(tmp.values)); - choices.addAll(mySet); - } - } - if (!choices.isEmpty()) { - Choice[] results = new Choice[choices.size()]; - choices.toArray(results); - return new Choices(results, 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - } else { - formName = dci.getInputFormNameByCollectionAndField(collection, field); - return delegates.get(formName).getMatches(field, query, collection, start, limit, locale); - } - } catch (DCInputsReaderException e) { - log.error(e.getMessage(), e); - } - return new Choices(Choices.CF_NOTFOUND); - } - - @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { - String formName; - try { - init(); - if (collection == null) { - Set choices = new HashSet(); - //workaround search in all authority configured - for (ChoiceAuthority ca : delegates.values()) { - Choices tmp = ca.getBestMatch(field, text, null, locale); - if (tmp.total > 0) { - Set mySet = new HashSet(Arrays.asList(tmp.values)); - choices.addAll(mySet); - } - } - if (!choices.isEmpty()) { - Choice[] results = new Choice[choices.size() - 1]; - choices.toArray(results); - return new Choices(results, 0, choices.size(), Choices.CF_UNCERTAIN, false); - } - } else { - formName = dci.getInputFormNameByCollectionAndField(collection, field); - return delegates.get(formName).getBestMatch(field, text, collection, locale); - } - } catch (DCInputsReaderException e) { - log.error(e.getMessage(), e); - } - return new Choices(Choices.CF_NOTFOUND); - } - - @Override - public String getLabel(String field, String key, String locale) { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - String label = delegate.getLabel(field, key, locale); - if (StringUtils.isNotBlank(label)) { - return label; - } - } - return "UNKNOWN KEY " + key; - } - - @Override - public boolean isHierarchical() { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - return delegate.isHierarchical(); - } - return false; - } - - @Override - public boolean isScrollable() { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - return delegate.isScrollable(); - } - return false; - } - - @Override - public boolean hasIdentifier() { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - return delegate.hasIdentifier(); - } - return false; - } - - public Map getDelegates() { - return delegates; - } - - public void setDelegates(Map delegates) { - this.delegates = delegates; - } -} diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 83db9a734e..3d0bdd7316 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -12,7 +12,6 @@ import java.util.Set; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; -import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; @@ -48,10 +47,10 @@ public interface ChoiceAuthorityService { * @param element element of metadata field * @param qualifier qualifier of metadata field * @return the name of the choice authority associated with the specified - * metadata. Throw IllegalArgumentException if the supplied metadat + * metadata. Throw IllegalArgumentException if the supplied metadata * is not associated with an authority choice */ - public String getChoiceAuthorityName(String schema, String element, String qualifier); + public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection); /** * Wrapper that calls getMatches method of the plugin corresponding to @@ -112,30 +111,33 @@ public interface ChoiceAuthorityService { * the metadata field defined by schema,element,qualifier. * * @param metadataValue metadata value + * @param collection Collection owner of Item * @param locale explicit localization key if available * @return label */ - public String getLabel(MetadataValue metadataValue, String locale); + public String getLabel(MetadataValue metadataValue, Collection collection, String locale); /** * Wrapper that calls getLabel method of the plugin corresponding to * the metadata field defined by single field key. * * @param fieldKey single string identifying metadata field + * @param collection Collection owner of Item * @param locale explicit localization key if available * @param authKey authority key * @return label */ - public String getLabel(String fieldKey, String authKey, String locale); + public String getLabel(String fieldKey, Collection collection, String authKey, String locale); /** * Predicate, is there a Choices configuration of any kind for the * given metadata field? * * @param fieldKey single string identifying metadata field + * @param collection Collection owner of Item * @return true if choices are configured for this field. */ - public boolean isChoicesConfigured(String fieldKey); + public boolean isChoicesConfigured(String fieldKey, Collection collection); /** * Get the presentation keyword (should be "lookup", "select" or "suggest", but this @@ -160,11 +162,7 @@ public interface ChoiceAuthorityService { * @param metadataValue metadata value * @return List of variants */ - public List getVariants(MetadataValue metadataValue); - - public String getChoiceMetadatabyAuthorityName(String name); - - public Choice getChoice(String fieldKey, String authKey, String locale); + public List getVariants(MetadataValue metadataValue, Collection collection); public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java index 187c6b0600..2b2be66384 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -17,6 +17,7 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; +import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -63,7 +64,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex return; } Item item = ((IndexableItem) indexableObject).getIndexedObject(); - + Collection collection = item.getOwningCollection(); // Get the currently configured browse indexes BrowseIndex[] bis; try { @@ -175,7 +176,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex true); if (!ignorePrefered) { preferedLabel = choiceAuthorityService - .getLabel(values.get(x), values.get(x).getLanguage()); + .getLabel(values.get(x), collection, values.get(x).getLanguage()); } List variants = null; @@ -195,7 +196,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex if (!ignoreVariants) { variants = choiceAuthorityService .getVariants( - values.get(x)); + values.get(x), collection); } if (StringUtils diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 7f98131566..2a1008aaf9 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -173,6 +173,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl discoveryConfigurations) throws SQLException, IOException { + // use the item service to retrieve the owning collection also for inprogress submission + Collection collection = (Collection) itemService.getParentObject(context, item); //Keep a list of our sort values which we added, sort values can only be added once List sortFieldsAdded = new ArrayList<>(); Map> searchFilters = null; @@ -359,7 +361,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl { +public class AuthorityEntryRestConverter implements DSpaceConverter { @Override - public AuthorityEntryRest convert(Choice choice, Projection projection) { - AuthorityEntryRest entry = new AuthorityEntryRest(); + public VocabularyEntryDetailsRest convert(Choice choice, Projection projection) { + VocabularyEntryDetailsRest entry = new VocabularyEntryDetailsRest(); entry.setProjection(projection); entry.setValue(choice.value); entry.setDisplay(choice.label); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java index 7e78ef7f14..f481568746 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.converter; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.ChoiceAuthority; @@ -23,15 +23,15 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityRestConverter implements DSpaceConverter { +public class AuthorityRestConverter implements DSpaceConverter { @Override - public AuthorityRest convert(ChoiceAuthority step, Projection projection) { - AuthorityRest authorityRest = new AuthorityRest(); + public VocabularyRest convert(ChoiceAuthority authority, Projection projection) { + VocabularyRest authorityRest = new VocabularyRest(); authorityRest.setProjection(projection); - authorityRest.setHierarchical(step.isHierarchical()); - authorityRest.setScrollable(step.isScrollable()); - authorityRest.setIdentifier(step.hasIdentifier()); + authorityRest.setHierarchical(authority.isHierarchical()); + authorityRest.setScrollable(authority.isScrollable()); + authorityRest.setPreloadLevel(authority.getPreloadLevel()); return authorityRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java deleted file mode 100644 index e24d70a526..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.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.app.rest.link; - -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; - -import java.util.LinkedList; - -import org.atteo.evo.inflector.English; -import org.dspace.app.rest.RestResourceController; -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.hateoas.AuthorityEntryResource; -import org.dspace.app.rest.utils.AuthorityUtils; -import org.springframework.data.domain.Pageable; -import org.springframework.hateoas.IanaLinkRelations; -import org.springframework.hateoas.Link; -import org.springframework.stereotype.Component; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * This class' purpose is to provide a factory to add links to the AuthorityEntryResource. The addLinks factory will - * be called - * from the HalLinkService class addLinks method. - */ -@Component -public class AuthorityEntryHalLinkFactory extends HalLinkFactory { - - protected void addLinks(final AuthorityEntryResource halResource, final Pageable pageable, - final LinkedList list) throws Exception { - AuthorityEntryRest entry = halResource.getContent(); - - if (entry.getOtherInformation() != null) { - if (entry.getOtherInformation().containsKey(AuthorityUtils.RESERVED_KEYMAP_PARENT)) { - UriComponentsBuilder uriComponentsBuilder = linkTo( - getMethodOn(AuthorityRest.CATEGORY, AuthorityRest.NAME) - .findRel(null, null, AuthorityRest.CATEGORY, - English.plural(AuthorityRest.NAME), - entry.getAuthorityName() + "/" + AuthorityRest.ENTRY, - entry.getOtherInformation().get(AuthorityUtils.RESERVED_KEYMAP_PARENT), null, null)) - .toUriComponentsBuilder(); - - list.add(buildLink(AuthorityUtils.RESERVED_KEYMAP_PARENT, uriComponentsBuilder.build().toString())); - } - } - String selfLinkString = linkTo( - getMethodOn().findOne(entry.getCategory(), English.plural(entry.getType()), entry.getAuthorityName())) - .toUriComponentsBuilder().build().toString() + "/entryValues/" + entry.getId(); - list.add(buildLink(IanaLinkRelations.SELF.value(), selfLinkString)); - } - - protected Class getControllerClass() { - return RestResourceController.class; - } - - protected Class getResourceClass() { - return AuthorityEntryResource.class; - } - -} - diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java index 594d715b22..cda0f6fbaa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; public class SubmissionFormInputTypeRest { private String type; private String regex; - private AuthorityRest authority; + private VocabularyRest authority; public String getType() { return type; @@ -39,11 +39,11 @@ public class SubmissionFormInputTypeRest { this.regex = regex; } - public AuthorityRest getAuthority() { + public VocabularyRest getAuthority() { return authority; } - public void setAuthority(AuthorityRest authority) { + public void setAuthority(VocabularyRest authority) { this.authority = authority; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java similarity index 76% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 9fcc01d972..0098ea6aa4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -13,19 +13,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.RestResourceController; /** - * The Authority Entry REST Resource + * The Vocabulary Entry Details REST Resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public class AuthorityEntryRest extends RestAddressableModel { - public static final String NAME = "authorityEntry"; +public class VocabularyEntryDetailsRest extends RestAddressableModel { + public static final String NAME = "vocabularyDetailEntry"; private String id; private String display; private String value; private Map otherInformation; @JsonIgnore - private String authorityName; + private String vocabularyName; public String getId() { return id; @@ -63,22 +63,22 @@ public class AuthorityEntryRest extends RestAddressableModel { return NAME; } - public String getAuthorityName() { - return authorityName; + public String getVocabularyName() { + return vocabularyName; } - public void setAuthorityName(String authorityName) { - this.authorityName = authorityName; + public void setVocabularyName(String vocabularyName) { + this.vocabularyName = vocabularyName; } @Override public String getCategory() { - return AuthorityRest.CATEGORY; + return VocabularyRest.CATEGORY; } @Override public String getType() { - return AuthorityRest.NAME; + return VocabularyRest.NAME; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java new file mode 100644 index 0000000000..27d5a4c75e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -0,0 +1,72 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * An entry in a Vocabulary + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class VocabularyEntryRest { + public static final String NAME = "vocabularyEntry"; + private String authority; + private String display; + private String value; + private Map otherInformation; + + /** + * The Vocabulary Entry Details resource if available related to this entry + */ + @JsonIgnore + private VocabularyEntryDetailsRest vocabularyEntryDetailsRest; + + public String getDisplay() { + return display; + } + + public void setDisplay(String value) { + this.display = value; + } + + public Map getOtherInformation() { + return otherInformation; + } + + public void setOtherInformation(Map otherInformation) { + this.otherInformation = otherInformation; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + public String getAuthority() { + return authority; + } + + public void setVocabularyEntryDetailsRest(VocabularyEntryDetailsRest vocabularyEntryDetailsRest) { + this.vocabularyEntryDetailsRest = vocabularyEntryDetailsRest; + } + + public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() { + return vocabularyEntryDetailsRest; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java similarity index 68% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java index 3245e6f877..cc848b945b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java @@ -10,25 +10,20 @@ package org.dspace.app.rest.model; import org.dspace.app.rest.RestResourceController; /** - * The authority REST resource + * The vocabulary REST resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = AuthorityRest.ENTRIES, - method = "query" + @LinkRest(name = VocabularyRest.ENTRIES, + method = "filter" ), - @LinkRest( - name = AuthorityRest.ENTRY, - method = "getResource" - ) }) -public class AuthorityRest extends BaseObjectRest { +public class VocabularyRest extends BaseObjectRest { - public static final String NAME = "authority"; - public static final String CATEGORY = RestAddressableModel.INTEGRATION; + public static final String NAME = "vocabulary"; + public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String ENTRIES = "entries"; - public static final String ENTRY = "entryValues"; private String name; @@ -36,7 +31,7 @@ public class AuthorityRest extends BaseObjectRest { private boolean hierarchical; - private boolean identifier; + private Integer preloadLevel; @Override public String getId() { @@ -67,6 +62,14 @@ public class AuthorityRest extends BaseObjectRest { this.hierarchical = hierarchical; } + public Integer getPreloadLevel() { + return preloadLevel; + } + + public void setPreloadLevel(Integer preloadLevel) { + this.preloadLevel = preloadLevel; + } + @Override public String getType() { return NAME; @@ -81,12 +84,4 @@ public class AuthorityRest extends BaseObjectRest { public String getCategory() { return CATEGORY; } - - public boolean hasIdentifier() { - return identifier; - } - - public void setIdentifier(boolean identifier) { - this.identifier = identifier; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java index c99ebd6f2e..e7b0e3e78c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.AuthorityEntryRest; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; /** @@ -16,11 +16,11 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@RelNameDSpaceResource(AuthorityEntryRest.NAME) -public class AuthorityEntryResource extends HALResource { +@RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME) +public class AuthorityEntryResource extends HALResource { - public AuthorityEntryResource(AuthorityEntryRest entry) { + public AuthorityEntryResource(VocabularyEntryDetailsRest entry) { super(entry); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java index 0e153097b4..6152f498fd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; @@ -17,13 +17,10 @@ import org.dspace.app.rest.utils.Utils; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@RelNameDSpaceResource(AuthorityRest.NAME) -public class AuthorityResource extends DSpaceResource { - public AuthorityResource(AuthorityRest sd, Utils utils) { +@RelNameDSpaceResource(VocabularyRest.NAME) +public class AuthorityResource extends DSpaceResource { + public AuthorityResource(VocabularyRest sd, Utils utils) { super(sd, utils); - if (sd.hasIdentifier()) { - add(utils.linkToSubResource(sd, AuthorityRest.ENTRY)); - } - add(utils.linkToSubResource(sd, AuthorityRest.ENTRIES)); + add(utils.linkToSubResource(sd, VocabularyRest.ENTRIES)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java deleted file mode 100644 index c2e3c557d4..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java +++ /dev/null @@ -1,60 +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.app.rest.repository; - -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.app.rest.utils.AuthorityUtils; -import org.dspace.content.authority.Choice; -import org.dspace.content.authority.ChoiceAuthority; -import org.dspace.content.authority.service.ChoiceAuthorityService; -import org.dspace.core.Context; -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.prepost.PreAuthorize; -import org.springframework.stereotype.Component; - -/** - * Controller for exposition of authority services - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) - */ -@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRY) -public class AuthorityEntryValueLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { - - @Autowired - private ChoiceAuthorityService cas; - - @Autowired - private AuthorityUtils authorityUtils; - - @PreAuthorize("hasAuthority('AUTHENTICATED')") - public AuthorityEntryRest getResource(HttpServletRequest request, String name, String relId, - Pageable pageable, Projection projection) { - Context context = obtainContext(); - ChoiceAuthority choiceAuthority = cas.getChoiceAuthorityByAuthorityName(name); - Choice choice = choiceAuthority.getChoice(null, relId, context.getCurrentLocale().toString()); - if (choice == null) { - throw new ResourceNotFoundException("The authority was not found"); - } - return authorityUtils.convertEntry(choice, name, projection); - } - - /** - * Not embeddable because this is not currently a pageable subresource. - */ - @Override - public boolean isEmbeddableRelation(Object data, String name) { - return false; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java similarity index 58% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 0c3ec16299..a262cad683 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -15,8 +15,10 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.Collection; @@ -37,8 +39,8 @@ import org.springframework.stereotype.Component; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRIES) -public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME + "." + VocabularyRest.ENTRIES) +public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -51,31 +53,37 @@ public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository private AuthorityUtils authorityUtils; @PreAuthorize("hasAuthority('AUTHENTICATED')") - public Page query(@Nullable HttpServletRequest request, String name, + public Page filter(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); - String query = request == null ? null : request.getParameter("query"); + String filter = request == null ? null : request.getParameter("filter"); String metadata = request == null ? null : request.getParameter("metadata"); String uuidCollectìon = request == null ? null : request.getParameter("uuid"); + Collection collection = null; if (StringUtils.isNotBlank(uuidCollectìon)) { try { collection = cs.find(context, UUID.fromString(uuidCollectìon)); } catch (SQLException e) { - throw new RuntimeException(e); + throw new UnprocessableEntityException(uuidCollectìon + " is not a valid collection"); } } - List results = new ArrayList<>(); + + // validate the parameters + String[] tokens = org.dspace.core.Utils.tokenize(metadata); + String vocName = cas.getChoiceAuthorityName(tokens[0], tokens[1], tokens[2], collection); + if (!StringUtils.equals(name, vocName)) { + throw new UnprocessableEntityException("The vocabulary " + name + " is not allowed for the metadata " + + metadata + " and collection " + uuidCollectìon); + } + Pageable pageable = utils.getPageable(optionalPageable); - if (StringUtils.isNotBlank(metadata)) { - String[] tokens = org.dspace.core.Utils.tokenize(metadata); - String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); - Choices choices = cas.getMatches(fieldKey, query, collection, Math.toIntExact(pageable.getOffset()), - pageable.getPageSize(), - context.getCurrentLocale().toString()); - for (Choice value : choices.values) { - results.add(authorityUtils.convertEntry(value, name, projection)); - } + List results = new ArrayList<>(); + String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); + Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), + pageable.getPageSize(), context.getCurrentLocale().toString()); + for (Choice value : choices.values) { + results.add(authorityUtils.convertEntry(value, name, projection)); } return new PageImpl<>(results, pageable, results.size()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java similarity index 54% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index d5dda5a0bc..179898e2d1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -8,35 +8,29 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; -import org.dspace.app.rest.DiscoverableEndpointsService; -import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.AuthorizationRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Context; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; -import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** - * Controller for exposition of authority services + * Controller for exposition of vocabularies for the submission * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) + * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME) -public class AuthorityRestRepository extends DSpaceRestRepository - implements InitializingBean { +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME) +public class VocabularyRestRepository extends DSpaceRestRepository { @Autowired private ChoiceAuthorityService cas; @@ -44,39 +38,30 @@ public class AuthorityRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { + public Page findAll(Context context, Pageable pageable) { Set authoritiesName = cas.getChoiceAuthoritiesNames(); - List results = new ArrayList<>(); + List results = new ArrayList<>(); Projection projection = utils.obtainProjection(); for (String authorityName : authoritiesName) { ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName); - AuthorityRest result = authorityUtils.convertAuthority(source, authorityName, projection); + VocabularyRest result = authorityUtils.convertAuthority(source, authorityName, projection); results.add(result); } - return new PageImpl<>(results, pageable, results.size()); + return utils.getPage(results, pageable); } @Override - public Class getDomainClass() { - return AuthorityRest.class; + public Class getDomainClass() { + return VocabularyRest.class; } - @Override - public void afterPropertiesSet() throws Exception { - discoverableEndpointsService.register(this, Arrays.asList( - new Link("/api/" + AuthorizationRest.CATEGORY + "/" + AuthorizationRest.NAME + "/search", - AuthorizationRest.NAME + "-search"))); - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 97be32ecf9..df0308fd41 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -8,8 +8,9 @@ package org.dspace.app.rest.utils; import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; @@ -39,11 +40,11 @@ public class AuthorityUtils { public boolean isChoice(String schema, String element, String qualifier) { - return cas.isChoicesConfigured(org.dspace.core.Utils.standardize(schema, element, qualifier, "_")); + return cas.isChoicesConfigured(org.dspace.core.Utils.standardize(schema, element, qualifier, "_"), null); } public String getAuthorityName(String schema, String element, String qualifier) { - return cas.getChoiceAuthorityName(schema, element, qualifier); + return cas.getChoiceAuthorityName(schema, element, qualifier, null); } public boolean isClosed(String schema, String element, String qualifier) { @@ -62,9 +63,21 @@ public class AuthorityUtils { * @param projection the name of the projection to use, or {@code null}. * @return */ - public AuthorityEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { - AuthorityEntryRest entry = converter.toRest(choice, projection); - entry.setAuthorityName(authorityName); + public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, Projection projection) { + VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); + entry.setVocabularyName(authorityName); + return entry; + } + + public VocabularyEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { + VocabularyEntryRest entry = new VocabularyEntryRest(); + entry.setDisplay(choice.label); + entry.setValue(choice.value); + entry.setOtherInformation(choice.extras); + entry.setAuthority(choice.authority); + if (choice.storeAuthority) { + entry.setVocabularyEntryDetailsRest(converter.toRest(choice, projection)); + } return entry; } @@ -76,8 +89,8 @@ public class AuthorityUtils { * @param projection the projecton to use. * @return */ - public AuthorityRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) { - AuthorityRest result = converter.toRest(source, projection); + public VocabularyRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) { + VocabularyRest result = converter.toRest(source, projection); result.setName(authorityName); return result; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index fc3b5fb711..7f8cfef8de 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -47,7 +47,7 @@ import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryNotFoundException; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.LinkRest; @@ -254,7 +254,7 @@ public class Utils { return CommunityRest.NAME; } if (modelPlural.equals("authorities")) { - return AuthorityRest.NAME; + return VocabularyRest.NAME; } if (modelPlural.equals("resourcepolicies")) { return ResourcePolicyRest.NAME; From 17c383364542672efaaa1d631bd13f29ef7dc5bd Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 17 Jun 2020 10:45:34 +0200 Subject: [PATCH 072/465] Refactoring for vocabularies, second part (Draft) --- .../authority/ChoiceAuthorityServiceImpl.java | 214 +++++++++++------- 1 file changed, 133 insertions(+), 81 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 0e6836f47b..52ce3f7d25 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -7,7 +7,9 @@ */ package org.dspace.content.authority; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -19,6 +21,9 @@ import org.dspace.app.util.DCInput; import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.app.util.SubmissionConfig; +import org.dspace.app.util.SubmissionConfigReader; +import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -54,14 +59,25 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService // map of field key to authority plugin protected Map controller = new HashMap(); + // map of field key, form definition to authority plugin + protected Map> controllerFormDefinitions = + new HashMap>(); + // map of field key to presentation type protected Map presentation = new HashMap(); // map of field key to closed value protected Map closed = new HashMap(); - // map of authority name to field key - protected Map authorities = new HashMap(); + // flag to track the initialization status of the service + private boolean initialized = false; + + // map of authority name to field keys (the same authority can be configured over multiple metadata) + protected Map> authorities = new HashMap>(); + + // map of authority name to form definition and field keys + protected Map>> authoritiesFormDefinitions = + new HashMap>>(); @Autowired(required = true) protected ConfigurationService configurationService; @@ -96,10 +112,18 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public Set getChoiceAuthoritiesNames() { - if (authorities.keySet().isEmpty()) { + init(); + Set authoritiesNames = new HashSet(); + authoritiesNames.addAll(authorities.keySet()); + authoritiesNames.addAll(authoritiesFormDefinitions.keySet()); + return authoritiesNames; + } + + private synchronized void init() { + if (!initialized) { loadChoiceAuthorityConfigurations(); + initialized = true; } - return authorities.keySet(); } @Override @@ -112,23 +136,24 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { throw new IllegalArgumentException( "No choices plugin was configured for field \"" + fieldKey - + "\"."); + + "\", collection=" + collection.getID().toString() + "."); } return ma.getMatches(fieldKey, query, collection, start, limit, locale); } + @Override public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale, boolean externalInput) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { throw new IllegalArgumentException( "No choices plugin was configured for field \"" + fieldKey - + "\"."); + + "\", collection=" + collection.getID().toString() + "."); } if (externalInput && ma instanceof SolrAuthority) { ((SolrAuthority) ma).addExternalResultsInNextMatches(); @@ -139,11 +164,11 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public Choices getBestMatch(String fieldKey, String query, Collection collection, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { throw new IllegalArgumentException( "No choices plugin was configured for field \"" + fieldKey - + "\"."); + + "\", collection=" + collection.getID().toString() + "."); } return ma.getBestMatch(fieldKey, query, collection, locale); } @@ -155,16 +180,18 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getLabel(String fieldKey, Collection collection, String authKey, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { - throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); + throw new IllegalArgumentException( + "No choices plugin was configured for field \"" + fieldKey + + "\", collection=" + collection.getID().toString() + "."); } return ma.getLabel(fieldKey, authKey, locale); } @Override public boolean isChoicesConfigured(String fieldKey, Collection collection) { - return getChoiceAuthorityMap().containsKey(fieldKey); + return getAuthorityByFieldKeyCollection(fieldKey, collection) != null; } @Override @@ -179,7 +206,13 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public List getVariants(MetadataValue metadataValue, Collection collection) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString()); + String fieldKey = metadataValue.getMetadataField().toString(); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); + if (ma == null) { + throw new IllegalArgumentException( + "No choices plugin was configured for field \"" + fieldKey + + "\", collection=" + collection.getID().toString() + "."); + } if (ma instanceof AuthorityVariantsSupport) { AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma; return avs.getVariants(metadataValue.getAuthority(), metadataValue.getLanguage()); @@ -190,41 +223,23 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { - String makeFieldKey = makeFieldKey(schema, element, qualifier); - if (getChoiceAuthorityMap().containsKey(makeFieldKey)) { - for (String key : this.authorities.keySet()) { - if (this.authorities.get(key).equals(makeFieldKey)) { - return key; - } - } - } - return configurationService.getProperty( - CHOICES_PLUGIN_PREFIX + schema + "." + element + (qualifier != null ? "." + qualifier : "")); + //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values + return null; } protected String makeFieldKey(String schema, String element, String qualifier) { return Utils.standardize(schema, element, qualifier, "_"); } - /** - * Return map of key to ChoiceAuthority plugin - * - * @return - */ - private Map getChoiceAuthorityMap() { - // If empty, load from configuration - if (controller.isEmpty()) { - loadChoiceAuthorityConfigurations(); - } - - return controller; - } - @Override public void clearCache() { controller.clear(); authorities.clear(); + controllerFormDefinitions.clear(); + authoritiesFormDefinitions.clear(); + initialized = false; } + private void loadChoiceAuthorityConfigurations() { // Get all configuration keys starting with a given prefix List propKeys = configurationService.getPropertyKeys(CHOICES_PLUGIN_PREFIX); @@ -249,17 +264,16 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "Skipping invalid configuration for " + key + " because named plugin not found: " + authorityName); continue; } - if (!authorities.containsKey(authorityName)) { - controller.put(fkey, ma); - authorities.put(authorityName, fkey); - } else { - log.warn( - "Skipping invalid configuration for " + key + " because plugin is alredy in use: " + - authorityName + " used by " + authorities - .get(authorityName)); - continue; - } + controller.put(fkey, ma); + List fkeys; + if (!authorities.containsKey(authorityName)) { + fkeys = authorities.get(authorityName); + } else { + fkeys = new ArrayList(); + } + fkeys.add(fkey); + authorities.put(authorityName, fkeys); log.debug("Choice Control: For field=" + fkey + ", Plugin=" + ma); } autoRegisterChoiceAuthorityFromInputReader(); @@ -267,50 +281,72 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private void autoRegisterChoiceAuthorityFromInputReader() { try { + SubmissionConfigReader itemSubmissionConfigReader = new SubmissionConfigReader(); + List submissionConfigs = itemSubmissionConfigReader + .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); - for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) { - DCInput[][] dcinputs = dcinputSet.getFields(); - for (DCInput[] dcrows : dcinputs) { - for (DCInput dcinput : dcrows) { - if (StringUtils.isNotBlank(dcinput.getPairsType()) - || StringUtils.isNotBlank(dcinput.getVocabulary())) { - String authorityName = dcinput.getPairsType(); - if (StringUtils.isBlank(authorityName)) { - authorityName = dcinput.getVocabulary(); - } - if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { - String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), - dcinput.getQualifier()); - ChoiceAuthority ca = controller.get(authorityName); - if (ca == null) { - InputFormSelfRegisterWrapperAuthority ifa = new - InputFormSelfRegisterWrapperAuthority(); - if (controller.containsKey(fieldKey)) { - ifa = (InputFormSelfRegisterWrapperAuthority) controller.get(fieldKey); + + for (SubmissionConfig subCfg : submissionConfigs) { + String submissionName = subCfg.getSubmissionName(); + List inputsBySubmissionName = dcInputsReader.getInputsBySubmissionName(submissionName); + for (DCInputSet dcinputSet : inputsBySubmissionName) { + DCInput[][] dcinputs = dcinputSet.getFields(); + for (DCInput[] dcrows : dcinputs) { + for (DCInput dcinput : dcrows) { + if (StringUtils.isNotBlank(dcinput.getPairsType()) + || StringUtils.isNotBlank(dcinput.getVocabulary())) { + String authorityName = dcinput.getPairsType(); + if (StringUtils.isBlank(authorityName)) { + authorityName = dcinput.getVocabulary(); + } + if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { + String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), + dcinput.getQualifier()); + ChoiceAuthority ca = controller.get(authorityName); + if (ca == null) { + ca = (ChoiceAuthority) pluginService + .getNamedPlugin(ChoiceAuthority.class, authorityName); + if (ca == null) { + throw new IllegalStateException("Invalid configuration for " + fieldKey + + " in submission definition " + submissionName + + ", form definition " + dcinputSet.getFormName() + + " no named plugin found: " + authorityName); + } } - ChoiceAuthority ma = (ChoiceAuthority) pluginService - .getNamedPlugin(ChoiceAuthority.class, authorityName); - if (ma == null) { - log.warn("Skipping invalid configuration for " + fieldKey - + " because named plugin not found: " + authorityName); - continue; + Map definition2authority; + if (controllerFormDefinitions.containsKey(fieldKey)) { + definition2authority = controllerFormDefinitions.get(fieldKey); } - ifa.getDelegates().put(dcinputSet.getFormName(), ma); - controller.put(fieldKey, ifa); - } + else { + definition2authority = new HashMap(); + } + definition2authority.put(submissionName, ca); + controllerFormDefinitions.put(fieldKey, definition2authority); - if (!authorities.containsKey(authorityName)) { - authorities.put(authorityName, fieldKey); - } + Map> authorityName2definitions; + if (authoritiesFormDefinitions.containsKey(authorityName)) { + authorityName2definitions = authoritiesFormDefinitions.get(authorityName); + } else { + authorityName2definitions = new HashMap>(); + } + List fields; + if (authorityName2definitions.containsKey(submissionName)) { + fields = authorityName2definitions.get(submissionName); + } else { + fields = new ArrayList(); + } + authorityName2definitions.put(submissionName, fields); + authoritiesFormDefinitions.put(fieldKey, authorityName2definitions); + } } } } } } - } catch (DCInputsReaderException e) { - throw new IllegalStateException(e.getMessage(), e); + } catch (SubmissionConfigReaderException | DCInputsReaderException e) { + throw new IllegalStateException(e); } } @@ -381,4 +417,20 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } return ma; } + + private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collection collection) { + init(); + ChoiceAuthority ma = controller.get(fieldKey); + if (ma == null) { + SubmissionConfigReader configReader; + try { + configReader = new SubmissionConfigReader(); + SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); + ma = controllerFormDefinitions.get(submissionName.getSubmissionName()).get(fieldKey); + } catch (SubmissionConfigReaderException e) { + throw new IllegalStateException(e); + } + } + return ma; + } } From 9849d6c41e00257e3721043661934f020ec5fac9 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 17 Jun 2020 14:19:54 +0200 Subject: [PATCH 073/465] fix checkstyle --- .../content/authority/ChoiceAuthorityServiceImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 52ce3f7d25..b06f6f3347 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -223,7 +223,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { - //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values + //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values return null; } @@ -267,7 +267,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService controller.put(fkey, ma); List fkeys; - if (!authorities.containsKey(authorityName)) { + if (authorities.containsKey(authorityName)) { fkeys = authorities.get(authorityName); } else { fkeys = new ArrayList(); @@ -317,8 +317,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService Map definition2authority; if (controllerFormDefinitions.containsKey(fieldKey)) { definition2authority = controllerFormDefinitions.get(fieldKey); - } - else { + } else { definition2authority = new HashMap(); } definition2authority.put(submissionName, ca); From e7ef7d3c5ec75c75b4cb5575e47c464c9539d666 Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Wed, 17 Jun 2020 16:23:54 +0200 Subject: [PATCH 074/465] 71343: Authorization for Downloads of restricted Bitstreams #2 --- .../rest/security/jwt/JWTTokenHandler.java | 101 +++++++++-------- ...JWTTokenRestAuthenticationServiceImpl.java | 27 ++++- .../jwt/ShortLivedJWTTokenHandler.java | 40 +++++++ .../rest/AuthenticationRestControllerIT.java | 102 ++++++++++++++++++ .../security/jwt/JWTTokenHandlerTest.java | 8 +- 5 files changed, 220 insertions(+), 58 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index fa090df7ed..47a869105d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -42,7 +42,6 @@ import org.dspace.service.ClientInfoService; import org.dspace.services.ConfigurationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -55,7 +54,7 @@ import org.springframework.security.crypto.keygen.KeyGenerators; * @author Frederic Van Reet (frederic dot vanreet at atmire dot com) * @author Tom Desair (tom dot desair at atmire dot com) */ -public abstract class JWTTokenHandler implements InitializingBean { +public abstract class JWTTokenHandler { private static final int MAX_CLOCK_SKEW_SECONDS = 60; private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); @@ -75,30 +74,8 @@ public abstract class JWTTokenHandler implements InitializingBean { @Autowired private ClientInfoService clientInfoService; - private String jwtKey; - private long expirationTime; - private boolean includeIP; - private boolean encryptionEnabled; - private boolean compressionEnabled; - private byte[] encryptionKey; - - - @Override - public void afterPropertiesSet() throws Exception { - this.jwtKey = - getSecret(getTokenSecretConfigurationKey()); - this.encryptionKey = - getSecret(getEncryptionSecretConfigurationKey()).getBytes(); - - this.expirationTime = - configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 30); - this.includeIP = - configurationService.getBooleanProperty(getTokenIncludeIPConfigurationKey(), true); - this.encryptionEnabled = - configurationService.getBooleanProperty(getEncryptionEnabledConfigurationKey(), false); - this.compressionEnabled = - configurationService.getBooleanProperty(getCompressionEnabledConfigurationKey(), false); - } + private String generatedJwtKey; + private String generatedEncryptionKey; /** * Get the configuration property key for the token secret. @@ -225,17 +202,54 @@ public abstract class JWTTokenHandler implements InitializingBean { } } - public long getExpirationPeriod() { - return expirationTime; + /** + * Retrieve the token secret key from configuration. If not specified, generate and cache a random 32 byte key + * @return configuration value or random 32 byte key + */ + public String getJwtKey() { + String secret = configurationService.getProperty(getTokenSecretConfigurationKey()); + + if (StringUtils.isBlank(secret)) { + if (StringUtils.isBlank(generatedJwtKey)) { + generatedJwtKey = generateRandomKey(); + } + secret = generatedJwtKey; + } + + return secret; } + public boolean getIncludeIP() { + return configurationService.getBooleanProperty(getTokenIncludeIPConfigurationKey(), true); + } + + public long getExpirationPeriod() { + return configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 30); + } public boolean isEncryptionEnabled() { - return encryptionEnabled; + return configurationService.getBooleanProperty(getEncryptionEnabledConfigurationKey(), false); } + public boolean getCompressionEnabled() { + return configurationService.getBooleanProperty(getCompressionEnabledConfigurationKey(), false); + } + + /** + * Retrieve the encryption secret key from configuration. If not specified, generate and cache a random 32 byte key + * @return configuration value or random 32 byte key + */ public byte[] getEncryptionKey() { - return encryptionKey; + String secretString = configurationService.getProperty(getEncryptionSecretConfigurationKey()); + + if (StringUtils.isBlank(secretString)) { + if (StringUtils.isBlank(generatedEncryptionKey)) { + generatedEncryptionKey = generateRandomKey(); + } + secretString = generatedEncryptionKey; + } + + return secretString.getBytes(); } private JWEObject encryptJWT(SignedJWT signedJWT) throws JOSEException { @@ -261,7 +275,7 @@ public abstract class JWTTokenHandler implements InitializingBean { * @return true if valid, false otherwise * @throws JOSEException */ - private boolean isValidToken(HttpServletRequest request, SignedJWT signedJWT, JWTClaimsSet jwtClaimsSet, + protected boolean isValidToken(HttpServletRequest request, SignedJWT signedJWT, JWTClaimsSet jwtClaimsSet, EPerson ePerson) throws JOSEException { if (ePerson == null || StringUtils.isBlank(ePerson.getSessionSalt())) { return false; @@ -351,7 +365,7 @@ public abstract class JWTTokenHandler implements InitializingBean { //This method makes compression configurable private JWEHeader.Builder compression(JWEHeader.Builder builder) { - if (compressionEnabled) { + if (getCompressionEnabled()) { return builder.compressionAlgorithm(CompressionAlgorithm.DEF); } return builder; @@ -367,12 +381,12 @@ public abstract class JWTTokenHandler implements InitializingBean { * @param ePerson * @return */ - private String buildSigningKey(HttpServletRequest request, EPerson ePerson) { + protected String buildSigningKey(HttpServletRequest request, EPerson ePerson) { String ipAddress = ""; - if (includeIP) { + if (getIncludeIP()) { ipAddress = getIpAddress(request); } - return jwtKey + ePerson.getSessionSalt() + ipAddress; + return getJwtKey() + ePerson.getSessionSalt() + ipAddress; } private String getIpAddress(HttpServletRequest request) { @@ -399,7 +413,7 @@ public abstract class JWTTokenHandler implements InitializingBean { //This allows a user to login on multiple devices/browsers at the same time. if (StringUtils.isBlank(ePerson.getSessionSalt()) || previousLoginDate == null - || (ePerson.getLastActive().getTime() - previousLoginDate.getTime() > expirationTime)) { + || (ePerson.getLastActive().getTime() - previousLoginDate.getTime() > getExpirationPeriod())) { ePerson.setSessionSalt(generateRandomKey()); ePersonService.update(context, ePerson); @@ -412,21 +426,6 @@ public abstract class JWTTokenHandler implements InitializingBean { return ePerson; } - /** - * Retrieve the given secret key from configuration. If not specified, generate a random 32 byte key - * @param property configuration property to check for - * @return configuration value or random 32 byte key - */ - private String getSecret(String property) { - String secret = configurationService.getProperty(property); - - if (StringUtils.isBlank(secret)) { - secret = generateRandomKey(); - } - - return secret; - } - /** * Generate a random 32 bytes key */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index e480154781..f32e221adf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -47,6 +47,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication private static final String AUTHORIZATION_COOKIE = "Authorization-cookie"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORIZATION_TYPE = "Bearer"; + private static final String AUTHORIZATION_TOKEN_PARAMETER = "token"; @Autowired private SessionJWTTokenHandler sessionJWTTokenHandler; @@ -112,9 +113,15 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication @Override public EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context) { - String token = getToken(request); try { - EPerson ePerson = sessionJWTTokenHandler.parseEPersonFromToken(token, request, context); + String token = getSessionToken(request); + EPerson ePerson = null; + if (token == null) { + token = getShortLivedToken(request); + ePerson = shortLivedJWTTokenHandler.parseEPersonFromToken(token, request, context); + } else { + ePerson = sessionJWTTokenHandler.parseEPersonFromToken(token, request, context); + } return ePerson; } catch (JOSEException e) { log.error("Jose error", e); @@ -129,13 +136,14 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication @Override public boolean hasAuthenticationData(HttpServletRequest request) { return StringUtils.isNotBlank(request.getHeader(AUTHORIZATION_HEADER)) - || StringUtils.isNotBlank(getAuthorizationCookie(request)); + || StringUtils.isNotBlank(getAuthorizationCookie(request)) + || StringUtils.isNotBlank(request.getParameter(AUTHORIZATION_TOKEN_PARAMETER)); } @Override public void invalidateAuthenticationData(HttpServletRequest request, HttpServletResponse response, Context context) throws Exception { - String token = getToken(request); + String token = getSessionToken(request); invalidateAuthenticationCookie(response); sessionJWTTokenHandler.invalidateToken(token, request, context); } @@ -192,7 +200,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication response.setHeader(AUTHORIZATION_HEADER, String.format("%s %s", AUTHORIZATION_TYPE, token)); } - private String getToken(HttpServletRequest request) { + private String getSessionToken(HttpServletRequest request) { String tokenValue = null; String authHeader = request.getHeader(AUTHORIZATION_HEADER); String authCookie = getAuthorizationCookie(request); @@ -205,6 +213,15 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication return tokenValue; } + private String getShortLivedToken(HttpServletRequest request) { + String tokenValue = null; + if (StringUtils.isNotBlank(request.getParameter(AUTHORIZATION_TOKEN_PARAMETER))) { + tokenValue = request.getParameter(AUTHORIZATION_TOKEN_PARAMETER); + } + + return tokenValue; + } + private String getAuthorizationCookie(HttpServletRequest request) { String authCookie = ""; Cookie[] cookies = request.getCookies(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index 6610d6f50a..95fd52530c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -7,6 +7,17 @@ */ package org.dspace.app.rest.security.jwt; +import java.util.Date; +import javax.servlet.http.HttpServletRequest; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.MACVerifier; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.jwt.util.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.eperson.EPerson; import org.springframework.stereotype.Component; /** @@ -15,6 +26,35 @@ import org.springframework.stereotype.Component; */ @Component public class ShortLivedJWTTokenHandler extends JWTTokenHandler { + + /** + * Determine if current JWT is valid for the given EPerson object. + * To be valid, current JWT *must* have been signed by the EPerson and not be expired. + * If EPerson is null or does not have a known active session, false is returned immediately. + * @param request current request + * @param signedJWT current signed JWT + * @param jwtClaimsSet claims set of current JWT + * @param ePerson EPerson parsed from current signed JWT + * @return true if valid, false otherwise + * @throws JOSEException + */ + @Override + protected boolean isValidToken(HttpServletRequest request, SignedJWT signedJWT, JWTClaimsSet jwtClaimsSet, + EPerson ePerson) throws JOSEException { + if (ePerson == null || StringUtils.isBlank(ePerson.getSessionSalt())) { + return false; + } else { + JWSVerifier verifier = new MACVerifier(buildSigningKey(request, ePerson)); + + //If token is valid and not expired return eperson in token + Date expirationTime = jwtClaimsSet.getExpirationTime(); + return signedJWT.verify(verifier) + && expirationTime != null + //Ensure expiration timestamp is after the current time, with a minute of acceptable clock skew. + && DateUtils.isAfter(expirationTime, new Date(), 0); + } + } + @Override protected String getTokenSecretConfigurationKey() { return "jwt.shortLived.token.secret"; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 41d397f61b..e69f859983 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -21,14 +21,29 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.InputStream; import java.util.Base64; +import java.util.Map; import javax.servlet.http.Cookie; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.builder.BitstreamBuilder; +import org.dspace.app.rest.builder.BundleBuilder; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.GroupBuilder; +import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.AuthenticationStatusMatcher; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; @@ -36,6 +51,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; /** @@ -774,4 +790,90 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio getClient().perform(post("/api/authn/shortlivedtokens")) .andExpect(status().isUnauthorized()); } + + @Test + public void testShortLivedTokenToDowloadBitstream() throws Exception { + Bitstream bitstream = createPrivateBitstream(); + String shortLivedToken = getShortLivedToken(eperson); + + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + shortLivedToken)) + .andExpect(status().isOk()); + } + + @Test + public void testSessionTokenToDowloadBitstream() throws Exception { + Bitstream bitstream = createPrivateBitstream(); + + String sessionToken = getAuthToken(eperson.getEmail(), password); + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + sessionToken)) + .andExpect(status().isForbidden()); + } + + @Test + public void testExpiredShortLivedTokenToDowloadBitstream() throws Exception { + Bitstream bitstream = createPrivateBitstream(); + configurationService.setProperty("jwt.shortLived.token.expiration", "1"); + String shortLivedToken = getShortLivedToken(eperson); + Thread.sleep(1); + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + shortLivedToken)) + .andExpect(status().isForbidden()); + } + + private String getShortLivedToken(EPerson ePerson) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + String token = getAuthToken(eperson.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/authn/shortlivedtokens")) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + return String.valueOf(map.get("token")); + } + + private Bitstream createPrivateBitstream() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST BUNDLE") + .build(); + + //2. An item restricted to a specific internal group + Group staffGroup = GroupBuilder.createGroup(context) + .withName("Staff") + .addMember(eperson) + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .withReaderGroup(staffGroup) + .build(); + } + + context.restoreAuthSystemState(); + + return bitstream; + } } + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java index 94fb653e6c..6ae4af8293 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java @@ -24,6 +24,7 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.service.ClientInfoService; +import org.dspace.services.ConfigurationService; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,6 +49,9 @@ public class JWTTokenHandlerTest { @Spy SessionJWTTokenHandler sessionJWTTokenHandler; + @Mock + private ConfigurationService configurationService; + @Mock private Context context; @@ -99,7 +103,7 @@ public class JWTTokenHandlerTest { when(sessionJWTTokenHandler.isEncryptionEnabled()).thenReturn(true); Date previous = new Date(System.currentTimeMillis() - 10000000000L); StringKeyGenerator keyGenerator = KeyGenerators.string(); - when(sessionJWTTokenHandler.getEncryptionKey()).thenReturn(keyGenerator.generateKey().getBytes()); + when(configurationService.getProperty("jwt.session.encryption.secret")).thenReturn(keyGenerator.generateKey()); String token = sessionJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); SignedJWT signedJWT = SignedJWT.parse(token); @@ -108,7 +112,7 @@ public class JWTTokenHandlerTest { //temporary set a negative expiration time so the token is invalid immediately @Test public void testExpiredToken() throws Exception { - when(sessionJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); + when(configurationService.getLongProperty("jwt.session.token.expiration", 30)).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = sessionJWTTokenHandler From 4784d34066d32c1f75ce1afebfc36d6681118e16 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 17 Jun 2020 18:22:13 +0200 Subject: [PATCH 075/465] renamed classes --- ...nverter.java => VocabularyEntryDetailsRestConverter.java} | 2 +- ...horityRestConverter.java => VocabularyRestConverter.java} | 2 +- ...ntryResource.java => VocabularyEntryDetailsResource.java} | 4 ++-- .../{AuthorityResource.java => VocabularyResource.java} | 4 ++-- .../app/rest/repository/VocabularyEntryLinkRepository.java | 1 - .../src/main/java/org/dspace/app/rest/utils/Utils.java | 5 ++++- ...yVocabularyEntryIT.java => VocabularyEntryDetailsIT.java} | 2 +- ...RestRepositoryIT.java => VocabularyRestRepositoryIT.java} | 0 8 files changed, 11 insertions(+), 9 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{AuthorityEntryRestConverter.java => VocabularyEntryDetailsRestConverter.java} (92%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{AuthorityRestConverter.java => VocabularyRestConverter.java} (93%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{AuthorityEntryResource.java => VocabularyEntryDetailsResource.java} (80%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{AuthorityResource.java => VocabularyResource.java} (85%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{AuthorityVocabularyEntryIT.java => VocabularyEntryDetailsIT.java} (99%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{AuthorityRestRepositoryIT.java => VocabularyRestRepositoryIT.java} (100%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java index d50ed6fc9f..63154ccf22 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityEntryRestConverter implements DSpaceConverter { +public class VocabularyEntryDetailsRestConverter implements DSpaceConverter { @Override public VocabularyEntryDetailsRest convert(Choice choice, Projection projection) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyRestConverter.java similarity index 93% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyRestConverter.java index f481568746..5dcb05a23e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyRestConverter.java @@ -23,7 +23,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityRestConverter implements DSpaceConverter { +public class VocabularyRestConverter implements DSpaceConverter { @Override public VocabularyRest convert(ChoiceAuthority authority, Projection projection) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java similarity index 80% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java index e7b0e3e78c..f307992ff8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java @@ -17,10 +17,10 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME) -public class AuthorityEntryResource extends HALResource { +public class VocabularyEntryDetailsResource extends HALResource { - public AuthorityEntryResource(VocabularyEntryDetailsRest entry) { + public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry) { super(entry); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyResource.java similarity index 85% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyResource.java index 6152f498fd..4a2ec01d33 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyResource.java @@ -18,8 +18,8 @@ import org.dspace.app.rest.utils.Utils; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @RelNameDSpaceResource(VocabularyRest.NAME) -public class AuthorityResource extends DSpaceResource { - public AuthorityResource(VocabularyRest sd, Utils utils) { +public class VocabularyResource extends DSpaceResource { + public VocabularyResource(VocabularyRest sd, Utils utils) { super(sd, utils); add(utils.linkToSubResource(sd, VocabularyRest.ENTRIES)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index a262cad683..feb593ae5b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -16,7 +16,6 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyEntryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index 7f8cfef8de..b91e28519e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -47,7 +47,6 @@ import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryNotFoundException; -import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.LinkRest; @@ -58,6 +57,7 @@ import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.VersionHistoryRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.EmbeddedPage; import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.projection.CompositeProjection; @@ -268,6 +268,9 @@ public class Utils { if (StringUtils.equals(modelPlural, "properties")) { return PropertyRest.NAME; } + if (StringUtils.equals(modelPlural, "vocabularies")) { + return VocabularyRest.NAME; + } return modelPlural.replaceAll("s$", ""); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index f78add992b..72c101ab03 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; -public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTest { +public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { @Test public void findOneTest() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java similarity index 100% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java From d7ce794eab7ee7a658e9fb99734f9fd1705283ed Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 18 Jun 2020 10:14:56 +0200 Subject: [PATCH 076/465] fix creation of the internal maps of authorities --- .../authority/ChoiceAuthorityServiceImpl.java | 2 +- .../content/authority/DCInputAuthority.java | 5 +++++ .../app/rest/VocabularyRestRepositoryIT.java | 21 +++++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index b06f6f3347..2f454ac612 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -337,7 +337,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService fields = new ArrayList(); } authorityName2definitions.put(submissionName, fields); - authoritiesFormDefinitions.put(fieldKey, authorityName2definitions); + authoritiesFormDefinitions.put(authorityName, authorityName2definitions); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index a64ebdd971..5bb2251eb3 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -146,4 +146,9 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority return "UNKNOWN KEY " + key; } } + + @Override + public boolean isScrollable() { + return true; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index ec5566ae9f..375822f4a4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; * This class handles all Authority related IT. It alters some config to run the tests, but it gets cleared again * after every test */ -public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest { +public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired ConfigurationService configurationService; @@ -100,11 +100,12 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder( VocabularyMatcher.matchProperties("srsc", "srsc", false, true), VocabularyMatcher.matchProperties("common_types", "common_types", true, false), - VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false) + VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false), + VocabularyMatcher.matchProperties("SolrAuthorAuthority", "SolrAuthorAuthority", false , false) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/submission/vocabularies"))) - .andExpect(jsonPath("$.page.totalElements", is(3))); + .andExpect(jsonPath("$.page.totalElements", is(4))); } @Test @@ -112,10 +113,9 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.contains( - VocabularyMatcher.matchProperties("srsc", "srsc", false, true) - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is( + VocabularyMatcher.matchProperties("srsc", "srsc", false, true) + ))); } @Test @@ -123,10 +123,9 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/common_types")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.contains( - VocabularyMatcher.matchProperties("common_types", "common_types", true, false) - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))); } @Test From 3e8b2f87b66f61b66c12ec41e11caa6a5a19ca5c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:28:48 +0200 Subject: [PATCH 077/465] Remove invalid test, cleanup --- .../app/rest/VocabularyEntryDetailsIT.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 72c101ab03..2fbd529a9d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -37,13 +37,6 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); } - @Test - public void findOneBadRequestTest() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID().toString())) - .andExpect(status().isBadRequest()); - } - public void findOneUnauthorizedTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) @@ -96,7 +89,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), @@ -173,7 +166,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void retrieveSrscValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "SCB1922") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1922") .param("projection", "full")) .andExpect(status().isOk()); } @@ -182,7 +175,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest public void srscSearchByParentFirstLevelPaginationTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); // first page - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children") .param("page", "0") .param("size", "2")) .andExpect(status().isOk()) @@ -195,7 +188,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.page.number", is(0))); // second page - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children") .param("page", "1") .param("size", "2")) .andExpect(status().isOk()) @@ -210,7 +203,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), @@ -224,7 +217,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchByParentEmptyTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } @@ -248,7 +241,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchParentByChildrenTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") @@ -259,7 +252,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchParentByChildrenRootTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } From be6f7f3bfd2edc3cd1513b075ab444a4fdae6f99 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:38:12 +0200 Subject: [PATCH 078/465] Update default to reflect the rest contract the visualization is now responsability of the angular UI so it is better by default present a clean label as the hierarchy is still visible in the otherInformation --- .../authority/DSpaceControlledVocabulary.java | 19 ++++++++++++++++--- dspace/config/dspace.cfg | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 40b1e6b73d..d97e06f250 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -65,7 +65,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic protected String vocabularyName = null; protected InputSource vocabulary = null; - protected Boolean suggestHierarchy = true; + protected Boolean suggestHierarchy = false; protected Boolean storeHierarchy = true; protected String hierarchyDelimiter = "::"; @@ -194,13 +194,26 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { + return getLabel(key, this.suggestHierarchy); + } + + @Override + public String getValue(String key, String locale) { + return getLabel(key, this.storeHierarchy); + } + + private String getLabel(String key, boolean useHierarchy) { init(); String xpathExpression = String.format(idTemplate, key); XPath xpath = XPathFactory.newInstance().newXPath(); try { Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - return node.getAttributes().getNamedItem("label").getNodeValue(); + if (useHierarchy) { + return this.buildString(node); + } else { + return node.getAttributes().getNamedItem("label").getNodeValue(); + } } catch (XPathExpressionException e) { return (""); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4311887f96..c07cd72df1 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1473,7 +1473,7 @@ orcid.url = https://orcid.org/ ## eg: nsi, srsc. ## Each DSpaceControlledVocabulary plugin comes with three configuration options: # vocabulary.plugin._plugin_.hierarchy.store = # default: true -# vocabulary.plugin._plugin_.hierarchy.suggest = # default: true +# vocabulary.plugin._plugin_.hierarchy.suggest = # default: false # vocabulary.plugin._plugin_.delimiter = "" # default: "::" ## ## An example using "srsc" can be found later in this section From ec3df2e523bed760fab64005ed879f147170e6c7 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:42:59 +0200 Subject: [PATCH 079/465] Fix test setup, update response code according to the contract --- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 375822f4a4..c5cca5cf46 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.UUID; import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.matcher.VocabularyMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authority.PersonAuthorityValue; @@ -69,6 +70,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes pluginService.clearNamedPluginClasses(); cas.clearCache(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("A parent community for all our test") + .build(); + context.restoreAuthSystemState(); PersonAuthorityValue person1 = new PersonAuthorityValue(); person1.setId(String.valueOf(UUID.randomUUID())); person1.setLastName("Shirasaka"); @@ -165,7 +170,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.subject") .param("collection", UUID.randomUUID().toString()) .param("query", "Research")) - .andExpect(status().isBadRequest()); + .andExpect(status().isUnprocessableEntity()); } @Test @@ -337,7 +342,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void findByMetadataAndCollectionUnprocessableEntityTest() throws Exception { context.turnOffAuthorisationSystem(); - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Test collection") .build(); From 94581761b91f703129d50803e5d0ec8c2991d031 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:55:40 +0200 Subject: [PATCH 080/465] Fix typos --- .../org/dspace/app/rest/model/VocabularyEntryDetailsRest.java | 4 ++-- .../java/org/dspace/app/rest/model/VocabularyEntryRest.java | 3 +-- .../rest/model/hateoas/VocabularyEntryDetailsResource.java | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 0098ea6aa4..e41ddafe45 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -18,7 +18,7 @@ import org.dspace.app.rest.RestResourceController; * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class VocabularyEntryDetailsRest extends RestAddressableModel { - public static final String NAME = "vocabularyDetailEntry"; + public static final String NAME = "vocabularyEntryDetail"; private String id; private String display; private String value; @@ -78,7 +78,7 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { @Override public String getType() { - return VocabularyRest.NAME; + return VocabularyEntryDetailsRest.NAME; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index 27d5a4c75e..f9855828ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.model; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.dspace.app.rest.RestResourceController; /** * An entry in a Vocabulary @@ -23,7 +22,7 @@ public class VocabularyEntryRest { private String display; private String value; private Map otherInformation; - + /** * The Vocabulary Entry Details resource if available related to this entry */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java index f307992ff8..3ba40ffa63 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java @@ -11,7 +11,7 @@ import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; /** - * Authority Rest HAL Resource. The HAL Resource wraps the REST Resource adding + * Vocabulary Entry Details Rest HAL Resource. The HAL Resource wraps the REST Resource adding * support for the links and embedded resources * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) @@ -19,7 +19,6 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; @RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME) public class VocabularyEntryDetailsResource extends HALResource { - public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry) { super(entry); } From ae60714f7d4035019f4581d39baa365333586d28 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:55:58 +0200 Subject: [PATCH 081/465] Align test with the contract --- .../app/rest/VocabularyRestRepositoryIT.java | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index c5cca5cf46..0e8b127494 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -147,7 +147,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) - .param("query", "Research") + .param("filter", "Research") .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( @@ -169,7 +169,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", UUID.randomUUID().toString()) - .param("query", "Research")) + .param("filter", "Research")) .andExpect(status().isUnprocessableEntity()); } @@ -178,7 +178,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") - .param("query", "Research")) + .param("filter", "Research")) .andExpect(status().isBadRequest()); } @@ -192,10 +192,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") + .param("metadata", "dc.type") .param("collection", collection.getID().toString()) - .param("query", "Research")) - .andExpect(status().isBadRequest()); + .param("filter", "Research")) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -227,7 +227,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) - .param("query", "Research2") + .param("filter", "Research2") .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); @@ -270,7 +270,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") .param("metadata", "dc.type") .param("collection", collection.getID().toString()) - .param("query", "Book") + .param("filter", "Book") .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( @@ -295,7 +295,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) - .param("query", "Shirasaka") + .param("filter", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); @@ -314,7 +314,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) - .param("query", "Smith") + .param("filter", "Smith") .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); @@ -379,17 +379,4 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.type")) .andExpect(status().isUnprocessableEntity()); } - - @Test - public void discoverableNestedLinkTest() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._links",Matchers.allOf( - hasJsonPath("$.authorizations.href", - is("http://localhost/api/authz/authorizations")), - hasJsonPath("$.authorization-search.href", - is("http://localhost/api/authz/authorization/search")) - ))); - } } From f44b28a8f5855dcf89f0815544be03e73e228456 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:56:23 +0200 Subject: [PATCH 082/465] Implement check for mandatory parameters --- .../app/rest/repository/VocabularyEntryLinkRepository.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index feb593ae5b..967124624a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -57,7 +57,11 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository Context context = obtainContext(); String filter = request == null ? null : request.getParameter("filter"); String metadata = request == null ? null : request.getParameter("metadata"); - String uuidCollectìon = request == null ? null : request.getParameter("uuid"); + String uuidCollectìon = request == null ? null : request.getParameter("collection"); + + if (StringUtils.isEmpty(metadata) || StringUtils.isEmpty(uuidCollectìon)) { + throw new IllegalArgumentException("the metadata and collection parameters are both required"); + } Collection collection = null; if (StringUtils.isNotBlank(uuidCollectìon)) { From d38dfdb31fe92592f34f7a91c29c5a79737a784c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:56:44 +0200 Subject: [PATCH 083/465] Add support for : in string identifier --- .../java/org/dspace/app/rest/RestResourceController.java | 8 ++++---- .../main/java/org/dspace/app/rest/utils/RegexUtils.java | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index a1684d782e..025f7ad03b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -149,7 +149,7 @@ public class RestResourceController implements InitializingBean { * @return single DSpaceResource */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) - public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, + public HALResource findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id) { return findOneInternal(apiCategory, model, id); } @@ -180,7 +180,7 @@ public class RestResourceController implements InitializingBean { * @return single DSpaceResource */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG) - public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, + public HALResource findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable String id) { return findOneInternal(apiCategory, model, id); } @@ -200,7 +200,7 @@ public class RestResourceController implements InitializingBean { * @return single DSpaceResource */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) - public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, + public HALResource findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid) { return findOneInternal(apiCategory, model, uuid); } @@ -213,7 +213,7 @@ public class RestResourceController implements InitializingBean { * @param id Identifier from request * @return single DSpaceResource */ - private DSpaceResource findOneInternal(String apiCategory, + private HALResource findOneInternal(String apiCategory, String model, ID id) { DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); Optional modelObject = Optional.empty(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java index 8e887261db..ea0b793055 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java @@ -28,7 +28,8 @@ public class RegexUtils { * identifier (digits or uuid) */ public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\d+$)" + - "(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)[\\w+\\-\\.]+$+}"; + "(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)" + + "[\\w+\\-\\.:]+$+}"; /** * Regular expression in the request mapping to accept number as identifier From 61c346089e14429b8208ddc7c9a3a38eef94c972 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:58:13 +0200 Subject: [PATCH 084/465] Cleanup authority service implementation --- .../content/authority/ChoiceAuthority.java | 23 +++++++++- .../authority/ChoiceAuthorityServiceImpl.java | 45 +++++++++++++++++-- .../content/authority/DCInputAuthority.java | 2 +- .../content/authority/SampleAuthority.java | 2 +- .../content/authority/SolrAuthority.java | 2 +- .../content/authority/TestAuthority.java | 2 +- .../dspace/app/rest/utils/AuthorityUtils.java | 3 +- 7 files changed, 68 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index c77799f83f..0fbab49bac 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -67,12 +67,23 @@ public interface ChoiceAuthority { * This may get called many times while populating a Web page so it should * be implemented as efficiently as possible. * - * @param field being matched for * @param key authority key known to this authority. * @param locale explicit localization key if available, or null * @return descriptive label - should always return something, never null. */ - public String getLabel(String field, String key, String locale); + public String getLabel(String key, String locale); + + /** + * Get the canonical value to store for a key in the authority. Can be localized + * given the implicit or explicit locale specification. + * + * @param key authority key known to this authority. + * @param locale explicit localization key if available, or null + * @return value to store - should always return something, never null. + */ + default String getValue(String key, String locale) { + return getLabel(key, locale); + } default boolean isHierarchical() { return false; @@ -85,4 +96,12 @@ public interface ChoiceAuthority { default Integer getPreloadLevel() { return isHierarchical() ? 0 : null; } + + default public Choice getChoice(String authKey, String locale) { + Choice result = new Choice(); + result.authority = authKey; + result.label = getLabel(authKey, locale); + result.value = getValue(authKey, locale); + return result; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 2f454ac612..a73bf525e2 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -79,6 +80,9 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService protected Map>> authoritiesFormDefinitions = new HashMap>>(); + // the item submission reader + private SubmissionConfigReader itemSubmissionConfigReader; + @Autowired(required = true) protected ConfigurationService configurationService; @Autowired(required = true) @@ -121,6 +125,12 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private synchronized void init() { if (!initialized) { + try { + itemSubmissionConfigReader = new SubmissionConfigReader(); + } catch (SubmissionConfigReaderException e) { + // the system is in an illegal state as the submission definition is not valid + throw new IllegalStateException(e); + } loadChoiceAuthorityConfigurations(); initialized = true; } @@ -186,7 +196,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "No choices plugin was configured for field \"" + fieldKey + "\", collection=" + collection.getID().toString() + "."); } - return ma.getLabel(fieldKey, authKey, locale); + return ma.getLabel(authKey, locale); } @Override @@ -223,7 +233,32 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { - //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values + String fieldKey = makeFieldKey(schema, element, qualifier); + // check if there is an authority configured for the metadata valid for all the collections + if (controller.containsKey(fieldKey)) { + for (Entry> authority2md : authorities.entrySet()) { + if (authority2md.getValue().contains(fieldKey)) { + return authority2md.getKey(); + } + } + } else if (collection != null && controllerFormDefinitions.containsKey(fieldKey)) { + // there is an authority configured for the metadata valid for some collections, + // check if it is the requested collection + Map controllerFormDef = controllerFormDefinitions.get(fieldKey); + SubmissionConfig submissionConfig = itemSubmissionConfigReader + .getSubmissionConfigByCollection(collection.getHandle()); + String submissionName = submissionConfig.getSubmissionName(); + // check if the requested collection has a submission definition that use an authority for the metadata + if (controllerFormDef.containsKey(submissionName)) { + for (Entry>> authority2defs2md : + authoritiesFormDefinitions.entrySet()) { + List mdByDefinition = authority2defs2md.getValue().get(submissionName); + if (mdByDefinition != null && mdByDefinition.contains(fieldKey)) { + return authority2defs2md.getKey(); + } + } + } + } return null; } @@ -237,6 +272,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService authorities.clear(); controllerFormDefinitions.clear(); authoritiesFormDefinitions.clear(); + itemSubmissionConfigReader = null; initialized = false; } @@ -281,7 +317,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private void autoRegisterChoiceAuthorityFromInputReader() { try { - SubmissionConfigReader itemSubmissionConfigReader = new SubmissionConfigReader(); List submissionConfigs = itemSubmissionConfigReader .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); @@ -344,7 +379,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } } } - } catch (SubmissionConfigReaderException | DCInputsReaderException e) { + } catch (DCInputsReaderException e) { + // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException(e); } } @@ -427,6 +463,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); ma = controllerFormDefinitions.get(submissionName.getSubmissionName()).get(fieldKey); } catch (SubmissionConfigReaderException e) { + // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException(e); } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 5bb2251eb3..42e79820c7 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -131,7 +131,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { init(); int pos = -1; for (int i = 0; i < values.length; i++) { diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java index 8197f180af..0aaf8e9003 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java @@ -60,7 +60,7 @@ public class SampleAuthority implements ChoiceAuthority { } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { return labels[Integer.parseInt(key)]; } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java index 5e913430b7..efc910a761 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java @@ -207,7 +207,7 @@ public class SolrAuthority implements ChoiceAuthority { } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { try { if (log.isDebugEnabled()) { log.debug("requesting label for key " + key + " using locale " + locale); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java index a017e8fe28..1bb3fa5450 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java @@ -70,7 +70,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { if (StringUtils.isNotBlank(key)) { return key.replaceAll("authority", "label"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index df0308fd41..1d15693f72 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -66,9 +66,10 @@ public class AuthorityUtils { public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, Projection projection) { VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); entry.setVocabularyName(authorityName); + entry.setId(authorityName + ":" + entry.getId()); return entry; } - + public VocabularyEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { VocabularyEntryRest entry = new VocabularyEntryRest(); entry.setDisplay(choice.label); From e9abbc505b635120e25d6827063fc1b938fb2243 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:59:02 +0200 Subject: [PATCH 085/465] Add initial implementation for VocabularyEntryDetails repository --- .../VocabularyEntryDetailsRestRepository.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java new file mode 100644 index 0000000000..0e4c39b7ab --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -0,0 +1,64 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.ResourcePolicyRest; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyRest; +import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Controller for exposition of vocabularies entry details for the submission + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME) +public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository { + + @Autowired + private ChoiceAuthorityService cas; + + @Autowired + private AuthorityUtils authorityUtils; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll"); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public VocabularyEntryDetailsRest findOne(Context context, String name) { + String[] parts = StringUtils.split(name, ":", 2); + if (parts.length != 2) { + return null; + } + String vocabularyName = parts[0]; + String vocabularyId = parts[1]; + ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(vocabularyName); + Choice choice = source.getChoice(vocabularyId, context.getCurrentLocale().toString()); + return authorityUtils.convertEntryDetails(choice, vocabularyName, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return VocabularyEntryDetailsRest.class; + } +} From 53765394803768fd5639323a30401d2d502da9fb Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 18 Jun 2020 22:07:04 +0200 Subject: [PATCH 086/465] missing filling list of metadata --- .../dspace/content/authority/ChoiceAuthorityServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index a73bf525e2..a36bd653c0 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -233,6 +233,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { + init(); String fieldKey = makeFieldKey(schema, element, qualifier); // check if there is an authority configured for the metadata valid for all the collections if (controller.containsKey(fieldKey)) { @@ -371,6 +372,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } else { fields = new ArrayList(); } + fields.add(fieldKey); authorityName2definitions.put(submissionName, fields); authoritiesFormDefinitions.put(authorityName, authorityName2definitions); } From 1114903645deee131620eca0c95368560266dad4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 18 Jun 2020 22:07:43 +0200 Subject: [PATCH 087/465] refactored tests --- .../dspace/app/rest/VocabularyRestRepositoryIT.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 0e8b127494..30de227ec2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest; -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -333,10 +332,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.type") .param("collection", collection.getID().toString())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.contains( - VocabularyMatcher.matchProperties("common_types", "common_types", true, false) - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))); } @Test @@ -377,6 +375,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes //missing collection getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } } From 9b2451754676740535841001593b6be431e8c969 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 23:49:25 +0200 Subject: [PATCH 088/465] Fix obviously wrong implementation --- .../SubmissionUploadRestRepository.java | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java index 3ea5989f5a..a359a7ec4f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -16,10 +17,6 @@ import org.dspace.app.rest.model.AccessConditionOptionRest; import org.dspace.app.rest.model.SubmissionUploadRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.DateMathParser; -import org.dspace.app.util.SubmissionConfig; -import org.dspace.app.util.SubmissionConfigReader; -import org.dspace.app.util.SubmissionConfigReaderException; -import org.dspace.app.util.SubmissionStepConfig; import org.dspace.core.Context; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; @@ -28,7 +25,6 @@ import org.dspace.submit.model.UploadConfiguration; import org.dspace.submit.model.UploadConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -45,8 +41,6 @@ public class SubmissionUploadRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List subConfs = new ArrayList(); - subConfs = submissionConfigReader.getAllSubmissionConfigs(pageable.getPageSize(), - Math.toIntExact(pageable.getOffset())); + Collection uploadConfigs = uploadConfigurationService.getMap().values(); Projection projection = utils.obtainProjection(); List results = new ArrayList<>(); - for (SubmissionConfig config : subConfs) { - for (int i = 0; i < config.getNumberOfSteps(); i++) { - SubmissionStepConfig step = config.getStep(i); - if (SubmissionStepConfig.UPLOAD_STEP_NAME.equals(step.getType())) { - UploadConfiguration uploadConfig = uploadConfigurationService.getMap().get(step.getId()); - if (uploadConfig != null) { - try { - results.add(convert(context, uploadConfig, projection)); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } + List configNames = new ArrayList(); + for (UploadConfiguration uploadConfig : uploadConfigs) { + if (!configNames.contains(uploadConfig.getName())) { + configNames.add(uploadConfig.getName()); + try { + results.add(convert(context, uploadConfig, projection)); + } catch (Exception e) { + log.error(e.getMessage(), e); } } } - return new PageImpl(results, pageable, results.size()); + return utils.getPage(results, pageable); } @Override From e4d61191e61ddf7f994c6fea4c4b699ead688488 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 23:52:49 +0200 Subject: [PATCH 089/465] Disable test failing due to bug in the Mock library --- .../ContentLanguageHeaderResponseFilter.java | 23 +++++++++---------- .../dspace/app/rest/LanguageSupportIT.java | 5 +++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java index 6552ae4f7f..74ffd73ad4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java @@ -22,7 +22,9 @@ import org.springframework.stereotype.Component; /** * This filter assures that when the dspace instance supports multiple languages - * they are noted in the Content-Language Header of the response + * they are noted in the Content-Language Header of the response. Where + * appropriate the single endpoint can set the Content-Language header directly + * to note that the response is specific for a language * * @author Mykhaylo Boychuk (at 4science.it) */ @@ -36,20 +38,17 @@ public class ContentLanguageHeaderResponseFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - chain.doFilter(request, response); - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - if (!httpServletResponse.containsHeader("Content-Language")) { - Locale[] locales = I18nUtil.getSupportedLocales(); - StringBuilder locsStr = new StringBuilder(); - for (Locale locale : locales) { - if (locsStr.length() > 0) { - locsStr.append(","); - } - locsStr.append(locale.getLanguage()); + Locale[] locales = I18nUtil.getSupportedLocales(); + StringBuilder locsStr = new StringBuilder(); + for (Locale locale : locales) { + if (locsStr.length() > 0) { + locsStr.append(","); } - httpServletResponse.setHeader("Content-Language", locsStr.toString()); + locsStr.append(locale.getLanguage()); } + httpServletResponse.setHeader("Content-Language", locsStr.toString()); + chain.doFilter(request, response); } @Override diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java index 2dc06eca8b..cc8af92e2b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java @@ -18,6 +18,7 @@ import org.dspace.content.authority.ChoiceAuthorityServiceImpl; import org.dspace.core.LegacyPluginServiceImpl; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -42,6 +43,8 @@ public class LanguageSupportIT extends AbstractControllerIntegrationTest { } @Test + @Ignore("This test fails due to a bug in the MockHttpResponseServlet," + + " see https://github.com/spring-projects/spring-framework/issues/25281") public void checkEnabledMultipleLanguageSupportTest() throws Exception { context.turnOffAuthorisationSystem(); String[] supportedLanguage = {"uk","it"}; @@ -75,7 +78,7 @@ public class LanguageSupportIT extends AbstractControllerIntegrationTest { .andExpect(header().stringValues("Content-Language","uk, it")); getClient(tokenEPersonFR).perform(get("/api").locale(it)) - .andExpect(header().stringValues("Content-Language","en")); + .andExpect(header().stringValues("Content-Language","uk, it")); configurationService.setProperty("webui.supported.locales",null); legacyPluginService.clearNamedPluginClasses(); From ea9d5da7644f73e66a2f1dadfb304879949c13f0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 09:35:24 +0200 Subject: [PATCH 090/465] implemented search method byMetadataAndCollection --- .../repository/VocabularyRestRepository.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index 179898e2d1..6ea08cafd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -7,15 +7,24 @@ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.UUID; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.Collection; +import org.dspace.content.MetadataField; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -38,6 +47,12 @@ public class VocabularyRestRepository extends DSpaceRestRepository getDomainClass() { return VocabularyRest.class; From c23a5b64de597dc78212522b775289e530470ea2 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Fri, 19 Jun 2020 09:35:40 +0200 Subject: [PATCH 091/465] Improve error handling and test env clenaup --- .../SubmissionUploadRestRepository.java | 35 +++++++++++++++---- .../app/rest/SubmissionFormsControllerIT.java | 11 +++--- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java index a359a7ec4f..25ac640d49 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -89,20 +91,31 @@ public class SubmissionUploadRestRepository extends DSpaceRestRepository Date: Fri, 19 Jun 2020 09:56:34 +0200 Subject: [PATCH 092/465] [Task 71442] applied feedback to the new registration endpoint --- .../app/rest/EPersonRestRepositoryIT.java | 24 +++++++++++-------- .../EPersonRegistrationFeatureIT.java | 7 ------ .../jackson/IgnoreJacksonWriteOnlyAccess.java | 11 +++++++++ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index ebbb26ad7a..b904429e1f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -2071,18 +2071,25 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isCreated()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); - // We need to create this json manually to support actually setting the password to a value. - // When using the mapper to write an EPersonRest object to JSON to pass it along, the password gets lost - String json = "{\"metadata\":{\"eperson.firstname\":[{\"value\":\"John\"}]," + - "\"eperson.lastname\":[{\"value\":\"Doe\"}]},\"password\":\"somePassword\"," + - "\"type\":\"eperson\"}"; - + EPersonRest ePersonRest = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + ePersonRest.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + ePersonRest.setMetadata(metadataRest); + ePersonRest.setPassword("somePassword"); AtomicReference idRef = new AtomicReference(); + mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); + try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) - .content(json) + .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf( @@ -2233,9 +2240,6 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); assertNull(registrationDataService.findByToken(context, newRegisterToken)); - context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, newRegisterToken); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java index 754804edf4..528ab24eb2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonRegistrationFeatureIT.java @@ -76,18 +76,13 @@ public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationT SiteRest SiteRest = siteConverter.convert(site, Projection.DEFAULT); String siteUri = utils.linkToSingleResource(SiteRest, "self").getHref(); - context.turnOffAuthorisationSystem(); configurationService.setProperty("user.registration", false); - context.restoreAuthSystemState(); getClient().perform(get("/api/authz/authorizations/search/objectAndFeature") .param("uri", siteUri) .param("feature", epersonRegistrationFeature.getName())) .andExpect(status().isNoContent()); - context.turnOffAuthorisationSystem(); - configurationService.setProperty("user.registration", true); - context.restoreAuthSystemState(); } @@ -104,10 +99,8 @@ public class EPersonRegistrationFeatureIT extends AbstractControllerIntegrationT .param("feature", epersonRegistrationFeature.getName())) .andExpect(status().isOk()); - context.turnOffAuthorisationSystem(); //Enable Shibboleth and password login configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY); - context.restoreAuthSystemState(); getClient().perform(get("/api/authz/authorizations/search/objectAndFeature") .param("uri", siteUri) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java index 577ed6e4d5..df68ce8ab3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/jackson/IgnoreJacksonWriteOnlyAccess.java @@ -11,6 +11,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +/** + * This is a custom JacksonAnnotationIntrospector which allows us to ignore `@JsonProperty(access = Access + * .WRITE_ONLY)` annotations in our tests. + * Normally, this annotation allows the property to be written to (during deserialization), + * but does NOT allow it to be read (during serialization). + * In some tests, we need to ignore this annotation so that the test can use/verify the property + * during both serialization & deserialization. + * + * In order to use this class in a test, assign it the the current mapper like this: + * mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); + */ public class IgnoreJacksonWriteOnlyAccess extends JacksonAnnotationIntrospector { @Override From de6bc7d8d024b88d9b69da5032c0ba7edb9c854c Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 19 Jun 2020 10:18:28 +0200 Subject: [PATCH 093/465] [Task 71442] fixed compile error after merge --- .../EPersonRestPermissionEvaluatorPlugin.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java index 14bde8e666..50f209cedc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.security; import java.io.Serializable; +import java.sql.SQLException; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -69,16 +70,20 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv UUID dsoId = UUID.fromString(targetId.toString()); // anonymous user - if (ePerson == null) { - return false; + try { + if (ePerson == null) { + return false; } else if (dsoId.equals(ePerson.getID())) { return true; } else if (authorizeService.isCommunityAdmin(context, ePerson) - && AuthorizeUtil.canCommunityAdminManageAccounts()) { + && AuthorizeUtil.canCommunityAdminManageAccounts()) { return true; } else if (authorizeService.isCollectionAdmin(context, ePerson) - && AuthorizeUtil.canCollectionAdminManageAccounts()) { - return true; + && AuthorizeUtil.canCollectionAdminManageAccounts()) { + return true; + } + } catch (SQLException e) { + log.error(e.getMessage(), e); } From b4b56750fd95146dae01f322ac8ae174b5a106d4 Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Fri, 19 Jun 2020 10:25:17 +0200 Subject: [PATCH 094/465] 71410: Authorization for Downloads of restricted Bitstreams - Internal feedback --- .../rest/AuthenticationRestController.java | 7 +- .../AuthenticationTokenConverter.java | 13 ++- .../model/wrapper/AuthenticationToken.java | 28 +++++ .../rest/security/CustomLogoutHandler.java | 3 +- .../security/RestAuthenticationService.java | 3 +- .../rest/security/jwt/JWTTokenHandler.java | 2 +- ...JWTTokenRestAuthenticationServiceImpl.java | 11 +- .../jwt/ShortLivedJWTTokenHandler.java | 2 +- .../security/jwt/JWTTokenHandlerTest.java | 20 ++-- .../jwt/ShortLivedJWTTokenHandlerTest.java | 106 ++++++++++++++++++ dspace/config/modules/authentication.cfg | 1 + 11 files changed, 167 insertions(+), 29 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/AuthenticationToken.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index f1f8d495c3..0b60fa1cd4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -22,6 +22,7 @@ import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource; import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource; import org.dspace.app.rest.model.hateoas.AuthnResource; +import org.dspace.app.rest.model.wrapper.AuthenticationToken; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.app.rest.utils.ContextUtil; @@ -122,7 +123,7 @@ public class AuthenticationRestController implements InitializingBean { } /** - * This method will generate a short lived token to be used for bitstream downloads. + * This method will generate a short lived token to be used for bitstream downloads among other things. * * curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo" * @@ -139,9 +140,9 @@ public class AuthenticationRestController implements InitializingBean { @RequestMapping(value = "/shortlivedtokens", method = RequestMethod.POST) public AuthenticationTokenResource shortLivedLogin(HttpServletRequest request) { Projection projection = utils.obtainProjection(); - String shortLivedToken = + AuthenticationToken shortLivedToken = restAuthenticationService.getShortLivedAuthenticationToken(ContextUtil.obtainContext(request), request); - AuthenticationTokenRest authenticationTokenRest = converter.toRest(shortLivedToken, projection); + AuthenticationTokenRest authenticationTokenRest = converter.toRest(shortLivedToken, projection); return converter.toResource(authenticationTokenRest); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java index a9f9d7208b..ea64bc8bc8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthenticationTokenConverter.java @@ -8,23 +8,24 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.AuthenticationTokenRest; +import org.dspace.app.rest.model.wrapper.AuthenticationToken; import org.dspace.app.rest.projection.Projection; import org.springframework.stereotype.Component; /** - * This is the converter from the AuthenticationToken string to tge REST data model + * This is the converter from the AuthenticationToken to the REST data model */ @Component -public class AuthenticationTokenConverter implements DSpaceConverter { +public class AuthenticationTokenConverter implements DSpaceConverter { @Override - public AuthenticationTokenRest convert(String modelObject, Projection projection) { + public AuthenticationTokenRest convert(AuthenticationToken modelObject, Projection projection) { AuthenticationTokenRest token = new AuthenticationTokenRest(); - token.setToken(modelObject); + token.setToken(modelObject.getToken()); return token; } @Override - public Class getModelClass() { - return String.class; + public Class getModelClass() { + return AuthenticationToken.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/AuthenticationToken.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/AuthenticationToken.java new file mode 100644 index 0000000000..30301ffd77 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/AuthenticationToken.java @@ -0,0 +1,28 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.wrapper; + +/** + * This class represents an authentication token. It acts as a wrapper for a String object to differentiate between + * actual Strings and AuthenticationToken + */ +public class AuthenticationToken { + private String token; + + public AuthenticationToken(String token) { + this.token = token; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java index 19b3e2f4f0..b3f4a00d37 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.security; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.dspace.app.rest.security.jwt.SessionJWTTokenHandler; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.core.Context; import org.slf4j.Logger; @@ -29,7 +28,7 @@ import org.springframework.stereotype.Component; @Component public class CustomLogoutHandler implements LogoutHandler { - private static final Logger log = LoggerFactory.getLogger(SessionJWTTokenHandler.class); + private static final Logger log = LoggerFactory.getLogger(CustomLogoutHandler.class); @Autowired private RestAuthenticationService restAuthenticationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java index 622070e77c..6270fa2851 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java @@ -11,6 +11,7 @@ import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.dspace.app.rest.model.wrapper.AuthenticationToken; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -28,7 +29,7 @@ public interface RestAuthenticationService { void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response, DSpaceAuthentication authentication, boolean addCookie) throws IOException; - String getShortLivedAuthenticationToken(Context context, HttpServletRequest request); + AuthenticationToken getShortLivedAuthenticationToken(Context context, HttpServletRequest request); EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 47a869105d..bec41c52ed 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -224,7 +224,7 @@ public abstract class JWTTokenHandler { } public long getExpirationPeriod() { - return configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 30); + return configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 1800000); } public boolean isEncryptionEnabled() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index f32e221adf..123f96f89b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -18,6 +18,7 @@ import javax.servlet.http.HttpServletResponse; import com.nimbusds.jose.JOSEException; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.wrapper.AuthenticationToken; import org.dspace.app.rest.security.DSpaceAuthentication; import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.app.rest.utils.ContextUtil; @@ -89,26 +90,26 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication } /** - * Create a short-lived token for bitstream downloads + * Create a short-lived token for bitstream downloads among other things * @param context The context for which to create the token * @param request The request for which to create the token * @return The token with a short lifespan */ @Override - public String getShortLivedAuthenticationToken(Context context, HttpServletRequest request) { - String token = null; + public AuthenticationToken getShortLivedAuthenticationToken(Context context, HttpServletRequest request) { try { + String token; List groups = authenticationService.getSpecialGroups(context, request); token = shortLivedJWTTokenHandler.createTokenForEPerson(context, request, null, groups); context.commit(); - return token; + return new AuthenticationToken(token); } catch (JOSEException e) { log.error("JOSE Exception", e); } catch (SQLException e) { log.error("SQL error when adding authentication", e); } - return token; + return null; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index 95fd52530c..de8e851118 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; /** * Class responsible for creating and parsing JSON Web Tokens (JWTs) used for bitstream - * dowloads, supports both JWS and JWE https://jwt.io/ . + * downloads among other things, supports both JWS and JWE https://jwt.io/ . */ @Component public class ShortLivedJWTTokenHandler extends JWTTokenHandler { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java index 6ae4af8293..1a33ae7484 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java @@ -47,31 +47,31 @@ public class JWTTokenHandlerTest { @InjectMocks @Spy - SessionJWTTokenHandler sessionJWTTokenHandler; + private SessionJWTTokenHandler sessionJWTTokenHandler; @Mock - private ConfigurationService configurationService; + protected ConfigurationService configurationService; @Mock - private Context context; + protected Context context; @Mock - private EPerson ePerson; + protected EPerson ePerson; @Mock - private HttpServletRequest httpServletRequest; + protected HttpServletRequest httpServletRequest; @Mock - private EPersonService ePersonService; + protected EPersonService ePersonService; @Mock - private EPersonClaimProvider ePersonClaimProvider; + protected EPersonClaimProvider ePersonClaimProvider; @Mock - private ClientInfoService clientInfoService; + protected ClientInfoService clientInfoService; @Spy - private List jwtClaimProviders = new ArrayList<>(); + protected List jwtClaimProviders = new ArrayList<>(); @Before public void setUp() throws Exception { @@ -112,7 +112,7 @@ public class JWTTokenHandlerTest { //temporary set a negative expiration time so the token is invalid immediately @Test public void testExpiredToken() throws Exception { - when(configurationService.getLongProperty("jwt.session.token.expiration", 30)).thenReturn(-99999999L); + when(configurationService.getLongProperty("jwt.session.token.expiration", 1800000)).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = sessionJWTTokenHandler diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java new file mode 100644 index 0000000000..38176ba57c --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java @@ -0,0 +1,106 @@ +/** + * 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.security.jwt; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Date; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.crypto.keygen.KeyGenerators; +import org.springframework.security.crypto.keygen.StringKeyGenerator; + +/** + * Test suite for the short lived authentication token + */ +@RunWith(MockitoJUnitRunner.class) +public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { + @InjectMocks + @Spy + private ShortLivedJWTTokenHandler shortLivedJWTTokenHandler; + + @Test + public void testJWTNoEncryption() throws Exception { + Date previous = new Date(System.currentTimeMillis() - 10000000000L); + String token = shortLivedJWTTokenHandler + .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + SignedJWT signedJWT = SignedJWT.parse(token); + String personId = (String) signedJWT.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID); + assertEquals("epersonID", personId); + } + + @Test(expected = ParseException.class) + public void testJWTEncrypted() throws Exception { + when(shortLivedJWTTokenHandler.isEncryptionEnabled()).thenReturn(true); + Date previous = new Date(System.currentTimeMillis() - 10000000000L); + StringKeyGenerator keyGenerator = KeyGenerators.string(); + when(configurationService.getProperty("jwt.shortLived.encryption.secret")) + .thenReturn(keyGenerator.generateKey()); + String token = shortLivedJWTTokenHandler + .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + SignedJWT signedJWT = SignedJWT.parse(token); + } + + //temporary set a negative expiration time so the token is invalid immediately + @Test + public void testExpiredToken() throws Exception { + when(configurationService.getLongProperty("jwt.shortLived.token.expiration", 1800000)) + .thenReturn(-99999999L); + when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); + Date previous = new Date(new Date().getTime() - 10000000000L); + String token = shortLivedJWTTokenHandler + .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + EPerson parsed = shortLivedJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); + assertEquals(null, parsed); + + } + + //Try if we can change the expiration date + @Test + public void testTokenTampering() throws Exception { + when(shortLivedJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); + when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); + Date previous = new Date(new Date().getTime() - 10000000000L); + String token = shortLivedJWTTokenHandler + .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().claim("eid", "epersonID").expirationTime( + new Date(System.currentTimeMillis() + 99999999)).build(); + String tamperedPayload = new String(Base64.getUrlEncoder().encode(jwtClaimsSet.toString().getBytes())); + String[] splitToken = token.split("\\."); + String tamperedToken = splitToken[0] + "." + tamperedPayload + "." + splitToken[2]; + EPerson parsed = shortLivedJWTTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context); + assertEquals(null, parsed); + } + + @Test + public void testInvalidatedToken() throws Exception { + Date previous = new Date(System.currentTimeMillis() - 10000000000L); + // create a new token + String token = shortLivedJWTTokenHandler + .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + // immediately invalidate it + shortLivedJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); + // Check if it is still valid by trying to parse the EPerson from it (should return null) + EPerson parsed = shortLivedJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); + assertEquals(null, parsed); + } +} diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index 3a5e618721..e3077f4a57 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -83,6 +83,7 @@ jwt.session.token.include.ip = true #---------------------------------------------------------------# #---Stateless JWT Authentication for downloads of bitstreams----# +#----------------------among other things-----------------------# #---------------------------------------------------------------# # Server key part that is a part of the key used to sign the authentication tokens. From 6c91e5acf05be11a60b8fa62d99b49099afbaf91 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Fri, 19 Jun 2020 12:03:42 +0200 Subject: [PATCH 095/465] Authorization for file downloads: Small comment changes --- .../dspace/app/rest/security/RestAuthenticationService.java | 6 ++++++ .../app/rest/security/jwt/ShortLivedJWTTokenHandler.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java index 6270fa2851..88b1d26524 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java @@ -29,6 +29,12 @@ public interface RestAuthenticationService { void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response, DSpaceAuthentication authentication, boolean addCookie) throws IOException; + /** + * Retrieve a short lived authentication token, this can be used (among other things) for file downloads + * @param context the DSpace context + * @param request The current client request + * @return An AuthenticationToken that contains a string with the token + */ AuthenticationToken getShortLivedAuthenticationToken(Context context, HttpServletRequest request); EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index de8e851118..375bfe4ae5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -50,7 +50,7 @@ public class ShortLivedJWTTokenHandler extends JWTTokenHandler { Date expirationTime = jwtClaimsSet.getExpirationTime(); return signedJWT.verify(verifier) && expirationTime != null - //Ensure expiration timestamp is after the current time, with a minute of acceptable clock skew. + //Ensure expiration timestamp is after the current time && DateUtils.isAfter(expirationTime, new Date(), 0); } } From 891ab3f3e261e8a78321091b8835fab413937899 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 19 Jun 2020 14:47:53 +0200 Subject: [PATCH 096/465] [Task 71442] fixed the tests without the additional cleanup required --- .../app/rest/RegistrationRestControllerIT.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java index da6f7a94d8..d02dceea20 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java @@ -12,19 +12,16 @@ import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.sql.SQLException; import java.util.Iterator; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.eperson.RegistrationData; import org.dspace.eperson.dao.RegistrationDataDAO; import org.dspace.services.ConfigurationService; -import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -36,19 +33,6 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT @Autowired private ConfigurationService configurationService; - - @Before - public void setup() throws SQLException { - CollectionUtils.emptyIfNull(registrationDataDAO.findAll(context, RegistrationData.class)).stream() - .forEach(registrationData -> { - try { - registrationDataDAO.delete(context, registrationData); - } catch (SQLException e) { - throw new RuntimeException(e); - } - }); - } - @Test public void registrationFlowTest() throws Exception { List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); @@ -119,5 +103,6 @@ public class RegistrationRestControllerIT extends AbstractControllerIntegrationT RegistrationData registrationData = iterator.next(); registrationDataDAO.delete(context, registrationData); } + context.complete(); } } From fa2769036a6253fbd6802549edc0172da8d0ea78 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 16:36:43 +0200 Subject: [PATCH 097/465] refactored search method byMetadataAndCollection --- .../app/rest/repository/VocabularyRestRepository.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index 6ea08cafd6..dcdf71186b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -86,13 +86,10 @@ public class VocabularyRestRepository extends DSpaceRestRepository Date: Fri, 19 Jun 2020 16:40:35 +0200 Subject: [PATCH 098/465] added VocabularyEntryResource --- .../hateoas/VocabularyEntryResource.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java new file mode 100644 index 0000000000..c29baa9fcd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java @@ -0,0 +1,24 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; + +/** + * Vocabulary Entry Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@RelNameDSpaceResource(VocabularyEntryRest.NAME) +public class VocabularyEntryResource extends HALResource { + public VocabularyEntryResource(VocabularyEntryRest sd) { + super(sd); + } +} From cd842fd806a55a71a6966cdb9625b7aefca628dc Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 16:42:10 +0200 Subject: [PATCH 099/465] refactored RestResourceController --- .../java/org/dspace/app/rest/RestResourceController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 025f7ad03b..2a9bc58d15 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -791,7 +791,7 @@ public class RestResourceController implements InitializingBean { Method linkMethod = utils.requireMethod(linkRepository.getClass(), linkRest.method()); try { if (Page.class.isAssignableFrom(linkMethod.getReturnType())) { - Page pageResult = (Page) linkMethod + Page pageResult = (Page) linkMethod .invoke(linkRepository, request, uuid, page, utils.obtainProjection()); if (pageResult == null) { @@ -815,8 +815,8 @@ public class RestResourceController implements InitializingBean { return new EntityModel(new EmbeddedPage(link.getHref(), pageResult.map(converter::toResource), null, subpath)); } else { - RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, uuid, page, - utils.obtainProjection()); + RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, + uuid, page, utils.obtainProjection()); if (object == null) { response.setStatus(HttpServletResponse.SC_NO_CONTENT); return null; @@ -838,7 +838,7 @@ public class RestResourceController implements InitializingBean { throw new RuntimeException(e); } } - RestAddressableModel modelObject = repository.findById(uuid).orElse(null); + RestModel modelObject = repository.findById(uuid).orElse(null); if (modelObject == null) { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); From 056a0fbdae16f8e385255359d828336c460ec0d6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 16:44:26 +0200 Subject: [PATCH 100/465] VocabularyEntryRest must implement RestModel --- .../org/dspace/app/rest/model/VocabularyEntryRest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index f9855828ea..41598bb7be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -16,7 +16,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public class VocabularyEntryRest { +public class VocabularyEntryRest implements RestModel { public static final String NAME = "vocabularyEntry"; private String authority; private String display; @@ -68,4 +68,9 @@ public class VocabularyEntryRest { public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() { return vocabularyEntryDetailsRest; } + + @Override + public String getType() { + return VocabularyEntryRest.NAME; + } } From f9f00672943f611b8bf909d3a3a26c495eb7aa19 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 18:45:37 +0200 Subject: [PATCH 101/465] fix bug --- .../content/authority/ChoiceAuthorityServiceImpl.java | 2 +- .../org/dspace/content/authority/DCInputAuthority.java | 9 +++++++-- .../rest/repository/VocabularyEntryLinkRepository.java | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index a36bd653c0..365e59f3a9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -463,7 +463,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService try { configReader = new SubmissionConfigReader(); SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); - ma = controllerFormDefinitions.get(submissionName.getSubmissionName()).get(fieldKey); + ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName()); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException(e); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 42e79820c7..53c7cca4c3 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -13,6 +13,7 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; @@ -107,14 +108,18 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority init(); int dflt = -1; + int index = 0; Choice v[] = new Choice[values.length]; for (int i = 0; i < values.length; ++i) { - v[i] = new Choice(values[i], values[i], labels[i]); + if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { + v[index] = new Choice(values[i], values[i], labels[i]); + index++; + } if (values[i].equalsIgnoreCase(query)) { dflt = i; } } - return new Choices(v, 0, v.length, Choices.CF_AMBIGUOUS, false, dflt); + return new Choices(v, 0, index, Choices.CF_AMBIGUOUS, false, dflt); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 967124624a..eae127d1f2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -86,7 +86,9 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); for (Choice value : choices.values) { - results.add(authorityUtils.convertEntry(value, name, projection)); + if (value != null) { + results.add(authorityUtils.convertEntry(value, name, projection)); + } } return new PageImpl<>(results, pageable, results.size()); } From de49227b076a65720bb3b09c1e1ca3cd0f3e643d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 18:46:22 +0200 Subject: [PATCH 102/465] fix tests --- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 30de227ec2..4c2ca7339e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -157,8 +157,8 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Youth research", "vocabularyEntry") ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) - .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @@ -274,7 +274,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), - VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularySuggestion") + VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) From e473f1170bb2007691a4bad885fbcc4365df6181 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 18:47:04 +0200 Subject: [PATCH 103/465] added test --- .../dspace/app/rest/VocabularyEntryDetailsIT.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 2fbd529a9d..20c36d6d2a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -19,8 +19,21 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; +/** + * + * + * @author Mykhaylo Boychuk (4science.it) + */ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { + @Test + public void findAllTest() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/submission/vocabularyEntryDetails")) + .andExpect(status() + .isMethodNotAllowed()); + } + @Test public void findOneTest() throws Exception { String idAuthority = "srsc:SCB110"; @@ -37,6 +50,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); } + @Test public void findOneUnauthorizedTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) From 5c8ff2c74312c5c97a14c01e2efa4d8521c1c1cb Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 20 Jun 2020 18:49:56 +0200 Subject: [PATCH 104/465] DS-4530 enforce the use of the latest override of the findOne when checking permission for embedding --- .../app/rest/converter/ConverterService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index fc786bfc85..563f2045ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -149,14 +149,29 @@ public class ConverterService { DSpaceRestRepository repositoryToUse = utils .getResourceRepositoryByCategoryAndModel(baseObjectRest.getCategory(), baseObjectRest.getType()); Annotation preAuthorize = null; + int maxDepth = 0; for (Method m : repositoryToUse.getClass().getMethods()) { if (StringUtils.equalsIgnoreCase(m.getName(), "findOne")) { - preAuthorize = AnnotationUtils.findAnnotation(m, PreAuthorize.class); + int depth = howManySuperclass(m.getDeclaringClass()); + if (depth > maxDepth) { + preAuthorize = AnnotationUtils.findAnnotation(m, PreAuthorize.class); + maxDepth = depth; + } } } return preAuthorize; } + private int howManySuperclass(Class declaringClass) { + Class curr = declaringClass; + int count = 0; + while (curr != Object.class) { + curr = curr.getSuperclass(); + count++; + } + return count; + } + private Annotation getDefaultFindOnePreAuthorize() { for (Method m : DSpaceRestRepository.class.getMethods()) { if (StringUtils.equalsIgnoreCase(m.getName(), "findOne")) { From f50d0d664832142525d68ed22ae4e5ec935d9770 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 20 Jun 2020 18:50:36 +0200 Subject: [PATCH 105/465] Add dependency needed by the pubmed live import provider --- dspace-api/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a0714c04ee..7de2b782af 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -622,6 +622,12 @@ jersey-client ${jersey.version} + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey.version} + com.amazonaws From 38c566723384729f1e97ab0b7687d6696bb79180 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 20 Jun 2020 18:52:23 +0200 Subject: [PATCH 106/465] DS-4515 enable live import provider as external authority --- .../provider/impl/LiveImportDataProvider.java | 142 ++++++++++++++++++ .../config/spring/api/external-services.xml | 6 + 2 files changed, 148 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java new file mode 100644 index 0000000000..776984ddb7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java @@ -0,0 +1,142 @@ +/** + * 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.external.provider.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.service.components.MetadataSource; + +/** + * This class allows to configure a Live Import Provider as an External Data Provider + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class LiveImportDataProvider implements ExternalDataProvider { + /** + * The {@link MetadataSource} live import provider + */ + private MetadataSource metadataSource; + + /** + * An unique human readable identifier for this provider + */ + private String sourceIdentifier; + + private String recordIdMetadata; + + private String displayMetadata = "dc.title"; + + @Override + public String getSourceIdentifier() { + return sourceIdentifier; + } + + public void setSourceIdentifier(String sourceIdentifier) { + this.sourceIdentifier = sourceIdentifier; + } + + public void setMetadataSource(MetadataSource metadataSource) { + this.metadataSource = metadataSource; + } + + public void setRecordIdMetadata(String recordIdMetadata) { + this.recordIdMetadata = recordIdMetadata; + } + + public void setDisplayMetadata(String displayMetadata) { + this.displayMetadata = displayMetadata; + } + + @Override + public Optional getExternalDataObject(String id) { + try { + ExternalDataObject externalDataObject = getExternalDataObject(metadataSource.getRecord(id)); + return Optional.of(externalDataObject); + } catch (MetadataSourceException e) { + throw new RuntimeException( + "The live import provider " + metadataSource.getImportSource() + " throws an exception", e); + } + } + + @Override + public List searchExternalDataObjects(String query, int start, int limit) { + Collection records; + try { + records = metadataSource.getRecords(query, start, limit); + return records.stream().map(r -> getExternalDataObject(r)).collect(Collectors.toList()); + } catch (MetadataSourceException e) { + throw new RuntimeException( + "The live import provider " + metadataSource.getImportSource() + " throws an exception", e); + } + } + + @Override + public boolean supports(String source) { + return StringUtils.equalsIgnoreCase(sourceIdentifier, source); + } + + @Override + public int getNumberOfResults(String query) { + try { + return metadataSource.getNbRecords(query); + } catch (MetadataSourceException e) { + throw new RuntimeException( + "The live import provider " + metadataSource.getImportSource() + " throws an exception", e); + } + } + + /** + * Internal method to convert an ImportRecord to an ExternalDataObject + * + * FIXME it would be useful to remove ImportRecord at all in favor of the + * ExternalDataObject + * + * @param record + * @return + */ + private ExternalDataObject getExternalDataObject(ImportRecord record) { + ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier); + String id = getFirstValue(record, recordIdMetadata); + String display = getFirstValue(record, displayMetadata); + externalDataObject.setId(id); + externalDataObject.setDisplayValue(display); + externalDataObject.setValue(display); + for (MetadatumDTO dto : record.getValueList()) { + // FIXME it would be useful to remove MetadatumDTO in favor of MetadataValueDTO + MetadataValueDTO mvDTO = new MetadataValueDTO(); + mvDTO.setSchema(dto.getSchema()); + mvDTO.setElement(dto.getElement()); + mvDTO.setQualifier(dto.getQualifier()); + mvDTO.setValue(dto.getValue()); + externalDataObject.addMetadata(mvDTO); + } + return externalDataObject; + } + + private String getFirstValue(ImportRecord record, String metadata) { + String id = null; + String[] split = StringUtils.split(metadata, ".", 3); + Collection values = record.getValue(split[0], split[1], split.length == 3 ? split[2] : null); + if (!values.isEmpty()) { + id = (values.iterator().next().getValue()); + } + return id; + } + +} diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 520c21a963..098b53c2ca 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -30,5 +30,11 @@ + + + + + + From bd2cf94376e5560559d838c04d74cd0817c654a4 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 20 Jun 2020 18:52:45 +0200 Subject: [PATCH 107/465] DS-4529 External authorities endpoint doesn't support the pagination --- .../app/rest/repository/ExternalSourceRestRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 49a128cd85..948e25e364 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -89,10 +89,10 @@ public class ExternalSourceRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List externalSources = externalDataService.getExternalDataProviders(); - return converter.toRestPage(externalSources, pageable, externalSources.size(), - utils.obtainProjection()); + return converter.toRestPage(externalSources, pageable, utils.obtainProjection()); } public Class getDomainClass() { From 64046093d2cb45ec2bcf6f3396953c9d2f9f9315 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Mon, 22 Jun 2020 11:46:53 +0200 Subject: [PATCH 108/465] Bibtex file import --- ...BibtexImportMetadataSourceServiceImpl.java | 107 ++++++++++++++ .../FileMultipleOccurencesException.java | 29 ++++ .../exception/FileSourceException.java | 28 ++++ .../AbstractMetadataFieldMapping.java | 3 - .../SimpleMetadataContributor.java | 97 +++++++++++++ ...PubmedImportMetadataSourceServiceImpl.java | 5 +- .../AbstractImportMetadataSourceService.java | 3 +- .../external/service/ImportService.java | 83 ++++++++--- .../AbstractPlainMetadataSource.java | 86 +++++++++++ .../service/components/FileSource.java | 46 ++++++ .../service/components/MetadataSource.java | 85 +---------- .../service/components/QuerySource.java | 106 ++++++++++++++ .../dto/PlainMetadataKeyValueItem.java | 37 +++++ .../dto/PlainMetadataSourceDto.java | 32 +++++ .../spring-dspace-addon-import-services.xml | 22 ++- .../WorkspaceItemRestRepository.java | 136 ++++++------------ .../dspace/app/rest/bibtex-test-3-entries.bib | 14 ++ .../org/dspace/app/rest/bibtex-test.bib | 12 +- .../config/spring/api/bibtex-integration.xml | 74 ++++++++++ .../config/spring/api/external-services.xml | 7 +- 20 files changed, 801 insertions(+), 211 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/exception/FileMultipleOccurencesException.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/exception/FileSourceException.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/components/QuerySource.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-3-entries.bib create mode 100644 dspace/config/spring/api/bibtex-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..7468d601f5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.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.importer.external.bibtex.service; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Resource; + +import org.dspace.importer.external.exception.FileSourceException; +import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; +import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; +import org.jbibtex.BibTeXDatabase; +import org.jbibtex.BibTeXEntry; +import org.jbibtex.BibTeXParser; +import org.jbibtex.Key; +import org.jbibtex.ParseException; +import org.jbibtex.Value; + +/** + * Implements a metadata importer for BibTeX files + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class BibtexImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { + + + /** + * The string that identifies this import implementation as + * MetadataSource implementation + * + * @return the identifying uri + */ + @Override + public String getImportSource() { + return "BibTeXMetadataSource"; + } + + @Override + protected List readData (InputStream + inputStream) throws FileSourceException { + List list = new ArrayList<>(); + BibTeXDatabase database; + try { + database = parseBibTex(inputStream); + } catch (IOException | ParseException e) { + throw new FileSourceException("Unable to parse file with BibTeX parser"); + } + if (database == null || database.getEntries() == null) { + throw new FileSourceException("File results in an empty list of metadata"); + } + if (database.getEntries() != null) { + for (Entry entry : database.getEntries().entrySet()) { + PlainMetadataSourceDto item = new PlainMetadataSourceDto(); + List keyValues = new ArrayList<>(); + item.setMetadata(keyValues); + PlainMetadataKeyValueItem keyValueItem = new PlainMetadataKeyValueItem(); + keyValueItem.setKey(entry.getValue().getType().getValue()); + keyValueItem.setValue(entry.getKey().getValue()); + keyValues.add(keyValueItem); + if (entry.getValue().getFields() != null) { + for (Entry subentry : entry.getValue().getFields().entrySet()) { + PlainMetadataKeyValueItem innerItem = new PlainMetadataKeyValueItem(); + innerItem.setKey(subentry.getKey().getValue()); + innerItem.setValue(subentry.getValue().toUserString()); + keyValues.add(innerItem); + } + } + list.add(item); + } + } + return list; + } + + private BibTeXDatabase parseBibTex(InputStream inputStream) throws IOException, ParseException { + Reader reader = new InputStreamReader(inputStream); + BibTeXParser bibtexParser = new BibTeXParser(); + return bibtexParser.parse(reader); + } + + + /** + * Retrieve the MetadataFieldMapping containing the mapping between RecordType + * (in this case PlainMetadataSourceDto.class) and Metadata + * + * @return The configured MetadataFieldMapping + */ + @Override + @SuppressWarnings("unchecked") + @Resource(name = "bibtexMetadataFieldMap") + public void setMetadataFieldMap(@SuppressWarnings("rawtypes") Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/exception/FileMultipleOccurencesException.java b/dspace-api/src/main/java/org/dspace/importer/external/exception/FileMultipleOccurencesException.java new file mode 100644 index 0000000000..d09889a7ff --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/exception/FileMultipleOccurencesException.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.importer.external.exception; + +/** + * This exception could be throws when more than one element is found + * in a method that works on one only. + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ + +public class FileMultipleOccurencesException extends Exception { + + private static final long serialVersionUID = 1222409723339501937L; + + public FileMultipleOccurencesException(String message, Throwable cause) { + super(message, cause); + } + + public FileMultipleOccurencesException(String message) { + super(message); + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/exception/FileSourceException.java b/dspace-api/src/main/java/org/dspace/importer/external/exception/FileSourceException.java new file mode 100644 index 0000000000..c41ce94151 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/exception/FileSourceException.java @@ -0,0 +1,28 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.importer.external.exception; + +/** + * Represents a problem with the File content: e.g. null input stream, invalid content, ... + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ + +public class FileSourceException extends Exception { + + private static final long serialVersionUID = 6895579588455260182L; + + public FileSourceException(String message, Throwable cause) { + super(message, cause); + } + + public FileSourceException(String message) { + super(message); + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java index 3ce45d6048..aed2f0e084 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java @@ -117,16 +117,13 @@ public abstract class AbstractMetadataFieldMapping public Collection resultToDCValueMapping(RecordType record) { List values = new LinkedList(); - for (MetadataContributor query : getMetadataFieldMap().values()) { try { values.addAll(query.contributeMetadata(record)); } catch (Exception e) { log.error("Error", e); } - } return values; - } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java new file mode 100644 index 0000000000..8fcbfdb8b8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java @@ -0,0 +1,97 @@ +/** + * 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.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; + +/** + * Metadata contributor that takes an PlainMetadataSourceDto instance and turns it into a metadatum + * + * @author Roeland Dillen (roeland at atmire dot com) + */ +public class SimpleMetadataContributor implements MetadataContributor { + + private MetadataFieldConfig field; + + private String key; + + private MetadataFieldMapping> metadataFieldMapping; + + public SimpleMetadataContributor(MetadataFieldConfig field, String key) { + this.field = field; + this.key = key; + } + + public SimpleMetadataContributor() { } + + /** + * Set the metadataFieldMapping of this SimpleMetadataContributor + * + * @param metadataFieldMapping the new mapping. + */ + @Override + public void setMetadataFieldMapping( + MetadataFieldMapping> metadataFieldMapping) { + this.metadataFieldMapping = metadataFieldMapping; + } + + /** + * Retrieve the metadata associated with the given object. + * It match the key found in PlainMetadataSourceDto instance with the key passed to constructor. + * In case of success, new metadatum is constructer (using field elements and PlainMetadataSourceDto value) + * and added to the list. + * + * @param t A class to retrieve metadata and key to match from. + * @return a collection of import records. Only the identifier of the found records may be put in the record. + */ + @Override + public Collection contributeMetadata(PlainMetadataSourceDto t) { + List values = new LinkedList<>(); + try { + for (PlainMetadataKeyValueItem metadatum : t.getMetadata()) { + if (metadatum.getKey().equals(key)) { + MetadatumDTO dcValue = new MetadatumDTO(); + dcValue.setValue(metadatum.getValue()); + dcValue.setElement(field.getElement()); + dcValue.setQualifier(field.getQualifier()); + dcValue.setSchema(field.getSchema()); + values.add(dcValue); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return values; + } + + /* + * Setter to inject field item + */ + public void setField(MetadataFieldConfig field) { + this.field = field; + } + + /* + * Setter to inject key value + */ + public void setKey(String key) { + this.key = key; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index bcb5034301..02d205914a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -29,6 +29,7 @@ import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; import org.jaxen.JaxenException; /** @@ -36,7 +37,9 @@ import org.jaxen.JaxenException; * * @author Roeland Dillen (roeland at atmire dot com) */ -public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService { +public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + private String baseAddress; private WebTarget pubmedWebTarget; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java index a803958a9d..f904ce613c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java @@ -28,8 +28,7 @@ import org.springframework.beans.factory.annotation.Required; * * @author Roeland Dillen (roeland at atmire dot com) */ -public abstract class AbstractImportMetadataSourceService extends AbstractRemoteMetadataSource - implements MetadataSource { +public abstract class AbstractImportMetadataSourceService extends AbstractRemoteMetadataSource { private GenerateQueryService generateQueryForItem = null; private MetadataFieldMapping> metadataFieldMapping; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java index 87c2bd0029..fe9f473137 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java @@ -8,6 +8,7 @@ package org.dspace.importer.external.service; +import java.io.InputStream; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -19,11 +20,16 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.FileMultipleOccurencesException; +import org.dspace.importer.external.exception.FileSourceException; import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; import org.dspace.importer.external.service.components.Destroyable; import org.dspace.importer.external.service.components.MetadataSource; +import org.dspace.importer.external.service.components.QuerySource; import org.springframework.beans.factory.annotation.Autowired; + /** * Main entry point for the import framework. * Instead of calling the different importer implementations, the ImportService should be called instead. @@ -32,8 +38,10 @@ import org.springframework.beans.factory.annotation.Autowired; * importer implementation you want to use. * * @author Roeland Dillen (roeland at atmire dot com) + * @author Pasquale Cavallo (pasquale.cavallo@4science.it) */ public class ImportService implements Destroyable { + private HashMap importSources = new HashMap<>(); Logger log = org.apache.logging.log4j.LogManager.getLogger(ImportService.class); @@ -101,11 +109,11 @@ public class ImportService implements Destroyable { public Collection findMatchingRecords(String uri, Item item) throws MetadataSourceException { try { List recordList = new LinkedList(); - for (MetadataSource metadataSource : matchingImports(uri)) { - recordList.addAll(metadataSource.findMatchingRecords(item)); + if (metadataSource instanceof QuerySource) { + recordList.addAll(((QuerySource)metadataSource).findMatchingRecords(item)); + } } - return recordList; } catch (Exception e) { throw new MetadataSourceException(e); @@ -125,9 +133,10 @@ public class ImportService implements Destroyable { try { List recordList = new LinkedList(); for (MetadataSource metadataSource : matchingImports(uri)) { - recordList.addAll(metadataSource.findMatchingRecords(query)); + if (metadataSource instanceof QuerySource) { + recordList.addAll(((QuerySource)metadataSource).findMatchingRecords(query)); + } } - return recordList; } catch (Exception e) { throw new MetadataSourceException(e); @@ -145,8 +154,10 @@ public class ImportService implements Destroyable { public int getNbRecords(String uri, String query) throws MetadataSourceException { try { int total = 0; - for (MetadataSource MetadataSource : matchingImports(uri)) { - total += MetadataSource.getNbRecords(query); + for (MetadataSource metadataSource : matchingImports(uri)) { + if (metadataSource instanceof QuerySource) { + total += ((QuerySource)metadataSource).getNbRecords(query); + } } return total; } catch (Exception e) { @@ -165,8 +176,10 @@ public class ImportService implements Destroyable { public int getNbRecords(String uri, Query query) throws MetadataSourceException { try { int total = 0; - for (MetadataSource MetadataSource : matchingImports(uri)) { - total += MetadataSource.getNbRecords(query); + for (MetadataSource metadataSource : matchingImports(uri)) { + if (metadataSource instanceof QuerySource) { + total += ((QuerySource)metadataSource).getNbRecords(query); + } } return total; } catch (Exception e) { @@ -189,7 +202,9 @@ public class ImportService implements Destroyable { try { List recordList = new LinkedList<>(); for (MetadataSource metadataSource : matchingImports(uri)) { - recordList.addAll(metadataSource.getRecords(query, start, count)); + if (metadataSource instanceof QuerySource) { + recordList.addAll(((QuerySource)metadataSource).getRecords(query, start, count)); + } } return recordList; } catch (Exception e) { @@ -209,7 +224,9 @@ public class ImportService implements Destroyable { try { List recordList = new LinkedList<>(); for (MetadataSource metadataSource : matchingImports(uri)) { - recordList.addAll(metadataSource.getRecords(query)); + if (metadataSource instanceof QuerySource) { + recordList.addAll(((QuerySource)metadataSource).getRecords(query)); + } } return recordList; } catch (Exception e) { @@ -229,10 +246,12 @@ public class ImportService implements Destroyable { public ImportRecord getRecord(String uri, String id) throws MetadataSourceException { try { for (MetadataSource metadataSource : matchingImports(uri)) { - if (metadataSource.getRecord(id) != null) { - return metadataSource.getRecord(id); + if (metadataSource instanceof QuerySource) { + QuerySource querySource = (QuerySource)metadataSource; + if (querySource.getRecord(id) != null) { + return querySource.getRecord(id); + } } - } return null; } catch (Exception e) { @@ -252,10 +271,12 @@ public class ImportService implements Destroyable { public ImportRecord getRecord(String uri, Query query) throws MetadataSourceException { try { for (MetadataSource metadataSource : matchingImports(uri)) { - if (metadataSource.getRecord(query) != null) { - return metadataSource.getRecord(query); + if (metadataSource instanceof QuerySource) { + QuerySource querySource = (QuerySource)metadataSource; + if (querySource.getRecord(query) != null) { + return querySource.getRecord(query); + } } - } return null; } catch (Exception e) { @@ -272,6 +293,34 @@ public class ImportService implements Destroyable { return importSources.keySet(); } + /* + * Get a collection of record from InputStream, + * The first match will be return. + * This method doesn't close the InputStream. + * + * @param fileInputStream the input stream to the resource + * @return a single record contains the metadatum + * @throws FileMultipleOccurencesException if more than one entry is found + */ + public ImportRecord getRecord(InputStream fileInputStream) throws FileMultipleOccurencesException { + ImportRecord importRecords = null; + for (MetadataSource metadataSource : importSources.values()) { + if (metadataSource instanceof AbstractPlainMetadataSource) { + AbstractPlainMetadataSource fileSource = (AbstractPlainMetadataSource)metadataSource; + try { + importRecords = fileSource.getRecord(fileInputStream); + break; + } catch (FileSourceException e) { + log.debug(fileSource.getImportSource() + " isn't a valid parser for file"); + } catch (FileMultipleOccurencesException e) { + log.debug("File contains multiple metadata, return with error"); + throw e; + } + } + } + return importRecords; + } + /** * Call destroy on all {@link Destroyable} {@link MetadataSource} objects set in this ImportService */ diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java new file mode 100644 index 0000000000..45b96f10b9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java @@ -0,0 +1,86 @@ +/** + * 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.importer.external.service.components; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.exception.FileMultipleOccurencesException; +import org.dspace.importer.external.exception.FileSourceException; +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; + + +/** + * This class is an abstract implementation of {@link MetadataSource} useful in cases + * of plain metadata sources. + * It provides the methot to mapping metadata to DSpace Format when source is a file + * whit a list of strings. + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ + +public abstract class AbstractPlainMetadataSource + extends AbstractMetadataFieldMapping + implements FileSource { + + protected abstract List + readData(InputStream fileInpuStream) throws FileSourceException; + + /** + * Return a list of ImportRecord constructed from input file. This list is based on + * the results retrieved from the file (InputStream) parsed through abstract method readData + * + * @param InputStream The inputStream of the file + * @return A list of {@link ImportRecord} + * @throws FileSourceException if, for any reason, the file is not parsable + */ + @Override + public List getRecords(InputStream is) throws FileSourceException { + List datas = readData(is); + List records = new ArrayList<>(); + for (PlainMetadataSourceDto item : datas) { + records.add(toRecord(item)); + } + return records; + } + + /** + * Return an ImportRecord constructed from input file. This list is based on + * the result retrieved from the file (InputStream) parsed through abstract method + * "readData" implementation + * + * @param InputStream The inputStream of the file + * @return An {@link ImportRecord} matching the file content + * @throws FileSourceException if, for any reason, the file is not parsable + * @throws FileMultipleOccurencesException if the file contains more than one entry + */ + @Override + public ImportRecord getRecord(InputStream is) throws FileSourceException, FileMultipleOccurencesException { + List datas = readData(is); + if (datas == null || datas.isEmpty()) { + throw new FileSourceException("File is empty"); + } + if (datas.size() > 1) { + throw new FileMultipleOccurencesException("File " + + "contains more than one entry (" + datas.size() + " entries"); + } + return toRecord(datas.get(0)); + } + + + private ImportRecord toRecord(PlainMetadataSourceDto entry) { + List metadata = new ArrayList<>(); + metadata.addAll(resultToDCValueMapping(entry)); + return new ImportRecord(metadata); + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java new file mode 100644 index 0000000000..ff05b8cdfb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.importer.external.service.components; + +import java.io.InputStream; +import java.util.List; + +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.exception.FileMultipleOccurencesException; +import org.dspace.importer.external.exception.FileSourceException; + +/** + * This interface declare the base methods to work with files containing metadata. + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public interface FileSource extends MetadataSource { + + /** + * Return a list of ImportRecord constructed from input file. + * + * @param InputStream The inputStream of the file + * @return A list of {@link ImportRecord} + * @throws FileSourceException if, for any reason, the file is not parsable + */ + public List getRecords(InputStream inputStream) + throws FileSourceException; + + /** + * Return an ImportRecord constructed from input file. + * + * @param InputStream The inputStream of the file + * @return An {@link ImportRecord} matching the file content + * @throws FileSourceException if, for any reason, the file is not parsable + * @throws FileMultipleOccurencesException if the file contains more than one entry + */ + public ImportRecord getRecord(InputStream inputStream) + throws FileSourceException, FileMultipleOccurencesException; + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/MetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/MetadataSource.java index 79bdcfa903..353f77b798 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/MetadataSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/MetadataSource.java @@ -8,76 +8,14 @@ package org.dspace.importer.external.service.components; -import java.util.Collection; - -import org.dspace.content.Item; -import org.dspace.importer.external.datamodel.ImportRecord; -import org.dspace.importer.external.datamodel.Query; -import org.dspace.importer.external.exception.MetadataSourceException; - /** - * Common interface for all import implementations. + * Super interface for all import implementations. * * @author Roeland Dillen (roeland at atmire dot com) + * @author Pasquale Cavallo (pasquale.cavallo@4science.it) */ public interface MetadataSource { - /** - * Gets the number of records matching a query - * - * @param query the query in string format - * @return the number of records matching the query - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public int getNbRecords(String query) throws MetadataSourceException; - /** - * Gets the number of records matching a query - * - * @param query the query object - * @return the number of records matching the query - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public int getNbRecords(Query query) throws MetadataSourceException; - - /** - * Gets a set of records matching a query. Supports pagination - * - * @param query the query. The query will generally be posted 'as is' to the source - * @param start offset - * @param count page size - * @return a collection of fully transformed id's - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public Collection getRecords(String query, int start, int count) throws MetadataSourceException; - - /** - * Find records based on a object query. - * - * @param query a query object to base the search on. - * @return a set of records. Fully transformed. - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public Collection getRecords(Query query) throws MetadataSourceException; - - /** - * Get a single record from the source. - * The first match will be returned - * - * @param id identifier for the record - * @return a matching record - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public ImportRecord getRecord(String id) throws MetadataSourceException; - - /** - * Get a single record from the source. - * The first match will be returned - * - * @param query a query matching a single record - * @return a matching record - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public ImportRecord getRecord(Query query) throws MetadataSourceException; /** * The string that identifies this import implementation. Preferable a URI @@ -86,23 +24,4 @@ public interface MetadataSource { */ public String getImportSource(); - /** - * Finds records based on an item - * Delegates to one or more MetadataSource implementations based on the uri. Results will be aggregated. - * - * @param item an item to base the search on - * @return a collection of import records. Only the identifier of the found records may be put in the record. - * @throws MetadataSourceException if the underlying methods throw any exception. - */ - public Collection findMatchingRecords(Item item) throws MetadataSourceException; - - /** - * Finds records based on query object. - * Delegates to one or more MetadataSource implementations based on the uri. Results will be aggregated. - * - * @param query a query object to base the search on. - * @return a collection of import records. Only the identifier of the found records may be put in the record. - * @throws MetadataSourceException passed through. - */ - public Collection findMatchingRecords(Query query) throws MetadataSourceException; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/QuerySource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/QuerySource.java new file mode 100644 index 0000000000..a989365e34 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/QuerySource.java @@ -0,0 +1,106 @@ +/** + * 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.importer.external.service.components; + +import java.util.Collection; + +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; + + +/** + * Common interface for database-based imports. + * + * @author Roeland Dillen (roeland at atmire dot com) + * @author Pasquale Cavallo (pasquale.cavallo@4science.it) + */ + +public interface QuerySource extends MetadataSource { + + /** + * Get a single record from the source. + * The first match will be returned + * + * @param id identifier for the record + * @return a matching record + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public ImportRecord getRecord(String id) throws MetadataSourceException; + + /** + * Gets the number of records matching a query + * + * @param query the query in string format + * @return the number of records matching the query + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public int getNbRecords(String query) throws MetadataSourceException; + + /** + * Gets the number of records matching a query + * + * @param query the query object + * @return the number of records matching the query + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public int getNbRecords(Query query) throws MetadataSourceException; + + /** + * Gets a set of records matching a query. Supports pagination + * + * @param query the query. The query will generally be posted 'as is' to the source + * @param start offset + * @param count page size + * @return a collection of fully transformed id's + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public Collection getRecords(String query, int start, int count) throws MetadataSourceException; + + /** + * Find records based on a object query. + * + * @param query a query object to base the search on. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public Collection getRecords(Query query) throws MetadataSourceException; + + /** + * Get a single record from the source. + * The first match will be returned + * + * @param query a query matching a single record + * @return a matching record + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public ImportRecord getRecord(Query query) throws MetadataSourceException; + + /** + * Finds records based on query object. + * Delegates to one or more MetadataSource implementations based on the uri. Results will be aggregated. + * + * @param query a query object to base the search on. + * @return a collection of import records. Only the identifier of the found records may be put in the record. + * @throws MetadataSourceException passed through. + */ + public Collection findMatchingRecords(Query query) throws MetadataSourceException; + + /** + * Finds records based on an item + * Delegates to one or more MetadataSource implementations based on the uri. Results will be aggregated. + * + * @param item an item to base the search on + * @return a collection of import records. Only the identifier of the found records may be put in the record. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + public Collection findMatchingRecords(Item item) throws MetadataSourceException; + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java new file mode 100644 index 0000000000..c3134db3a5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java @@ -0,0 +1,37 @@ +/** + * 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.importer.external.service.components.dto; + +/** + * Simple object to construct items + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ + +public class PlainMetadataKeyValueItem { + + private String key; + private String value; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java new file mode 100644 index 0000000000..bf800f1e49 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java @@ -0,0 +1,32 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.service.components.dto; + +import java.util.List; + + +/** + * Simple object used to construct a list of items. + * This type is used in file plain metadata import as RecordType. + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ + +public class PlainMetadataSourceDto { + + private List metadata; + + public List getMetadata() { + return metadata; + } + + public void setMetadata(List metadata) { + this.metadata = metadata; + } + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index bbdf085619..b369b3295a 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -21,7 +21,12 @@ - + + + + + + + + + + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index 91d6f0c652..a85f50414b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -8,18 +8,16 @@ package org.dspace.app.rest.repository; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import gr.ekt.bte.core.TransformationEngine; -import gr.ekt.bte.core.TransformationSpec; -import gr.ekt.bte.exceptions.BadTransformationSpec; -import gr.ekt.bte.exceptions.MalformedSourceException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -27,6 +25,7 @@ import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.WorkspaceItemConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.patch.Operation; @@ -44,25 +43,24 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.ItemServiceImpl; +import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CollectionService; -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.eperson.EPerson; import org.dspace.eperson.EPersonServiceImpl; import org.dspace.event.Event; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.exception.FileMultipleOccurencesException; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.service.ImportService; import org.dspace.services.ConfigurationService; import org.dspace.submit.AbstractProcessingStep; -import org.dspace.submit.lookup.DSpaceWorkspaceItemOutputGenerator; -import org.dspace.submit.lookup.MultipleSubmissionLookupDataLoader; -import org.dspace.submit.lookup.SubmissionItemDataLoader; -import org.dspace.submit.lookup.SubmissionLookupOutputGenerator; -import org.dspace.submit.lookup.SubmissionLookupService; -import org.dspace.submit.util.ItemSubmissionLookupDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -72,10 +70,12 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; + /** * This is the repository responsible to manage WorkspaceItem Rest object * * @author Andrea Bollini (andrea.bollini at 4science.it) + * @author Pasquale Cavallo (pasquale.cavallo at 4science.it) */ @Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME) public class WorkspaceItemRestRepository extends DSpaceRestRepository @@ -89,7 +89,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository results = new ArrayList<>(); - try { + try (InputStream fileInputStream = new FileInputStream(file)) { String uuid = request.getParameter("collection"); if (StringUtils.isBlank(uuid)) { uuid = configurationService.getProperty("submission.default.collection"); @@ -370,82 +370,20 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository tmpResult = new ArrayList(); - - TransformationEngine transformationEngine1 = submissionLookupService.getPhase1TransformationEngine(); - TransformationSpec spec = new TransformationSpec(); - // FIXME this is mostly due to the need to test. The BTE framework has an assert statement that check if the - // number of found record is less than the requested and treat 0 as is, instead, the implementation assume - // 0=unlimited this lead to test failure. - // It is unclear if BTE really respect values other than 0/MAX allowing us to put a protection against heavy - // load - spec.setNumberOfRecords(Integer.MAX_VALUE); - if (transformationEngine1 != null) { - MultipleSubmissionLookupDataLoader dataLoader = - (MultipleSubmissionLookupDataLoader) transformationEngine1.getDataLoader(); - - List fileDataLoaders = submissionLookupService.getFileProviders(); - for (String fileDataLoader : fileDataLoaders) { - dataLoader.setFile(file.getAbsolutePath(), fileDataLoader); - - try { - SubmissionLookupOutputGenerator outputGenerator = - (SubmissionLookupOutputGenerator) transformationEngine1.getOutputGenerator(); - outputGenerator.setDtoList(new ArrayList()); - log.debug("BTE transformation is about to start!"); - transformationEngine1.transform(spec); - log.debug("BTE transformation finished!"); - tmpResult.addAll(outputGenerator.getDtoList()); - if (!tmpResult.isEmpty()) { - //exit with the results founded on the first data provided - break; - } - } catch (BadTransformationSpec e1) { - log.error(e1.getMessage(), e1); - } catch (MalformedSourceException e1) { - log.error(e1.getMessage(), e1); - } - } - } - List result = null; - - //try to ingest workspaceitems - if (!tmpResult.isEmpty()) { - TransformationEngine transformationEngine2 = submissionLookupService.getPhase2TransformationEngine(); - if (transformationEngine2 != null) { - SubmissionItemDataLoader dataLoader = - (SubmissionItemDataLoader) transformationEngine2.getDataLoader(); - dataLoader.setDtoList(tmpResult); - // dataLoader.setProviders() - - DSpaceWorkspaceItemOutputGenerator outputGenerator = - (DSpaceWorkspaceItemOutputGenerator) transformationEngine2.getOutputGenerator(); - outputGenerator.setCollection(collection); - outputGenerator.setContext(context); - outputGenerator.setFormName(submissionConfig.getSubmissionName()); - outputGenerator.setDto(tmpResult.get(0)); - - try { - transformationEngine2.transform(spec); - result = outputGenerator.getWitems(); - } catch (BadTransformationSpec e1) { - e1.printStackTrace(); - } catch (MalformedSourceException e1) { - e1.printStackTrace(); - } - } - } - - //we have to create the workspaceitem to push the file also if nothing found before - if (result == null) { - WorkspaceItem source = - submissionService.createWorkspaceItem(context, getRequestService().getCurrentRequest()); - result = new ArrayList<>(); - result.add(source); + ImportRecord record = null; + try { + record = importService.getRecord(fileInputStream); + } catch (FileMultipleOccurencesException e) { + throw new UnprocessableEntityException("Too many entries in file"); + } catch ( Exception e) { + throw e; } + WorkspaceItem source = submissionService. + createWorkspaceItem(context, getRequestService().getCurrentRequest()); + merge(context, record, source); + result = new ArrayList<>(); + result.add(source); //perform upload of bitstream if there is exact one result and convert workspaceitem to entity rest if (result != null && !result.isEmpty()) { @@ -541,4 +479,22 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository getPKClass() { return Integer.class; } + + private void merge(Context context, ImportRecord record, WorkspaceItem item) throws SQLException { + for (MetadataValue metadataValue : itemService.getMetadata( + item.getItem(), Item.ANY, Item.ANY, Item.ANY, Item.ANY)) { + itemService.clearMetadata(context, item.getItem(), + metadataValue.getMetadataField().getMetadataSchema().getNamespace(), + metadataValue.getMetadataField().getElement(), + metadataValue.getMetadataField().getQualifier(), + metadataValue.getLanguage()); + } + if (record != null && record.getValueList() != null) { + for (MetadatumDTO metadataValue : record.getValueList()) { + itemService.addMetadata(context, item.getItem(), metadataValue.getSchema(), + metadataValue.getElement(), metadataValue.getQualifier(), null, + metadataValue.getValue()); + } + } + } } diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-3-entries.bib b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-3-entries.bib new file mode 100644 index 0000000000..4d197ff90f --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-3-entries.bib @@ -0,0 +1,14 @@ +@misc{ Nobody01, + author = "Nobody Jr", + title = "My Article", + year = "2006" } + +@misc{ Nobody02, + author = "Nobody Jr", + title = "My Article 2", + year = "2006" } + +@misc{ Nobody03, + author = "Nobody Jr", + title = "My Article 3", + year = "2018" } diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test.bib b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test.bib index 4d197ff90f..d6e0d992a4 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test.bib +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test.bib @@ -1,14 +1,4 @@ @misc{ Nobody01, author = "Nobody Jr", title = "My Article", - year = "2006" } - -@misc{ Nobody02, - author = "Nobody Jr", - title = "My Article 2", - year = "2006" } - -@misc{ Nobody03, - author = "Nobody Jr", - title = "My Article 3", - year = "2018" } + year = "2006" } \ No newline at end of file diff --git a/dspace/config/spring/api/bibtex-integration.xml b/dspace/config/spring/api/bibtex-integration.xml new file mode 100644 index 0000000000..dbf6f6b493 --- /dev/null +++ b/dspace/config/spring/api/bibtex-integration.xml @@ -0,0 +1,74 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 520c21a963..08c87d6c74 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -20,10 +20,13 @@ - + - + + + + From efb4f6a1be9fbbb566a82c336e42bdc29a9d65f6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 22 Jun 2020 12:00:49 +0200 Subject: [PATCH 109/465] renamed embedded link in tests --- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 4c2ca7339e..fbe4d7c6ce 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -149,7 +149,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("filter", "Research") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Family research", "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", "vocabularyEntry"), @@ -247,10 +247,11 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("collection", collection.getID().toString()) .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") ))) + .andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); @@ -272,7 +273,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("filter", "Book") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry") ))) From 73e0bd8759b69aa11bf54b6a4d3efe0a4ca17149 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 22 Jun 2020 12:15:00 +0200 Subject: [PATCH 110/465] Avoid to expose the authority where not needed --- .../main/java/org/dspace/content/authority/Choice.java | 2 -- .../content/authority/ChoiceAuthorityServiceImpl.java | 6 ++++++ .../org/dspace/content/authority/DCInputAuthority.java | 9 ++++----- .../authority/service/ChoiceAuthorityService.java | 9 +++++++++ .../rest/repository/VocabularyEntryLinkRepository.java | 5 ++--- .../java/org/dspace/app/rest/utils/AuthorityUtils.java | 10 +++++++--- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 9c092c7e8b..9b68c75d28 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -18,8 +18,6 @@ import java.util.Map; * @see Choices */ public class Choice { - public boolean storeAuthority = true; - /** * Authority key for this value */ diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 365e59f3a9..f907317a75 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -471,4 +471,10 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } return ma; } + + @Override + public boolean storeAuthority(String fieldKey, Collection collection) { + // currently only named authority can eventually provide real authority + return controller.containsKey(fieldKey); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 53c7cca4c3..8be9a8286b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -108,18 +108,17 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority init(); int dflt = -1; - int index = 0; - Choice v[] = new Choice[values.length]; + List v = new ArrayList(); for (int i = 0; i < values.length; ++i) { if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { - v[index] = new Choice(values[i], values[i], labels[i]); - index++; + v.add(new Choice(null, values[i], labels[i])); } if (values[i].equalsIgnoreCase(query)) { dflt = i; } } - return new Choices(v, 0, index, Choices.CF_AMBIGUOUS, false, dflt); + Choice[] vArray = new Choice[v.size()]; + return new Choices(v.toArray(vArray), 0, v.size(), Choices.CF_AMBIGUOUS, false, dflt); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 3d0bdd7316..b74bd71c98 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -171,4 +171,13 @@ public interface ChoiceAuthorityService { */ public void clearCache(); + /** + * Should we store the authority key (if any) for such field key and collection? + * + * @param fieldKey single string identifying metadata field + * @param collection Collection owner of Item or where the item is submitted to + * @return true if the configuration allows to store the authority value + */ + public boolean storeAuthority(String fieldKey, Collection collection); + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index eae127d1f2..3f3407563f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -85,10 +85,9 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); + boolean storeAuthority = cas.storeAuthority(fieldKey, collection); for (Choice value : choices.values) { - if (value != null) { - results.add(authorityUtils.convertEntry(value, name, projection)); - } + results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } return new PageImpl<>(results, pageable, results.size()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 1d15693f72..2f58c19918 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.utils; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyEntryRest; @@ -70,13 +71,16 @@ public class AuthorityUtils { return entry; } - public VocabularyEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { + public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority, + Projection projection) { VocabularyEntryRest entry = new VocabularyEntryRest(); entry.setDisplay(choice.label); entry.setValue(choice.value); entry.setOtherInformation(choice.extras); - entry.setAuthority(choice.authority); - if (choice.storeAuthority) { + if (storeAuthority) { + entry.setAuthority(choice.authority); + } + if (StringUtils.isNotBlank(choice.authority)) { entry.setVocabularyEntryDetailsRest(converter.toRest(choice, projection)); } return entry; From de8def4418357b82b0425cdaf142bed48425f107 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 22 Jun 2020 14:44:41 +0200 Subject: [PATCH 111/465] refactoring --- .../dspace/content/authority/DCInputAuthority.java | 14 +++++++++----- .../authority/DSpaceControlledVocabulary.java | 6 ++++-- .../dspace/app/rest/model/VocabularyEntryRest.java | 4 ++++ .../repository/VocabularyEntryLinkRepository.java | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 8be9a8286b..5b3df48002 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -108,17 +108,21 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority init(); int dflt = -1; + int found = 0; List v = new ArrayList(); for (int i = 0; i < values.length; ++i) { if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { - v.add(new Choice(null, values[i], labels[i])); - } - if (values[i].equalsIgnoreCase(query)) { - dflt = i; + if (found >= start && v.size() < limit) { + v.add(new Choice(null, values[i], labels[i])); + if (values[i].equalsIgnoreCase(query)) { + dflt = i; + } + } + found++; } } Choice[] vArray = new Choice[v.size()]; - return new Choices(v.toArray(vArray), 0, v.size(), Choices.CF_AMBIGUOUS, false, dflt); + return new Choices(v.toArray(vArray), start, found, Choices.CF_AMBIGUOUS, false, dflt); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index d97e06f250..5f3837d9de 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -152,14 +152,16 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } XPath xpath = XPathFactory.newInstance().newXPath(); Choice[] choices; + int total = 0; try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + total = results.getLength(); String[] authorities = new String[results.getLength()]; String[] values = new String[results.getLength()]; String[] labels = new String[results.getLength()]; String[] parent = new String[results.getLength()]; String[] notes = new String[results.getLength()]; - for (int i = 0; i < results.getLength(); i++) { + for (int i = 0; i < total; i++) { Node node = results.item(i); readNode(authorities, values, labels, parent, notes, i, node); } @@ -183,7 +185,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } catch (XPathExpressionException e) { choices = new Choice[0]; } - return new Choices(choices, 0, choices.length, Choices.CF_AMBIGUOUS, false); + return new Choices(choices, start, total, Choices.CF_AMBIGUOUS, false); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index 41598bb7be..713d4c5209 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -10,6 +10,8 @@ package org.dspace.app.rest.model; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * An entry in a Vocabulary @@ -18,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; */ public class VocabularyEntryRest implements RestModel { public static final String NAME = "vocabularyEntry"; + + @JsonInclude(Include.NON_NULL) private String authority; private String display; private String value; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 3f3407563f..814f9e4672 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -89,6 +89,6 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } - return new PageImpl<>(results, pageable, results.size()); + return new PageImpl<>(results, pageable, choices.total); } } From 64a29b1b6050c94d50fc7676085ae82033ba437b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 22 Jun 2020 14:45:44 +0200 Subject: [PATCH 112/465] update tests --- .../app/rest/VocabularyRestRepositoryIT.java | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index fbe4d7c6ce..a1520a6e5e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -150,15 +150,13 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( - VocabularyMatcher.matchVocabularyEntry("Family research", - "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", - "vocabularyEntry"), - VocabularyMatcher.matchVocabularyEntry("Youth research", - "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Youth research", - "vocabularyEntry") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) - .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) + VocabularyMatcher.matchVocabularyEntry("Research Subject Categories", + "Research Subject Categories", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Family research", + "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", + "vocabularyEntry")))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @@ -233,7 +231,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes } @Test - public void vocabularyEntriesCommon_typesTest() throws Exception { + public void vocabularyEntriesCommonTypesWithPaginationTest() throws Exception { context.turnOffAuthorisationSystem(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) @@ -242,19 +240,34 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) - .param("size", "2")) + getClient(token) + .perform(get("/api/submission/vocabularies/common_types/entries").param("metadata", "dc.type") + .param("collection", collection.getID().toString()).param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( - VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), - VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") - ))) + VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") + ))) .andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); + + //second page + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("size", "2") + .param("page", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry") + ))) + .andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @Test @@ -298,6 +311,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("filter", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries", Matchers.contains( + VocabularyMatcher.matchVocabularyEntry("Shirasaka, Seiko", "Shirasaka, Seiko", "vocabularyEntry") + ))) + .andExpect(jsonPath("$._embedded.entries[0].authority").isNotEmpty()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } From aef91df802af0144df8dcc0d040c664d9170741d Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Mon, 22 Jun 2020 15:43:48 +0200 Subject: [PATCH 113/465] Use metadataField as unnamed bean --- .../config/spring/api/bibtex-integration.xml | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/dspace/config/spring/api/bibtex-integration.xml b/dspace/config/spring/api/bibtex-integration.xml index dbf6f6b493..eade932459 100644 --- a/dspace/config/spring/api/bibtex-integration.xml +++ b/dspace/config/spring/api/bibtex-integration.xml @@ -25,50 +25,47 @@ - + + + + + - + + + + + - + + + + + - + + + + + - + + + + + - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From ffad77b43bbd07699c25e48ce1d39e3971c4bd5d Mon Sep 17 00:00:00 2001 From: Danilo Di Nuzzo Date: Mon, 22 Jun 2020 17:20:22 +0200 Subject: [PATCH 114/465] [CST-2877] fix submission multi-lang --- .../main/java/org/dspace/core/Context.java | 23 +++++++++++++++++++ .../AbstractDSpaceRestRepository.java | 22 ++++++++++++++---- .../SubmissionFormRestRepository.java | 14 +++++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 11e388c727..3ede4ab5a0 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -68,6 +68,13 @@ public class Context implements AutoCloseable { */ private Locale currentLocale; + /** + * List of locale object indicating the locales that are + * acceptable to the client based on the Accept-Language header. + * The Locales are in decreasing order starting with the preferred locale. + */ + private List clientLocales; + /** * Extra log info */ @@ -876,4 +883,20 @@ public class Context implements AutoCloseable { private void reloadContextBoundEntities() throws SQLException { currentUser = reloadEntity(currentUser); } + + /** + * Returns a list of locale object indicating the locales that are + * acceptable to the client based on the Accept-Language header. + * The Locales are in decreasing order starting with the preferred locale. + * @return List + */ + public List getClientLocales() { + return clientLocales; + } + + + public void setClientLocales(List clientLocales) { + this.clientLocales = clientLocales; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java index 7f815ea8f2..57650fa905 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java @@ -7,9 +7,11 @@ */ package org.dspace.app.rest.repository; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; import java.util.Locale; -import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; @@ -42,6 +44,7 @@ public abstract class AbstractDSpaceRestRepository { context = ContextUtil.obtainContext(currentRequest.getServletRequest()); Locale currentLocale = getLocal(context, currentRequest); context.setCurrentLocale(currentLocale); + context.setClientLocales(getLocalesFromRequest(currentRequest)); return context; } @@ -58,9 +61,9 @@ public abstract class AbstractDSpaceRestRepository { userLocale = new Locale(userLanguage); } } - String locale = request.getHttpServletRequest().getHeader("Accept-Language"); - if (StringUtils.isNotBlank(locale)) { - userLocale = new Locale(locale); + Enumeration locales = request.getHttpServletRequest().getLocales(); + if (locales != null) { + userLocale = locales.nextElement(); } if (userLocale == null) { return I18nUtil.getDefaultLocale(); @@ -68,4 +71,15 @@ public abstract class AbstractDSpaceRestRepository { supportedLocale = I18nUtil.getSupportedLocale(userLocale); return supportedLocale; } + + private List getLocalesFromRequest(Request request) { + List locales = new ArrayList<>(); + Enumeration reqLocales = request.getHttpServletRequest().getLocales(); + if (reqLocales != null) { + while (reqLocales.hasMoreElements()) { + locales.add(reqLocales.nextElement()); + } + } + return locales; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 434a8dc0fd..1070848917 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -46,11 +46,15 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository clientLocales = context.getClientLocales(); + DCInputsReader inputReader = null; + for (Locale locale: clientLocales) { + inputReader = inputReaders.get(locale); + if (inputReader != null) { + break; + } + } + if (inputReader == null) { inputReader = defaultInputReader; } DCInputSet subConfs = inputReader.getInputsByFormName(submitName); From 8ad4b0ed39252e9e58adde4f6ac062df2a43c16e Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Tue, 23 Jun 2020 01:51:59 +0200 Subject: [PATCH 115/465] Implement FileSource in PubmedImportMetadataSourceServiceImpl --- ...PubmedImportMetadataSourceServiceImpl.java | 54 +++++-- .../external/service/ImportService.java | 8 +- .../rest/WorkspaceItemRestRepositoryIT.java | 141 +++++++++++++++--- .../config/spring/api/bibtex-integration.xml | 53 +++---- 4 files changed, 195 insertions(+), 61 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index 02d205914a..858953da1e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -8,6 +8,10 @@ package org.dspace.importer.external.pubmed.service; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.io.StringReader; import java.util.Collection; import java.util.LinkedList; @@ -20,6 +24,7 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import com.google.common.io.CharStreams; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMXMLBuilderFactory; import org.apache.axiom.om.OMXMLParserWrapper; @@ -27,8 +32,11 @@ import org.apache.axiom.om.xpath.AXIOMXPath; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.FileMultipleOccurencesException; +import org.dspace.importer.external.exception.FileSourceException; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.FileSource; import org.dspace.importer.external.service.components.QuerySource; import org.jaxen.JaxenException; @@ -38,7 +46,7 @@ import org.jaxen.JaxenException; * @author Roeland Dillen (roeland at atmire dot com) */ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService - implements QuerySource { + implements QuerySource, FileSource { private String baseAddress; @@ -360,7 +368,6 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat @Override public Collection call() throws Exception { - List records = new LinkedList(); WebTarget getRecordIdsTarget = pubmedWebTarget .queryParam("term", query.getParameterAsClass("term", String.class)); @@ -385,13 +392,42 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat invocationBuilder = getRecordsTarget.request(MediaType.TEXT_PLAIN_TYPE); response = invocationBuilder.get(); - List omElements = splitToRecords(response.readEntity(String.class)); - - for (OMElement record : omElements) { - records.add(transformSourceRecords(record)); - } - - return records; + String xml = response.readEntity(String.class); + return parseXMLString(xml); } } + + + @Override + public List getRecords(InputStream inputStream) throws FileSourceException { + String xml = null; + try (Reader reader = new InputStreamReader(inputStream)) { + xml = CharStreams.toString(reader); + return parseXMLString(xml); + } catch (IOException e) { + throw new FileSourceException ("Cannot read XML from InputStream", e); + } + } + + @Override + public ImportRecord getRecord(InputStream inputStream) throws FileSourceException, FileMultipleOccurencesException { + List importRecord = getRecords(inputStream); + if (importRecord == null || importRecord.isEmpty()) { + throw new FileSourceException("Cannot find (valid) record in File"); + } else if (importRecord.size() > 1) { + throw new FileMultipleOccurencesException("File contains more than one entry"); + } else { + return importRecord.get(0); + } + } + + private List parseXMLString(String xml) { + List records = new LinkedList(); + List omElements = splitToRecords(xml); + for (OMElement record : omElements) { + records.add(transformSourceRecords(record)); + } + return records; + } + } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java index fe9f473137..8590fb6114 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java @@ -23,8 +23,8 @@ import org.dspace.importer.external.datamodel.Query; import org.dspace.importer.external.exception.FileMultipleOccurencesException; import org.dspace.importer.external.exception.FileSourceException; import org.dspace.importer.external.exception.MetadataSourceException; -import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; import org.dspace.importer.external.service.components.Destroyable; +import org.dspace.importer.external.service.components.FileSource; import org.dspace.importer.external.service.components.MetadataSource; import org.dspace.importer.external.service.components.QuerySource; import org.springframework.beans.factory.annotation.Autowired; @@ -305,13 +305,13 @@ public class ImportService implements Destroyable { public ImportRecord getRecord(InputStream fileInputStream) throws FileMultipleOccurencesException { ImportRecord importRecords = null; for (MetadataSource metadataSource : importSources.values()) { - if (metadataSource instanceof AbstractPlainMetadataSource) { - AbstractPlainMetadataSource fileSource = (AbstractPlainMetadataSource)metadataSource; + if (metadataSource instanceof FileSource) { + FileSource fileSource = (FileSource)metadataSource; try { importRecords = fileSource.getRecord(fileInputStream); break; } catch (FileSourceException e) { - log.debug(fileSource.getImportSource() + " isn't a valid parser for file"); + log.debug(metadataSource.getImportSource() + " isn't a valid parser for file"); } catch (FileMultipleOccurencesException e) { log.debug("File contains multiple metadata, return with error"); throw e; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 3b95b18ee0..7a78a85a90 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -871,7 +871,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration * * @throws Exception */ - public void createMultipleWorkspaceItemFromFileTest() throws Exception { + public void createSingleWorkspaceItemFromFileTest() throws Exception { context.turnOffAuthorisationSystem(); //** GIVEN ** @@ -907,17 +907,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration is("My Article"))) .andExpect( jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col1.getID().toString()))) - .andExpect(jsonPath("$._embedded.workspaceitems[1].sections.traditionalpageone['dc.title'][0].value", - is("My Article 2"))) - .andExpect( - jsonPath("$._embedded.workspaceitems[1]._embedded.collection.id", is(col1.getID().toString()))) - .andExpect(jsonPath("$._embedded.workspaceitems[2].sections.traditionalpageone['dc.title'][0].value", - is("My Article 3"))) - .andExpect( - jsonPath("$._embedded.workspaceitems[2]._embedded.collection.id", is(col1.getID().toString()))) - .andExpect( - jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) - ; + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); // bulk create workspaceitems explicitly in the col2 getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") @@ -928,21 +918,126 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration is("My Article"))) .andExpect( jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col2.getID().toString()))) - .andExpect(jsonPath("$._embedded.workspaceitems[1].sections.traditionalpageone['dc.title'][0].value", - is("My Article 2"))) - .andExpect( - jsonPath("$._embedded.workspaceitems[1]._embedded.collection.id", is(col2.getID().toString()))) - .andExpect(jsonPath("$._embedded.workspaceitems[2].sections.traditionalpageone['dc.title'][0].value", - is("My Article 3"))) - .andExpect( - jsonPath("$._embedded.workspaceitems[2]._embedded.collection.id", is(col2.getID().toString()))) - .andExpect( - jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) - ; + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); bibtex.close(); } + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file + * contains more than one entry. + * + * @throws Exception + */ + public void createMultipleWorkspaceItemsFromFileTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream bibtex = getClass().getResourceAsStream("bibtex-test-3-entries.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test-3-entries.bib", + "application/x-bibtex", + bibtex); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + // bulk create workspaceitems in the default collection (col1) + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(bibtexFile)) + // bulk create should return 200, 201 (created) is better for single resource + .andExpect(status().is(422)); + bibtex.close(); + } + + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a pubmed XML + * file. + * + * @throws Exception + */ + public void createPubmedWorkspaceItemFromFileTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + InputStream xmlIS = getClass().getResourceAsStream("pubmed-test.xml"); + final MockMultipartFile pubmedFile = new MockMultipartFile("file", "pubmed-test.xml", + "application/xml", xmlIS); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + // bulk create workspaceitems in the default collection (col1) + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(pubmedFile)) + // bulk create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", + is("Multistep microreactions with proteins using electrocapture technology."))) + .andExpect( + jsonPath( + "$._embedded.workspaceitems[0].sections.traditionalpageone['dc.identifier.other'][0].value", + is(15117179))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.contributor.author'][0].value", + is("Astorga-Wells, J"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.date.issued'][0].value", + is("2004-05-01"))); + + // bulk create workspaceitems explicitly in the col2 + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(pubmedFile) + .param("collection", col2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", + is("Multistep microreactions with proteins using electrocapture technology."))) + .andExpect( + jsonPath( + "$._embedded.workspaceitems[0].sections.traditionalpageone['dc.identifier.other'][0].value", + is(15117179))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.contributor.author'][0].value", + is("Astorga-Wells, J"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.date.issued'][0].value", + is("2004-05-01"))); + + xmlIS.close(); + } + @Test /** * Test the creation of a workspaceitem POSTing to the resource collection endpoint a PDF file. As a single item diff --git a/dspace/config/spring/api/bibtex-integration.xml b/dspace/config/spring/api/bibtex-integration.xml index eade932459..9675ef82b3 100644 --- a/dspace/config/spring/api/bibtex-integration.xml +++ b/dspace/config/spring/api/bibtex-integration.xml @@ -25,47 +25,50 @@ - - - - - + - - - - - + - - - - - + - - - - - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 3c4a46377175fc7caeeacb55f653880b7f7f4d4f Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Tue, 23 Jun 2020 01:58:42 +0200 Subject: [PATCH 116/465] pubmed test data --- .../org/dspace/app/rest/pubmed-test.xml | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmed-test.xml diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmed-test.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmed-test.xml new file mode 100644 index 0000000000..3fdceb3880 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmed-test.xml @@ -0,0 +1,151 @@ + + + + + + 15117179 + + 2005 + 02 + 15 + + + 2006 + 11 + 15 + +
+ + 0003-2700 + + 76 + 9 + + 2004 + May + 01 + + + Analytical chemistry + Anal. Chem. + + Multistep microreactions with proteins using electrocapture technology. + + 2425-9 + + + A method to perform multistep reactions by means of electroimmobilization of a target molecule in a microflow stream is presented. A target protein is captured by the opposing effects between the hydrodynamic and electric forces, after which another medium is injected into the system. The second medium carries enzymes or other reagents, which are brought into contact with the target protein and react. The immobilization is reversed by disconnecting the electric field, upon which products are collected at the outlet of the device for analysis. On-line reduction, alkylation, and trypsin digestion of proteins is demonstrated and was monitored by MALDI mass spectrometry. + + + + Astorga-Wells + Juan + J + + Department of Medical Biochemistry and Biophysics, Karolinska Institutet, SE-171 77 Stockholm, Sweden. + + + + Bergman + Tomas + T + + + Jörnvall + Hans + H + + + eng + + Journal Article + Research Support, Non-U.S. Gov't + +
+ + United States + Anal Chem + 0370536 + 0003-2700 + + + + 0 + Proteins + + + EC 3.4.21.4 + Trypsin + + + IM + + + Animals + + + Cattle + + + Electrochemistry + + + Horses + + + Microfluidics + instrumentation + methods + + + Peptide Mapping + methods + + + Proteins + analysis + chemistry + + + Spectrometry, Mass, Matrix-Assisted Laser Desorption-Ionization + methods + + + Trypsin + chemistry + + +
+ + + + 2004 + 5 + 1 + 5 + 0 + + + 2005 + 2 + 16 + 9 + 0 + + + 2004 + 5 + 1 + 5 + 0 + + + ppublish + + 15117179 + 10.1021/ac0354342 + + +
+ +
\ No newline at end of file From 8407a2f6888658f01f8485462aff4cf2ea2641ab Mon Sep 17 00:00:00 2001 From: Danilo Di Nuzzo Date: Tue, 23 Jun 2020 11:54:48 +0200 Subject: [PATCH 117/465] [CST-2877] fix --- .../main/java/org/dspace/core/Context.java | 22 ------------------- .../main/java/org/dspace/core/I18nUtil.java | 19 ++++++++++++++++ .../AbstractDSpaceRestRepository.java | 22 +++++++------------ .../SubmissionFormRestRepository.java | 10 ++------- 4 files changed, 29 insertions(+), 44 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 3ede4ab5a0..4ea314e108 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -68,13 +68,6 @@ public class Context implements AutoCloseable { */ private Locale currentLocale; - /** - * List of locale object indicating the locales that are - * acceptable to the client based on the Accept-Language header. - * The Locales are in decreasing order starting with the preferred locale. - */ - private List clientLocales; - /** * Extra log info */ @@ -884,19 +877,4 @@ public class Context implements AutoCloseable { currentUser = reloadEntity(currentUser); } - /** - * Returns a list of locale object indicating the locales that are - * acceptable to the client based on the Accept-Language header. - * The Locales are in decreasing order starting with the preferred locale. - * @return List - */ - public List getClientLocales() { - return clientLocales; - } - - - public void setClientLocales(List clientLocales) { - this.clientLocales = clientLocales; - } - } diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index 0d5e95d048..d06b1e9bc8 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -381,4 +381,23 @@ public class I18nUtil { } return resultList.toArray(new Locale[resultList.size()]); } + + /** + * Check if the input locale is in the list of supported locales + * @param locale + * @return true if locale is supported, false otherwise + */ + public static boolean isSupportedLocale(Locale locale) { + boolean isSupported = false; + Locale[] supportedLocales = getSupportedLocales(); + if (supportedLocales != null) { + for (Locale sLocale: supportedLocales) { + if (locale.getLanguage().equals(sLocale.getLanguage()) ) { + isSupported = true; + break; + } + } + } + return isSupported; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java index 57650fa905..6460424f1c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java @@ -7,9 +7,7 @@ */ package org.dspace.app.rest.repository; -import java.util.ArrayList; import java.util.Enumeration; -import java.util.List; import java.util.Locale; import org.dspace.app.rest.converter.ConverterService; @@ -44,7 +42,6 @@ public abstract class AbstractDSpaceRestRepository { context = ContextUtil.obtainContext(currentRequest.getServletRequest()); Locale currentLocale = getLocal(context, currentRequest); context.setCurrentLocale(currentLocale); - context.setClientLocales(getLocalesFromRequest(currentRequest)); return context; } @@ -61,9 +58,16 @@ public abstract class AbstractDSpaceRestRepository { userLocale = new Locale(userLanguage); } } + // Locales requested from client Enumeration locales = request.getHttpServletRequest().getLocales(); if (locales != null) { - userLocale = locales.nextElement(); + while (locales.hasMoreElements()) { + Locale current = locales.nextElement(); + if (I18nUtil.isSupportedLocale(current)) { + userLocale = current; + break; + } + } } if (userLocale == null) { return I18nUtil.getDefaultLocale(); @@ -72,14 +76,4 @@ public abstract class AbstractDSpaceRestRepository { return supportedLocale; } - private List getLocalesFromRequest(Request request) { - List locales = new ArrayList<>(); - Enumeration reqLocales = request.getHttpServletRequest().getLocales(); - if (reqLocales != null) { - while (reqLocales.hasMoreElements()) { - locales.add(reqLocales.nextElement()); - } - } - return locales; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 1070848917..770438e183 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -46,14 +46,8 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository clientLocales = context.getClientLocales(); - DCInputsReader inputReader = null; - for (Locale locale: clientLocales) { - inputReader = inputReaders.get(locale); - if (inputReader != null) { - break; - } - } + Locale currentLocale = context.getCurrentLocale(); + DCInputsReader inputReader = inputReaders.get(currentLocale); if (inputReader == null) { inputReader = defaultInputReader; } From 0c4985e1cf2634f775bd4cbf8551c46b439703b6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 14:59:15 +0200 Subject: [PATCH 118/465] added implementations for getChoicesByParent and getTopChoices --- .../org/dspace/content/authority/Choice.java | 12 ++ .../authority/ChoiceAuthorityServiceImpl.java | 34 +++++ .../authority/DSpaceControlledVocabulary.java | 138 +++++++++++++++--- .../authority/HierarchicalAuthority.java | 70 +++++++++ .../service/ChoiceAuthorityService.java | 26 ++++ .../VocabularyEntryDetailsRestConverter.java | 1 + .../VocabularyEntryDetailsResource.java | 11 +- .../dspace/app/rest/utils/AuthorityUtils.java | 4 +- .../controlledvocabulary.xsd | 1 + 9 files changed, 276 insertions(+), 21 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 9b68c75d28..ff09e7553a 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -33,6 +33,11 @@ public class Choice { */ public String value = null; + /** + * A boolean representing if choice entry value can selected + */ + public boolean selectable = true; + public Map extras = new HashMap(); public Choice() { @@ -50,4 +55,11 @@ public class Choice { this.value = value; this.extras = extras; } + + public Choice(String authority, String label, String value, boolean selectable) { + this.authority = authority; + this.label = label; + this.value = value; + this.selectable = selectable; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index f907317a75..df5bd9335f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -477,4 +477,38 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService // currently only named authority can eventually provide real authority return controller.containsKey(fieldKey); } + + /** + * Wrapper that calls getChoicesByParent method of the plugin. + * + * @param authorityName authority name + * @param parentId parent Id + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String, + * int, int, java.lang.String) + */ + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); + return ma.getChoicesByParent(authorityName, parentId, start, limit, locale); + } + + /** + * Wrapper that calls getTopChoices method of the plugin. + * + * @param authorityName authority name + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String) + */ + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); + return ma.getTopChoices(authorityName, start, limit, locale); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 5f3837d9de..cd91636f18 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -54,13 +54,14 @@ import org.xml.sax.InputSource; * @author Michael B. Klein */ -public class DSpaceControlledVocabulary extends SelfNamedPlugin implements ChoiceAuthority { +public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class); protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + "'abcdefghijklmnopqrstuvwxyz'),'%s')]"; protected static String idTemplate = "//node[@id = '%s']"; protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy"; + protected static String rootTemplate = "/node"; protected static String pluginNames[] = null; protected String vocabularyName = null; @@ -68,6 +69,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic protected Boolean suggestHierarchy = false; protected Boolean storeHierarchy = true; protected String hierarchyDelimiter = "::"; + protected Integer preloadLevel = 1; + protected String rootNodeId = ""; public DSpaceControlledVocabulary() { super(); @@ -112,6 +115,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic String configurationPrefix = "vocabulary.plugin." + vocabularyName; storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); + preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel); String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter"); if (configuredDelimiter != null) { hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", ""); @@ -119,6 +123,17 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic String filename = vocabulariesPath + vocabularyName + ".xml"; log.info("Loading " + filename); vocabulary = new InputSource(filename); + + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node rootNode = (Node) xpath.evaluate(rootTemplate, vocabulary, XPathConstants.NODE); + Node idAttr = rootNode.getAttributes().getNamedItem("id"); + if (null != idAttr) { // 'id' is optional + rootNodeId = idAttr.getNodeValue(); + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + } } } @@ -151,7 +166,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); } XPath xpath = XPathFactory.newInstance().newXPath(); - Choice[] choices; + List choices = new ArrayList(); int total = 0; try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); @@ -160,32 +175,41 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic String[] values = new String[results.getLength()]; String[] labels = new String[results.getLength()]; String[] parent = new String[results.getLength()]; + Boolean[] selectable = new Boolean[results.getLength()]; + ArrayList[] children = new ArrayList[results.getLength()]; String[] notes = new String[results.getLength()]; for (int i = 0; i < total; i++) { Node node = results.item(i); - readNode(authorities, values, labels, parent, notes, i, node); + children[i] = new ArrayList(); + readNode(authorities, values, labels, parent,children[i], notes, selectable, i, node); } int resultCount = labels.length - start; // limit = 0 means no limit if ((limit > 0) && (resultCount > limit)) { resultCount = limit; } - choices = new Choice[resultCount]; if (resultCount > 0) { for (int i = 0; i < resultCount; i++) { - choices[i] = new Choice(authorities[start + i], values[start + i], labels[start + i]); + Choice choice = new Choice(authorities[start + i], values[start + i], labels[start + i], + selectable[start + i]); if (StringUtils.isNotBlank(parent[i])) { - choices[i].extras.put("parent", parent[i]); + choice.extras.put("parent", parent[i]); } if (StringUtils.isNotBlank(notes[i])) { - choices[i].extras.put("note", notes[i]); + choice.extras.put("note", notes[i]); } + if (children[i].size() > 0) { + choice.extras.put("hasChildren", "true"); + } else { + choice.extras.put("hasChildren", "false"); + } + choices.add(choice); } } } catch (XPathExpressionException e) { - choices = new Choice[0]; + log.warn(e.getMessage(), e); } - return new Choices(choices, start, total, Choices.CF_AMBIGUOUS, false); + return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, false); } @Override @@ -226,8 +250,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic return true; } - private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes, - int i, Node node) { + private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, + List children, String[] notes, Boolean[] selectable, int i, Node node) { String hierarchy = this.buildString(node); if (this.suggestHierarchy) { labels[i] = hierarchy; @@ -243,13 +267,25 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic NodeList childNodes = node.getChildNodes(); for (int ci = 0; ci < childNodes.getLength(); ci++) { Node firstChild = childNodes.item(ci); - if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { - String nodeValue = firstChild.getTextContent(); - if (StringUtils.isNotBlank(nodeValue)) { - notes[i] = nodeValue; + if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) { + for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) { + Node childN = firstChild.getChildNodes().item(cii); + if (childN != null && "node".equals(childN.getNodeName())) { + Node childIdAttr = childN.getAttributes().getNamedItem("id"); + if (null != childIdAttr) { + children.add(childIdAttr.getNodeValue()); + } + } } + break; } } + Node selectableAttr = node.getAttributes().getNamedItem("selectable"); + if (null != selectableAttr) { + selectable[i] = Boolean.valueOf(selectableAttr.getNodeValue()); + } else { // Default is true + selectable[i] = true; + } Node idAttr = node.getAttributes().getNamedItem("id"); if (null != idAttr) { // 'id' is optional authorities[i] = idAttr.getNodeValue(); @@ -259,8 +295,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic parentN = parentN.getParentNode(); if (parentN != null) { Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); - if (null != parentIdAttr) { - parent[i] = parentIdAttr.getNodeValue(); + if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { + parent[i] = hierarchy + hierarchyDelimiter + parentIdAttr.getNodeValue(); } } } @@ -271,4 +307,72 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } } + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + init(); + String xpathExpression = rootTemplate; + List choices = getChoicesByXpath(xpathExpression); + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + private List getChoicesByXpath(String xpathExpression) { + List choices = new ArrayList(); + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + if (parentNode != null) { + NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE); + if (null != childNodes) { + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + if (childNode != null && "node".equals(childNode.getNodeName())) { + choices.add(createChoiceFromNode(childNode)); + } + } + } + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + } + return choices; + } + + private Choice createChoiceFromNode(Node node) { + if (node != null) { + String[] authorities = new String[1]; + String[] values = new String[1]; + String[] labels = new String[1]; + String[] parent = new String[1]; + String[] note = new String[1]; + Boolean[] selectable = new Boolean[1]; + List children = new ArrayList(); + readNode(authorities, values, labels, parent, children, note, selectable, 0, node); + + if (values.length > 0) { + Choice choice = new Choice(authorities[0], values[0], labels[0], selectable[0]); + if (StringUtils.isNotBlank(parent[0])) { + choice.extras.put("parent", parent[0]); + } + if (StringUtils.isNotBlank(note[0])) { + choice.extras.put("note", note[0]); + } + return choice; + } + } + return new Choice("", "", ""); + } + + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + List choices = getChoicesByXpath(xpathExpression); + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + @Override + public Integer getPreloadLevel() { + return preloadLevel; + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java new file mode 100644 index 0000000000..200a8b809f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -0,0 +1,70 @@ +/** + * 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.authority; + +/** + * Plugin interface that supplies an authority control mechanism for + * one metadata field. + * + * @author Larry Stone + * @see ChoiceAuthority + */ +public interface HierarchicalAuthority extends ChoiceAuthority { + + /** + * Get all values from the authority that match the preferred value. + * Note that the offering was entered by the user and may contain + * mixed/incorrect case, whitespace, etc so the plugin should be careful + * to clean up user data before making comparisons. + * + * Value of a "Name" field will be in canonical DSpace person name format, + * which is "Lastname, Firstname(s)", e.g. "Smith, John Q.". + * + * Some authorities with a small set of values may simply return the whole + * set for any sample value, although it's a good idea to set the + * defaultSelected index in the Choices instance to the choice, if any, + * that matches the value. + * + * @param authorityName authority name + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + */ + public Choices getTopChoices(String authorityName, int start, int limit, String locale); + + /** + * Get all values from the authority that match the preferred value. + * Note that the offering was entered by the user and may contain + * mixed/incorrect case, whitespace, etc so the plugin should be careful + * to clean up user data before making comparisons. + * + * Value of a "Name" field will be in canonical DSpace person name format, + * which is "Lastname, Firstname(s)", e.g. "Smith, John Q.". + * + * Some authorities with a small set of values may simply return the whole + * set for any sample value, although it's a good idea to set the + * defaultSelected index in the Choices instance to the choice, if any, + * that matches the value. + * + * @param authorityName authority name + * @param parentId user's value to match + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + */ + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); + + public Integer getPreloadLevel(); + + default boolean isHierarchical() { + return true; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index b74bd71c98..fbd474a6f1 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -180,4 +180,30 @@ public interface ChoiceAuthorityService { */ public boolean storeAuthority(String fieldKey, Collection collection); + /** + * Wrapper that calls getChoicesByParent method of the plugin. + * + * @param authorityName authority name + * @param parentId parent Id + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String, + * int, int, java.lang.String) + */ + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); + + /** + * Wrapper that calls getTopChoices method of the plugin. + * + * @param authorityName authority name + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String) + */ + public Choices getTopChoices(String authorityName, int start, int limit, String locale); + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java index 63154ccf22..358a71455d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java @@ -32,6 +32,7 @@ public class VocabularyEntryDetailsRestConverter implements DSpaceConverter { +public class VocabularyEntryDetailsResource extends DSpaceResource { - public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry) { - super(entry); + public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry, Utils utils) { + super(entry, utils); + if (entry.isInHierarchicalVocabulary()) { + add(utils.linkToSubResource(entry, VocabularyEntryDetailsRest.PARENT)); + add(utils.linkToSubResource(entry, VocabularyEntryDetailsRest.CHILDREN)); + } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 2f58c19918..70a586007c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -64,10 +64,12 @@ public class AuthorityUtils { * @param projection the name of the projection to use, or {@code null}. * @return */ - public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, Projection projection) { + public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, + boolean isHierarchical, Projection projection) { VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); entry.setVocabularyName(authorityName); entry.setId(authorityName + ":" + entry.getId()); + entry.setInHierarchicalVocabulary(isHierarchical); return entry; } diff --git a/dspace/config/controlled-vocabularies/controlledvocabulary.xsd b/dspace/config/controlled-vocabularies/controlledvocabulary.xsd index 30fbb7f8ad..7a5defefbd 100644 --- a/dspace/config/controlled-vocabularies/controlledvocabulary.xsd +++ b/dspace/config/controlled-vocabularies/controlledvocabulary.xsd @@ -58,6 +58,7 @@ or refer to the Web site http://dspace-dev.dsi.uminho.pt. + From 530a9d5fac9eee6fdaa075619087fa519fa11b3d Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Tue, 23 Jun 2020 17:11:56 +0200 Subject: [PATCH 119/465] Tests and minor fix --- ...PubmedImportMetadataSourceServiceImpl.java | 2 +- .../external/service/ImportService.java | 23 +-- .../rest/repository/DSpaceRestRepository.java | 2 +- .../WorkspaceItemRestRepository.java | 146 +++++++++--------- .../rest/WorkspaceItemRestRepositoryIT.java | 31 ++-- .../config/spring/api/external-services.xml | 7 +- 6 files changed, 105 insertions(+), 106 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index 858953da1e..74bc6a8b45 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -401,7 +401,7 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat @Override public List getRecords(InputStream inputStream) throws FileSourceException { String xml = null; - try (Reader reader = new InputStreamReader(inputStream)) { + try (Reader reader = new InputStreamReader(inputStream, "UTF-8")) { xml = CharStreams.toString(reader); return parseXMLString(xml); } catch (IOException e) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java index 8590fb6114..5a838e8027 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java @@ -8,6 +8,9 @@ package org.dspace.importer.external.service; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Collections; @@ -302,20 +305,22 @@ public class ImportService implements Destroyable { * @return a single record contains the metadatum * @throws FileMultipleOccurencesException if more than one entry is found */ - public ImportRecord getRecord(InputStream fileInputStream) throws FileMultipleOccurencesException { + public ImportRecord getRecord(File file) throws FileMultipleOccurencesException, FileSourceException { ImportRecord importRecords = null; for (MetadataSource metadataSource : importSources.values()) { - if (metadataSource instanceof FileSource) { - FileSource fileSource = (FileSource)metadataSource; - try { + try (InputStream fileInputStream = new FileInputStream(file)) { + if (metadataSource instanceof FileSource) { + FileSource fileSource = (FileSource)metadataSource; importRecords = fileSource.getRecord(fileInputStream); break; - } catch (FileSourceException e) { - log.debug(metadataSource.getImportSource() + " isn't a valid parser for file"); - } catch (FileMultipleOccurencesException e) { - log.debug("File contains multiple metadata, return with error"); - throw e; } + } catch (FileSourceException e) { + log.debug(metadataSource.getImportSource() + " isn't a valid parser for file"); + } catch (FileMultipleOccurencesException e) { + log.debug("File contains multiple metadata, return with error"); + throw e; + } catch (IOException e1) { + throw new FileSourceException("File cannot be read, may be null"); } } return importRecords; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index e8bf235940..5dacf5a61c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -486,7 +486,7 @@ public abstract class DSpaceRestRepository upload(Context context, HttpServletRequest request, - MultipartFile uploadfile) + MultipartFile ... uploadfile) throws SQLException, FileNotFoundException, IOException, AuthorizeException { throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index a85f50414b..5827d8987e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -8,10 +8,8 @@ package org.dspace.app.rest.repository; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -350,85 +348,85 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository upload(Context context, HttpServletRequest request, - MultipartFile uploadfile) + MultipartFile... uploadfiles) throws SQLException, FileNotFoundException, IOException, AuthorizeException { - File file = Utils.getFile(uploadfile, "upload-loader", "filedataloader"); List results = new ArrayList<>(); - try (InputStream fileInputStream = new FileInputStream(file)) { - String uuid = request.getParameter("collection"); - if (StringUtils.isBlank(uuid)) { - uuid = configurationService.getProperty("submission.default.collection"); + String uuid = request.getParameter("owningCollection"); + if (StringUtils.isBlank(uuid)) { + uuid = configurationService.getProperty("submission.default.collection"); + } + Collection collection = null; + if (StringUtils.isNotBlank(uuid)) { + collection = collectionService.find(context, UUID.fromString(uuid)); + } else { + collection = collectionService.findAuthorizedOptimized(context, Constants.ADD).get(0); + } + + SubmissionConfig submissionConfig = + submissionConfigReader.getSubmissionConfigByCollection(collection.getHandle()); + List result = null; + List records = null; + try { + for (MultipartFile mpFile : uploadfiles) { + File file = Utils.getFile(mpFile, "upload-loader", "filedataloader"); + try { + if (records == null) { + records = new ArrayList<>(); + } + records.add(importService.getRecord(file)); + } finally { + file.delete(); + } } + } catch (FileMultipleOccurencesException e) { + throw new UnprocessableEntityException("Too many entries in file"); + } catch (Exception e) { + log.error("Error importing metadata", e); + } + WorkspaceItem source = submissionService. + createWorkspaceItem(context, getRequestService().getCurrentRequest()); + merge(context, records, source); + result = new ArrayList<>(); + result.add(source); - Collection collection = null; - if (StringUtils.isNotBlank(uuid)) { - collection = collectionService.find(context, UUID.fromString(uuid)); - } else { - collection = collectionService.findAuthorizedOptimized(context, Constants.ADD).get(0); - } - - SubmissionConfig submissionConfig = - submissionConfigReader.getSubmissionConfigByCollection(collection.getHandle()); - List result = null; - ImportRecord record = null; - try { - record = importService.getRecord(fileInputStream); - } catch (FileMultipleOccurencesException e) { - throw new UnprocessableEntityException("Too many entries in file"); - } catch ( Exception e) { - throw e; - } - WorkspaceItem source = submissionService. - createWorkspaceItem(context, getRequestService().getCurrentRequest()); - merge(context, record, source); - result = new ArrayList<>(); - result.add(source); - - //perform upload of bitstream if there is exact one result and convert workspaceitem to entity rest - if (result != null && !result.isEmpty()) { - for (WorkspaceItem wi : result) { - - List errors = new ArrayList(); - - //load bitstream into bundle ORIGINAL only if there is one result (approximately this is the - // right behaviour for pdf file but not for other bibliographic format e.g. bibtex) - if (result.size() == 1) { - - for (int i = 0; i < submissionConfig.getNumberOfSteps(); i++) { - SubmissionStepConfig stepConfig = submissionConfig.getStep(i); - - ClassLoader loader = this.getClass().getClassLoader(); - Class stepClass; - try { - stepClass = loader.loadClass(stepConfig.getProcessingClassName()); - - Object stepInstance = stepClass.newInstance(); - if (UploadableStep.class.isAssignableFrom(stepClass)) { - UploadableStep uploadableStep = (UploadableStep) stepInstance; - ErrorRest err = uploadableStep.upload(context, submissionService, stepConfig, wi, - uploadfile); + //perform upload of bitstream if there is exact one result and convert workspaceitem to entity rest + if (result != null && !result.isEmpty()) { + for (WorkspaceItem wi : result) { + List errors = new ArrayList(); + //load bitstream into bundle ORIGINAL only if there is one result (approximately this is the + // right behaviour for pdf file but not for other bibliographic format e.g. bibtex) + if (result.size() == 1) { + for (int i = 0; i < submissionConfig.getNumberOfSteps(); i++) { + SubmissionStepConfig stepConfig = submissionConfig.getStep(i); + ClassLoader loader = this.getClass().getClassLoader(); + Class stepClass; + try { + stepClass = loader.loadClass(stepConfig.getProcessingClassName()); + Object stepInstance = stepClass.newInstance(); + if (UploadableStep.class.isAssignableFrom(stepClass)) { + UploadableStep uploadableStep = (UploadableStep) stepInstance; + for (MultipartFile mpFile : uploadfiles) { + ErrorRest err = uploadableStep.upload(context, + submissionService, stepConfig, wi, mpFile); if (err != null) { errors.add(err); } } - - } catch (Exception e) { - log.error(e.getMessage(), e); } + } catch (Exception e) { + log.error(e.getMessage(), e); } } - WorkspaceItemRest wsi = converter.toRest(wi, utils.obtainProjection()); - if (result.size() == 1) { - if (!errors.isEmpty()) { - wsi.getErrors().addAll(errors); - } - } - results.add(wsi); } + WorkspaceItemRest wsi = converter.toRest(wi, utils.obtainProjection()); + if (result.size() == 1) { + if (!errors.isEmpty()) { + wsi.getErrors().addAll(errors); + } + } + results.add(wsi); } - } finally { - file.delete(); } return results; } @@ -480,7 +478,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository records, WorkspaceItem item) throws SQLException { for (MetadataValue metadataValue : itemService.getMetadata( item.getItem(), Item.ANY, Item.ANY, Item.ANY, Item.ANY)) { itemService.clearMetadata(context, item.getItem(), @@ -489,11 +487,13 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository
- + - + - - - From 6a88ef51b37338cd2f7ca28f3db54ee35cc54517 Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Tue, 23 Jun 2020 18:08:39 +0200 Subject: [PATCH 120/465] 71410: Authorization for Downloads of restricted Bitstreams - Don't update ePerson session salt when requesting a short lived token --- .../app/rest/security/jwt/JWTTokenHandler.java | 2 +- .../security/jwt/ShortLivedJWTTokenHandler.java | 12 ++++++++++++ .../jwt/ShortLivedJWTTokenHandlerTest.java | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index bec41c52ed..ab24a7cbae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -403,7 +403,7 @@ public abstract class JWTTokenHandler { * @return EPerson object of current user, with an updated session salt * @throws SQLException */ - private EPerson updateSessionSalt(final Context context, final Date previousLoginDate) throws SQLException { + protected EPerson updateSessionSalt(final Context context, final Date previousLoginDate) throws SQLException { EPerson ePerson; try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index 375bfe4ae5..902e391c30 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -17,6 +17,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.util.DateUtils; import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.springframework.stereotype.Component; @@ -55,6 +56,17 @@ public class ShortLivedJWTTokenHandler extends JWTTokenHandler { } } + /** + * The session salt doesn't need to be updated for short lived tokens. + * @param context current DSpace Context + * @param previousLoginDate date of last login (prior to this one) + * @return EPerson object of current user, with an updated session salt + */ + @Override + protected EPerson updateSessionSalt(final Context context, final Date previousLoginDate) { + return context.getCurrentUser(); + } + @Override protected String getTokenSecretConfigurationKey() { return "jwt.shortLived.token.secret"; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java index 38176ba57c..795694b202 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java @@ -15,14 +15,17 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Base64; import java.util.Date; +import javax.servlet.http.HttpServletRequest; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; +import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.mock.web.MockHttpServletRequest; @@ -38,6 +41,17 @@ public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { @Spy private ShortLivedJWTTokenHandler shortLivedJWTTokenHandler; + @Before + @Override + public void setUp() throws Exception { + when(ePerson.getSessionSalt()).thenReturn("01234567890123456789012345678901"); + when(context.getCurrentUser()).thenReturn(ePerson); + when(clientInfoService.getClientIp(any())).thenReturn("123.123.123.123"); + when(ePersonClaimProvider.getKey()).thenReturn("eid"); + when(ePersonClaimProvider.getValue(any(), Mockito.any(HttpServletRequest.class))).thenReturn("epersonID"); + jwtClaimProviders.add(ePersonClaimProvider); + } + @Test public void testJWTNoEncryption() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); From ed5ea81241386b5abf10356857960486d7c5e24f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 22:45:54 +0200 Subject: [PATCH 121/465] added implementations for link child and parent repositories --- .../authority/ChoiceAuthorityServiceImpl.java | 6 ++ .../authority/DSpaceControlledVocabulary.java | 61 +++++++++++--- .../authority/HierarchicalAuthority.java | 11 +++ .../service/ChoiceAuthorityService.java | 11 +++ .../model/VocabularyEntryDetailsRest.java | 24 ++++++ ...aryEntryDetailsChildrenLinkRepository.java | 83 +++++++++++++++++++ ...ularyEntryDetailsParentLinkRepository.java | 65 +++++++++++++++ 7 files changed, 248 insertions(+), 13 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index df5bd9335f..09724bb179 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -511,4 +511,10 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); return ma.getTopChoices(authorityName, start, limit, locale); } + + @Override + public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale) { + HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); + return ma.getParentChoice(authorityName, vocabularyId, start, limit, locale); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index cd91636f18..7a8cddfd82 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -10,7 +10,9 @@ package org.dspace.content.authority; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; @@ -190,19 +192,9 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } if (resultCount > 0) { for (int i = 0; i < resultCount; i++) { - Choice choice = new Choice(authorities[start + i], values[start + i], labels[start + i], + Choice choice = new Choice(authorities[start + i], labels[start + i], values[start + i], selectable[start + i]); - if (StringUtils.isNotBlank(parent[i])) { - choice.extras.put("parent", parent[i]); - } - if (StringUtils.isNotBlank(notes[i])) { - choice.extras.put("note", notes[i]); - } - if (children[i].size() > 0) { - choice.extras.put("hasChildren", "true"); - } else { - choice.extras.put("hasChildren", "false"); - } + choice.extras = addOtherInformation(parent[i], notes[i], children[i], authorities[i]); choices.add(choice); } } @@ -212,6 +204,24 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, false); } + private Map addOtherInformation(String parentCurr, String noteCurr, + ArrayList childrenCurr, String authorityCurr) { + Map extras = new HashMap(); + if (StringUtils.isNotBlank(parentCurr)) { + extras.put("parent", parentCurr); + } + if (StringUtils.isNotBlank(noteCurr)) { + extras.put("note", noteCurr); + } + if (childrenCurr.size() > 0) { + extras.put("hasChildren", "true"); + } else { + extras.put("hasChildren", "false"); + } + extras.put("id", authorityCurr); + return extras; + } + @Override public Choices getBestMatch(String field, String text, Collection collection, String locale) { init(); @@ -296,7 +306,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera if (parentN != null) { Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { - parent[i] = hierarchy + hierarchyDelimiter + parentIdAttr.getNodeValue(); + parent[i] = buildString(parentN); } } } @@ -370,6 +380,31 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); } + @Override + public Choice getParentChoice(String authorityName, String parentId, int start, int limit, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + Choice choice = getParent(xpathExpression); + return choice; + } + + private Choice getParent(String xpathExpression) { + Choice choice = null; + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + if (node != null) { + Node parent = node.getParentNode().getParentNode(); + if (null != parent) { + choice = createChoiceFromNode(parent); + } + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + } + return choice; + } + @Override public Integer getPreloadLevel() { return preloadLevel; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index 200a8b809f..18e088eb54 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -61,6 +61,17 @@ public interface HierarchicalAuthority extends ChoiceAuthority { */ public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); + /** + * + * @param authorityName authority name + * @param vocabularyId user's value to match + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choice object (never null). + */ + public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); + public Integer getPreloadLevel(); default boolean isHierarchical() { diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index fbd474a6f1..7afad12539 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -12,6 +12,7 @@ import java.util.Set; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; +import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; @@ -206,4 +207,14 @@ public interface ChoiceAuthorityService { */ public Choices getTopChoices(String authorityName, int start, int limit, String locale); + /** + * + * @param authorityName authority name + * @param vocabularyId child id + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choice object (never null). + */ + public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index e41ddafe45..42ce1f47a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -17,12 +17,21 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), + @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") + }) public class VocabularyEntryDetailsRest extends RestAddressableModel { public static final String NAME = "vocabularyEntryDetail"; + public static final String PARENT = "parent"; + public static final String CHILDREN = "children"; private String id; private String display; private String value; private Map otherInformation; + private boolean selectable; + @JsonIgnore + private boolean isInHierarchicalVocabulary = false; @JsonIgnore private String vocabularyName; @@ -86,4 +95,19 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { return RestResourceController.class; } + public Boolean isSelectable() { + return selectable; + } + + public void setSelectable(Boolean selectable) { + this.selectable = selectable; + } + + public void setInHierarchicalVocabulary(boolean isInHierarchicalVocabulary) { + this.isInHierarchicalVocabulary = isInHierarchicalVocabulary; + } + + public boolean isInHierarchicalVocabulary() { + return isInHierarchicalVocabulary; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java new file mode 100644 index 0000000000..3c002d8b25 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.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.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.NotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.PaginationException; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.Choices; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository to expose the parent of a vocabulary entry details in an hierarchical vocabulary + * + * @author Mykhaylo Boychuk (4Science.it) + */ +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.CHILDREN) +public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private ChoiceAuthorityService choiceAuthorityService; + + @Autowired + private AuthorityUtils authorityUtils; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page getChildren(@Nullable HttpServletRequest request, String name, + @Nullable Pageable pageable, Projection projection) { + + Context context = obtainContext(); + String[] parts = StringUtils.split(name, ":", 2); + if (parts.length != 2) { + return null; + } + String vocabularyName = parts[0]; + String id = parts[1]; + + List results = new ArrayList(); + ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); + if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { + Choices choices = choiceAuthorityService.getChoicesByParent(vocabularyName, id, (int) pageable.getOffset(), + pageable.getPageSize(), context.getCurrentLocale().toString()); + for (Choice value : choices.values) { + results.add(authorityUtils.convertEntryDetails(value, vocabularyName, authority.isHierarchical(), + utils.obtainProjection())); + } + } else { + throw new NotFoundException(); + } + Page resources; + try { + resources = utils.getPage(results, pageable); + } catch (PaginationException pe) { + resources = new PageImpl(new ArrayList(), pageable, + pe.getTotal()); + } + return resources; + } +} + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java new file mode 100644 index 0000000000..c105c69747 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -0,0 +1,65 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.NotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository to expose the parent of a vocabulary entry details in an hierarchical vocabulary + * + * @author Mykhaylo Boychuk ($science.it) + */ +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.PARENT) +public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private ChoiceAuthorityService choiceAuthorityService; + + @Autowired + private AuthorityUtils authorityUtils; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, + @Nullable Pageable pageable, Projection projection) { + Context context = obtainContext(); + String[] parts = StringUtils.split(name, ":", 2); + if (parts.length != 2) { + return null; + } + String vocabularyName = parts[0]; + String id = parts[1]; + + ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); + Choice choice = null; + if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { + choice = choiceAuthorityService.getParentChoice(vocabularyName, id, (int) pageable.getOffset(), + pageable.getPageSize(), context.getCurrentLocale().toString()); + } else { + throw new NotFoundException(); + } + return authorityUtils.convertEntryDetails(choice, vocabularyName, authority.isHierarchical(), + utils.obtainProjection()); + } +} From b940e3d41a59bbf181b227942425d68367bc4ff2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 22:48:54 +0200 Subject: [PATCH 122/465] implemented search method top --- .../VocabularyEntryDetailsRestRepository.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 0e4c39b7ab..7a417319cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -7,7 +7,13 @@ */ package org.dspace.app.rest.repository; +import java.util.ArrayList; +import java.util.List; + import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; @@ -15,10 +21,12 @@ import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -54,7 +62,33 @@ public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository findAllTop(@Parameter(value = "vocabulary", required = true) + String vocabularyId, Pageable pageable) { + Context context = obtainContext(); + List results = new ArrayList(); + ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(vocabularyId); + if (source.isHierarchical()) { + Choices choices = cas.getTopChoices(vocabularyId, (int)pageable.getOffset(), pageable.getPageSize(), + context.getCurrentLocale().toString()); + for (Choice value : choices.values) { + results.add(authorityUtils.convertEntryDetails(value, vocabularyId, source.isHierarchical(), + utils.obtainProjection())); + } + } + Page resources; + try { + resources = utils.getPage(results, pageable); + } catch (PaginationException pe) { + resources = new PageImpl(new ArrayList(), pageable, + pe.getTotal()); + } + return resources; } @Override From 8c5dc56193910ed3cfdaae85cf4391046128f515 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 22:50:55 +0200 Subject: [PATCH 123/465] updated tests of VocabularyEntryDetails --- .../content/authority/ChoiceAuthority.java | 8 + .../app/rest/VocabularyEntryDetailsIT.java | 187 ++++++++++-------- ...ava => VocabularyEntryDedailsMatcher.java} | 4 +- 3 files changed, 114 insertions(+), 85 deletions(-) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{AuthorityEntryMatcher.java => VocabularyEntryDedailsMatcher.java} (95%) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index 0fbab49bac..a3bfeb9e83 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -7,6 +7,9 @@ */ package org.dspace.content.authority; +import java.util.HashMap; +import java.util.Map; + import org.dspace.content.Collection; /** @@ -85,6 +88,10 @@ public interface ChoiceAuthority { return getLabel(key, locale); } + default Map getExtra(String key, String locale) { + return new HashMap(); + } + default boolean isHierarchical() { return false; } @@ -102,6 +109,7 @@ public interface ChoiceAuthority { result.authority = authKey; result.label = getLabel(authKey, locale); result.value = getValue(authKey, locale); + result.extras.putAll(getExtra(authKey, locale)); return result; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 20c36d6d2a..b6cf3bab6c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -14,7 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.UUID; -import org.dspace.app.rest.matcher.AuthorityEntryMatcher; +import org.dspace.app.rest.matcher.VocabularyEntryDedailsMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; @@ -64,38 +64,40 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); getClient(tokenEPerson).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @@ -105,11 +107,14 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") - ))) + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", + "Algebra, geometry and mathematical analysis"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + ))) + .andExpect(jsonPath("$._embedded.children[0].otherInformation.parent", + is("Research Subject Categories::MATHEMATICS"))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } @@ -121,41 +126,44 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("page", "0") .param("size", "5")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(0))); //second page - getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc") .param("page", "1") .param("size", "5")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(1))); // third page - getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc") .param("page", "2") .param("size", "5")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -177,14 +185,6 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isUnauthorized()); } - @Test - public void retrieveSrscValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1922") - .param("projection", "full")) - .andExpect(status().isOk()); - } - @Test public void srscSearchByParentFirstLevelPaginationTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); @@ -193,9 +193,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("page", "0") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("SCB1401", "Algebra, geometry and mathematical analysis"), - AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", + "Algebra, geometry and mathematical analysis"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -206,8 +207,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("page", "1") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( - AuthorityEntryMatcher.matchAuthority("SCB1409", "Other mathematics") + .andExpect(jsonPath("$._embedded.children", Matchers.contains( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -218,14 +219,14 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), - AuthorityEntryMatcher.matchAuthority("VR140203", "Mathematical statistics"), - AuthorityEntryMatcher.matchAuthority("VR140204", "Optimization, systems theory"), - AuthorityEntryMatcher.matchAuthority("VR140205", "Theoretical computer science") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140202", "Numerical analysis"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140203", "Mathematical statistics"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140204", "Optimization, systems theory"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140205", "Theoretical computer science") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @Test @@ -246,28 +247,48 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchTopUnauthorizedTest() throws Exception { - String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") - .param("vocabulary", "srsc")) - .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isUnauthorized()); } @Test - public void srscSearchParentByChildrenTest() throws Exception { + public void findParentByChildTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( - AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is(VocabularyEntryDedailsMatcher.matchAuthority( + "srsc:ResearchSubjectCategories", "Research Subject Categories") + ))); } @Test - public void srscSearchParentByChildrenRootTest() throws Exception { + public void findParentByChildSecondLevelTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + .andExpect(jsonPath("$", is( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE") + ))); + } + + @Test + public void findParentByChildBadRequestTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID() + "/parent")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findParentByChildUnauthorizedTest() throws Exception { + getClient().perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findParentRootChildTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc/parent")) + .andExpect(status().isNoContent()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java similarity index 95% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java index 519e77d05e..323d5ab14a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java @@ -18,9 +18,9 @@ import org.hamcrest.Matcher; /** * This matcher has been created so that we can use a predefined Matcher class to verify Authority Entries */ -public class AuthorityEntryMatcher { +public class VocabularyEntryDedailsMatcher { - private AuthorityEntryMatcher() { + private VocabularyEntryDedailsMatcher() { } public static Matcher matchAuthorityEntry(String id, String display, String value) { From 13727fb49aee07e838a1b0951be38b598689af89 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 24 Jun 2020 12:36:34 +0200 Subject: [PATCH 124/465] Added management of the parametr exact --- .../VocabularyEntryLinkRepository.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 814f9e4672..4507026cf3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -14,6 +14,7 @@ import java.util.UUID; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.VocabularyEntryRest; @@ -55,7 +56,9 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository public Page filter(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); + String exact = request == null ? null : request.getParameter("exact"); String filter = request == null ? null : request.getParameter("filter"); + String entryID = request == null ? null : request.getParameter("entryID"); String metadata = request == null ? null : request.getParameter("metadata"); String uuidCollectìon = request == null ? null : request.getParameter("collection"); @@ -63,6 +66,10 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository throw new IllegalArgumentException("the metadata and collection parameters are both required"); } + if (StringUtils.isNotBlank(filter) && StringUtils.isNotBlank(entryID)) { + throw new IllegalArgumentException("required only one of the parameters: filter or entryID"); + } + Collection collection = null; if (StringUtils.isNotBlank(uuidCollectìon)) { try { @@ -79,12 +86,17 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository throw new UnprocessableEntityException("The vocabulary " + name + " is not allowed for the metadata " + metadata + " and collection " + uuidCollectìon); } - Pageable pageable = utils.getPageable(optionalPageable); List results = new ArrayList<>(); String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); - Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), - pageable.getPageSize(), context.getCurrentLocale().toString()); + + Choices choices = null; + if (BooleanUtils.toBoolean(exact)) { + choices = cas.getBestMatch(fieldKey, filter, collection, context.getCurrentLocale().toString()); + } else { + choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), + pageable.getPageSize(), context.getCurrentLocale().toString()); + } boolean storeAuthority = cas.storeAuthority(fieldKey, collection); for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); From f39ce1cf54944ccbe7eae5aca4661f70669e7657 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 24 Jun 2020 12:37:09 +0200 Subject: [PATCH 125/465] Added ITs --- .../app/rest/VocabularyRestRepositoryIT.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index a1520a6e5e..bddfb7f5ba 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -395,4 +395,61 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.type")) .andExpect(status().isBadRequest()); } + + @Test + public void linkedEntitiesWithExactParamTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("filter", "Animation") + .param("exact", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries", Matchers.contains( + VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + + @Test + public void linkedEntitiesWrongMetataForAuthorityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("filter", "Animation")) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void linkedEntitiesWithFilterAndEntryIdTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) + .param("filter", "Research") + .param("entryID", "VR131402")) + .andExpect(status().isBadRequest()); + } } From ecefb1a30222dbf664f5f3fe59245274111d9875 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 24 Jun 2020 14:52:33 +0200 Subject: [PATCH 126/465] Comment and minor improvements --- .../SimpleMetadataContributor.java | 27 +++++++++---------- .../AbstractImportMetadataSourceService.java | 3 ++- .../dto/PlainMetadataKeyValueItem.java | 15 ++++++++++- .../dto/PlainMetadataSourceDto.java | 6 +++++ .../spring-dspace-addon-import-services.xml | 7 ----- .../rest/WorkspaceItemRestRepositoryIT.java | 2 +- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java index 8fcbfdb8b8..21dd1bfcee 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java @@ -19,9 +19,10 @@ import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValue import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; /** - * Metadata contributor that takes an PlainMetadataSourceDto instance and turns it into a metadatum + * Metadata contributor that takes an PlainMetadataSourceDto instance and turns it into a + * collection of metadatum * - * @author Roeland Dillen (roeland at atmire dot com) + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) */ public class SimpleMetadataContributor implements MetadataContributor { @@ -57,25 +58,21 @@ public class SimpleMetadataContributor implements MetadataContributor contributeMetadata(PlainMetadataSourceDto t) { List values = new LinkedList<>(); - try { - for (PlainMetadataKeyValueItem metadatum : t.getMetadata()) { - if (metadatum.getKey().equals(key)) { - MetadatumDTO dcValue = new MetadatumDTO(); - dcValue.setValue(metadatum.getValue()); - dcValue.setElement(field.getElement()); - dcValue.setQualifier(field.getQualifier()); - dcValue.setSchema(field.getSchema()); - values.add(dcValue); - } + for (PlainMetadataKeyValueItem metadatum : t.getMetadata()) { + if (key.equals(metadatum.getKey())) { + MetadatumDTO dcValue = new MetadatumDTO(); + dcValue.setValue(metadatum.getValue()); + dcValue.setElement(field.getElement()); + dcValue.setQualifier(field.getQualifier()); + dcValue.setSchema(field.getSchema()); + values.add(dcValue); } - } catch (Exception e) { - throw new RuntimeException(e); } return values; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java index f904ce613c..a803958a9d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java @@ -28,7 +28,8 @@ import org.springframework.beans.factory.annotation.Required; * * @author Roeland Dillen (roeland at atmire dot com) */ -public abstract class AbstractImportMetadataSourceService extends AbstractRemoteMetadataSource { +public abstract class AbstractImportMetadataSourceService extends AbstractRemoteMetadataSource + implements MetadataSource { private GenerateQueryService generateQueryForItem = null; private MetadataFieldMapping> metadataFieldMapping; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java index c3134db3a5..fa362760b9 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataKeyValueItem.java @@ -12,24 +12,37 @@ package org.dspace.importer.external.service.components.dto; * * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) */ - public class PlainMetadataKeyValueItem { private String key; private String value; + /* + * In a key-value items, like PlainMetadata, this method get the item's key + */ public String getKey() { return key; } + /* + * In a key-value items, like PlainMetadata, this method set the item's key. + * Never set or leave this field to null + * + */ public void setKey(String key) { this.key = key; } + /* + * In key-value items, like PlainMetadata, this method get the item's value + */ public String getValue() { return value; } + /* + * In key-value items, like PlainMetadata, this method set the item's value + */ public void setValue(String value) { this.value = value; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java index bf800f1e49..041823b027 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/dto/PlainMetadataSourceDto.java @@ -21,10 +21,16 @@ public class PlainMetadataSourceDto { private List metadata; + /* + * Method used to get the Metadata list + */ public List getMetadata() { return metadata; } + /* + * Method used to set the metadata list + */ public void setMetadata(List metadata) { this.metadata = metadata; } diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index b369b3295a..3b4f7bad31 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -53,13 +53,6 @@ - - - diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 297282980c..ba5f3226d0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -900,7 +900,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration String authToken = getAuthToken(eperson.getEmail(), password); // bulk create workspaceitems in the default collection (col1) getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") - .file(bibtexFile).param("owningCollection", col1.getID().toString())) + .file(bibtexFile).param("projection", "full")) // bulk create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", From c69168b27def235ed3451f34423d187f98e37361 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 00:06:00 +0200 Subject: [PATCH 127/465] Fix test and final cleanup --- .../authority/ChoiceAuthorityServiceImpl.java | 6 +- .../authority/DSpaceControlledVocabulary.java | 311 ++++++++++-------- .../authority/HierarchicalAuthority.java | 6 +- .../service/ChoiceAuthorityService.java | 6 +- .../converter/SubmissionFormConverter.java | 15 +- .../rest/exception/LinkNotFoundException.java | 30 ++ .../model/VocabularyEntryDetailsRest.java | 6 +- ...aryEntryDetailsChildrenLinkRepository.java | 16 +- ...ularyEntryDetailsParentLinkRepository.java | 5 +- .../VocabularyEntryDetailsRestRepository.java | 14 +- .../VocabularyEntryLinkRepository.java | 8 + .../dspace/app/rest/utils/AuthorityUtils.java | 6 + .../app/rest/VocabularyEntryDetailsIT.java | 177 ++++++---- ...ava => VocabularyEntryDetailsMatcher.java} | 26 +- 14 files changed, 374 insertions(+), 258 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{VocabularyEntryDedailsMatcher.java => VocabularyEntryDetailsMatcher.java} (65%) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 09724bb179..5e15d4d74a 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -458,7 +458,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collection collection) { init(); ChoiceAuthority ma = controller.get(fieldKey); - if (ma == null) { + if (ma == null && collection != null) { SubmissionConfigReader configReader; try { configReader = new SubmissionConfigReader(); @@ -513,8 +513,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale) { + public Choice getParentChoice(String authorityName, String vocabularyId, String locale) { HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); - return ma.getParentChoice(authorityName, vocabularyId, start, limit, locale); + return ma.getParentChoice(authorityName, vocabularyId, locale); } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 7a8cddfd82..c729c547c7 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -62,6 +62,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + "'abcdefghijklmnopqrstuvwxyz'),'%s')]"; protected static String idTemplate = "//node[@id = '%s']"; + protected static String labelTemplate = "//node[@label = '%s']"; protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy"; protected static String rootTemplate = "/node"; protected static String pluginNames[] = null; @@ -168,44 +169,122 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); } XPath xpath = XPathFactory.newInstance().newXPath(); - List choices = new ArrayList(); int total = 0; + List choices = new ArrayList(); try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); total = results.getLength(); - String[] authorities = new String[results.getLength()]; - String[] values = new String[results.getLength()]; - String[] labels = new String[results.getLength()]; - String[] parent = new String[results.getLength()]; - Boolean[] selectable = new Boolean[results.getLength()]; - ArrayList[] children = new ArrayList[results.getLength()]; - String[] notes = new String[results.getLength()]; - for (int i = 0; i < total; i++) { - Node node = results.item(i); - children[i] = new ArrayList(); - readNode(authorities, values, labels, parent,children[i], notes, selectable, i, node); - } - int resultCount = labels.length - start; - // limit = 0 means no limit - if ((limit > 0) && (resultCount > limit)) { - resultCount = limit; - } - if (resultCount > 0) { - for (int i = 0; i < resultCount; i++) { - Choice choice = new Choice(authorities[start + i], labels[start + i], values[start + i], - selectable[start + i]); - choice.extras = addOtherInformation(parent[i], notes[i], children[i], authorities[i]); - choices.add(choice); - } - } + choices = getChoicesFromNodeList(results, start, limit); } catch (XPathExpressionException e) { log.warn(e.getMessage(), e); + return new Choices(true); } - return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, false); + return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, + total > start + limit); + } + + @Override + public Choices getBestMatch(String field, String text, Collection collection, String locale) { + init(); + log.debug("Getting best matches for '" + text + "'"); + String xpathExpression = ""; + String[] textHierarchy = text.split(hierarchyDelimiter, -1); + for (int i = 0; i < textHierarchy.length; i++) { + xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); + } + XPath xpath = XPathFactory.newInstance().newXPath(); + List choices = new ArrayList(); + try { + NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + choices = getChoicesFromNodeList(results, 0, 1); + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + return new Choices(true); + } + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + @Override + public String getLabel(String key, String locale) { + return getNodeLabel(key, this.suggestHierarchy); + } + + @Override + public String getValue(String key, String locale) { + return getNodeLabel(key, this.storeHierarchy); + } + + @Override + public Choice getChoice(String authKey, String locale) { + Node node; + try { + node = getNode(authKey); + } catch (XPathExpressionException e) { + return null; + } + return createChoiceFromNode(node); + } + + @Override + public boolean isHierarchical() { + return true; + } + + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + init(); + String xpathExpression = rootTemplate; + return getChoicesByXpath(xpathExpression, start, limit); + } + + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + return getChoicesByXpath(xpathExpression, start, limit); + } + + @Override + public Choice getParentChoice(String authorityName, String parentId, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + Choice choice = getParent(xpathExpression); + return choice; + } + + @Override + public Integer getPreloadLevel() { + return preloadLevel; + } + + private Node getNode(String key) throws XPathExpressionException { + init(); + String xpathExpression = String.format(idTemplate, key); + XPath xpath = XPathFactory.newInstance().newXPath(); + Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + return node; + } + + private List getChoicesFromNodeList(NodeList results, int start, int limit) { + List choices = new ArrayList(); + for (int i = 0; i < results.getLength(); i++) { + if (i < start) { + continue; + } + if (choices.size() == limit) { + break; + } + Node node = results.item(i); + Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), + isSelectable(node)); + choice.extras = addOtherInformation(getParent(node), getNote(node), getChildren(node), getAuthority(node)); + choices.add(choice); + } + return choices; } private Map addOtherInformation(String parentCurr, String noteCurr, - ArrayList childrenCurr, String authorityCurr) { + List childrenCurr, String authorityCurr) { Map extras = new HashMap(); if (StringUtils.isNotBlank(parentCurr)) { extras.put("parent", parentCurr); @@ -222,29 +301,9 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return extras; } - @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { - init(); - log.debug("Getting best match for '" + text + "'"); - return getMatches(field, text, collection, 0, 2, locale); - } - - @Override - public String getLabel(String key, String locale) { - return getLabel(key, this.suggestHierarchy); - } - - @Override - public String getValue(String key, String locale) { - return getLabel(key, this.storeHierarchy); - } - - private String getLabel(String key, boolean useHierarchy) { - init(); - String xpathExpression = String.format(idTemplate, key); - XPath xpath = XPathFactory.newInstance().newXPath(); + private String getNodeLabel(String key, boolean useHierarchy) { try { - Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + Node node = getNode(key); if (useHierarchy) { return this.buildString(node); } else { @@ -255,25 +314,40 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } } - @Override - public boolean isHierarchical() { - return true; - } - - private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, - List children, String[] notes, Boolean[] selectable, int i, Node node) { + private String getLabel(Node node) { String hierarchy = this.buildString(node); if (this.suggestHierarchy) { - labels[i] = hierarchy; + return hierarchy; } else { - labels[i] = node.getAttributes().getNamedItem("label").getNodeValue(); - } - if (this.storeHierarchy) { - values[i] = hierarchy; - } else { - values[i] = node.getAttributes().getNamedItem("label").getNodeValue(); + return node.getAttributes().getNamedItem("label").getNodeValue(); } + } + private String getValue(Node node) { + String hierarchy = this.buildString(node); + if (this.storeHierarchy) { + return hierarchy; + } else { + return node.getAttributes().getNamedItem("label").getNodeValue(); + } + } + + private String getNote(Node node) { + NodeList childNodes = node.getChildNodes(); + for (int ci = 0; ci < childNodes.getLength(); ci++) { + Node firstChild = childNodes.item(ci); + if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { + String nodeValue = firstChild.getTextContent(); + if (StringUtils.isNotBlank(nodeValue)) { + return nodeValue; + } + } + } + return null; + } + + private List getChildren(Node node) { + List children = new ArrayList(); NodeList childNodes = node.getChildNodes(); for (int ci = 0; ci < childNodes.getLength(); ci++) { Node firstChild = childNodes.item(ci); @@ -290,102 +364,80 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera break; } } + return children; + } + + private boolean isSelectable(Node node) { Node selectableAttr = node.getAttributes().getNamedItem("selectable"); if (null != selectableAttr) { - selectable[i] = Boolean.valueOf(selectableAttr.getNodeValue()); + return Boolean.valueOf(selectableAttr.getNodeValue()); } else { // Default is true - selectable[i] = true; + return true; } - Node idAttr = node.getAttributes().getNamedItem("id"); - if (null != idAttr) { // 'id' is optional - authorities[i] = idAttr.getNodeValue(); - if (isHierarchical()) { - Node parentN = node.getParentNode(); - if (parentN != null) { - parentN = parentN.getParentNode(); - if (parentN != null) { - Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); - if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { - parent[i] = buildString(parentN); - } - } + } + + private String getParent(Node node) { + Node parentN = node.getParentNode(); + if (parentN != null) { + parentN = parentN.getParentNode(); + if (parentN != null) { + Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); + if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { + return buildString(parentN); } } + } + return null; + } + + private String getAuthority(Node node) { + Node idAttr = node.getAttributes().getNamedItem("id"); + if (null != idAttr) { // 'id' is optional + return idAttr.getNodeValue(); } else { - authorities[i] = null; - parent[i] = null; + return null; } } - @Override - public Choices getTopChoices(String authorityName, int start, int limit, String locale) { - init(); - String xpathExpression = rootTemplate; - List choices = getChoicesByXpath(xpathExpression); - return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - - private List getChoicesByXpath(String xpathExpression) { + private Choices getChoicesByXpath(String xpathExpression, int start, int limit) { List choices = new ArrayList(); XPath xpath = XPathFactory.newInstance().newXPath(); try { Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + int count = 0; if (parentNode != null) { NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE); if (null != childNodes) { for (int i = 0; i < childNodes.getLength(); i++) { Node childNode = childNodes.item(i); if (childNode != null && "node".equals(childNode.getNodeName())) { + if (count < start || choices.size() >= limit) { + count++; + continue; + } + count++; choices.add(createChoiceFromNode(childNode)); } } } + return new Choices(choices.toArray(new Choice[choices.size()]), start, count, + Choices.CF_AMBIGUOUS, false); } } catch (XPathExpressionException e) { log.warn(e.getMessage(), e); + return new Choices(true); } - return choices; + return new Choices(false); } private Choice createChoiceFromNode(Node node) { if (node != null) { - String[] authorities = new String[1]; - String[] values = new String[1]; - String[] labels = new String[1]; - String[] parent = new String[1]; - String[] note = new String[1]; - Boolean[] selectable = new Boolean[1]; - List children = new ArrayList(); - readNode(authorities, values, labels, parent, children, note, selectable, 0, node); - - if (values.length > 0) { - Choice choice = new Choice(authorities[0], values[0], labels[0], selectable[0]); - if (StringUtils.isNotBlank(parent[0])) { - choice.extras.put("parent", parent[0]); - } - if (StringUtils.isNotBlank(note[0])) { - choice.extras.put("note", note[0]); - } - return choice; - } + Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), + isSelectable(node)); + choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node)); + return choice; } - return new Choice("", "", ""); - } - - @Override - public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { - init(); - String xpathExpression = String.format(idTemplate, parentId); - List choices = getChoicesByXpath(xpathExpression); - return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - - @Override - public Choice getParentChoice(String authorityName, String parentId, int start, int limit, String locale) { - init(); - String xpathExpression = String.format(idTemplate, parentId); - Choice choice = getParent(xpathExpression); - return choice; + return null; } private Choice getParent(String xpathExpression) { @@ -405,9 +457,4 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return choice; } - @Override - public Integer getPreloadLevel() { - return preloadLevel; - } - } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index 18e088eb54..279f62d22f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -65,12 +65,10 @@ public interface HierarchicalAuthority extends ChoiceAuthority { * * @param authorityName authority name * @param vocabularyId user's value to match - * @param start choice at which to start, 0 is first. - * @param limit maximum number of choices to return, 0 for no limit. * @param locale explicit localization key if available, or null - * @return a Choice object (never null). + * @return a Choice object */ - public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); + public Choice getParentChoice(String authorityName, String vocabularyId, String locale); public Integer getPreloadLevel(); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 7afad12539..bfcee338b9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -211,10 +211,8 @@ public interface ChoiceAuthorityService { * * @param authorityName authority name * @param vocabularyId child id - * @param start choice at which to start, 0 is first. - * @param limit maximum number of choices to return, 0 for no limit. * @param locale explicit localization key if available, or null - * @return a Choice object (never null). + * @return the parent Choice object if any */ - public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); + public Choice getParentChoice(String authorityName, String vocabularyId, String locale); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index f8f34612eb..bb817bffea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -122,7 +122,8 @@ public class SubmissionFormConverter implements DSpaceConverter getModelClass() { return DCInputSet.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java new file mode 100644 index 0000000000..5710b7a176 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java @@ -0,0 +1,30 @@ +/** + * 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.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * This is the exception to capture details about a not existing linked resource + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "This link is not found in the system") +public class LinkNotFoundException extends RuntimeException { + String apiCategory; + String model; + String id; + + public LinkNotFoundException(String apiCategory, String model, String id) { + this.apiCategory = apiCategory; + this.model = model; + this.id = id; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 42ce1f47a5..1418bd8216 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -31,7 +31,7 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { private Map otherInformation; private boolean selectable; @JsonIgnore - private boolean isInHierarchicalVocabulary = false; + private boolean inHierarchicalVocabulary = false; @JsonIgnore private String vocabularyName; @@ -104,10 +104,10 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { } public void setInHierarchicalVocabulary(boolean isInHierarchicalVocabulary) { - this.isInHierarchicalVocabulary = isInHierarchicalVocabulary; + this.inHierarchicalVocabulary = isInHierarchicalVocabulary; } public boolean isInHierarchicalVocabulary() { - return isInHierarchicalVocabulary; + return inHierarchicalVocabulary; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java index 3c002d8b25..f9e8e5a7ad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -11,10 +11,9 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.NotFoundException; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.exception.PaginationException; +import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; @@ -67,17 +66,12 @@ public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpace results.add(authorityUtils.convertEntryDetails(value, vocabularyName, authority.isHierarchical(), utils.obtainProjection())); } + Page resources = new PageImpl(results, pageable, + choices.total); + return resources; } else { - throw new NotFoundException(); + throw new LinkNotFoundException(VocabularyRest.CATEGORY, VocabularyEntryDetailsRest.NAME, name); } - Page resources; - try { - resources = utils.getPage(results, pageable); - } catch (PaginationException pe) { - resources = new PageImpl(new ArrayList(), pageable, - pe.getTotal()); - } - return resources; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java index c105c69747..bebb0fdf85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -42,7 +42,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe @PreAuthorize("hasAuthority('AUTHENTICATED')") public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, - @Nullable Pageable pageable, Projection projection) { + @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); String[] parts = StringUtils.split(name, ":", 2); if (parts.length != 2) { @@ -54,8 +54,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); Choice choice = null; if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { - choice = choiceAuthorityService.getParentChoice(vocabularyName, id, (int) pageable.getOffset(), - pageable.getPageSize(), context.getCurrentLocale().toString()); + choice = choiceAuthorityService.getParentChoice(vocabularyName, id, context.getCurrentLocale().toString()); } else { throw new NotFoundException(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 7a417319cc..620bb527b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -13,7 +13,7 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.exception.PaginationException; +import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; @@ -80,15 +80,11 @@ public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository resources = new PageImpl(results, pageable, + choices.total); + return resources; } - Page resources; - try { - resources = utils.getPage(results, pageable); - } catch (PaginationException pe) { - resources = new PageImpl(new ArrayList(), pageable, - pe.getTotal()); - } - return resources; + throw new LinkNotFoundException(VocabularyRest.CATEGORY, VocabularyEntryDetailsRest.NAME, vocabularyId); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 4507026cf3..e18fc35d84 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -93,6 +93,14 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository Choices choices = null; if (BooleanUtils.toBoolean(exact)) { choices = cas.getBestMatch(fieldKey, filter, collection, context.getCurrentLocale().toString()); + } else if (StringUtils.isNotBlank(entryID)) { + Choice choice = cas.getChoiceAuthorityByAuthorityName(vocName).getChoice(entryID, + context.getCurrentLocale().toString()); + if (choice != null) { + choices = new Choices(new Choice[] {choice}, 0, 1, Choices.CF_ACCEPTED, false); + } else { + choices = new Choices(false); + } } else { choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 70a586007c..22e1ff6101 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -66,6 +66,9 @@ public class AuthorityUtils { */ public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, boolean isHierarchical, Projection projection) { + if (choice == null) { + return null; + } VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); entry.setVocabularyName(authorityName); entry.setId(authorityName + ":" + entry.getId()); @@ -75,6 +78,9 @@ public class AuthorityUtils { public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority, Projection projection) { + if (choice == null) { + return null; + } VocabularyEntryRest entry = new VocabularyEntryRest(); entry.setDisplay(choice.label); entry.setValue(choice.value); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index b6cf3bab6c..7af95646e8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -14,7 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.UUID; -import org.dspace.app.rest.matcher.VocabularyEntryDedailsMatcher; +import org.dspace.app.rest.matcher.VocabularyEntryDetailsMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; @@ -47,7 +47,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) - .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); + .andExpect(jsonPath("$.otherInformation.parent", + is("Research Subject Categories::HUMANITIES and RELIGION"))); } @Test @@ -65,19 +66,31 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", - "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", + "Research Subject Categories::LAW/JURISPRUDENCE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", + "Research Subject Categories::SOCIAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", + "Research Subject Categories::MATHEMATICS"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", + "Research Subject Categories::NATURAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", + "Research Subject Categories::TECHNOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", + "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", + "Research Subject Categories::MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", + "Research Subject Categories::ODONTOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", + "Research Subject Categories::PHARMACY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", + "Research Subject Categories::VETERINARY MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", + "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); @@ -85,19 +98,31 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", - "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", + "Research Subject Categories::LAW/JURISPRUDENCE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", + "Research Subject Categories::SOCIAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", + "Research Subject Categories::MATHEMATICS"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", + "Research Subject Categories::NATURAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", + "Research Subject Categories::TECHNOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", + "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", + "Research Subject Categories::MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", + "Research Subject Categories::ODONTOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", + "Research Subject Categories::PHARMACY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", + "Research Subject Categories::VETERINARY MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", + "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @@ -108,13 +133,16 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", - "Algebra, geometry and mathematical analysis"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", + "Algebra, geometry and mathematical analysis", + "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", + "Research Subject Categories::MATHEMATICS::Applied mathematics"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", + "Research Subject Categories::MATHEMATICS::Other mathematics") ))) - .andExpect(jsonPath("$._embedded.children[0].otherInformation.parent", - is("Research Subject Categories::MATHEMATICS"))) + .andExpect(jsonPath("$._embedded.children[*].otherInformation.parent", + Matchers.everyItem(is("Research Subject Categories::MATHEMATICS")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } @@ -127,11 +155,16 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", + "Research Subject Categories::LAW/JURISPRUDENCE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", + "Research Subject Categories::SOCIAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", + "Research Subject Categories::MATHEMATICS"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", + "Research Subject Categories::NATURAL SCIENCES") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -144,12 +177,17 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", - "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", + "Research Subject Categories::TECHNOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", + "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", + "Research Subject Categories::MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", + "Research Subject Categories::ODONTOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", + "Research Subject Categories::PHARMACY") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -162,8 +200,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", + "Research Subject Categories::VETERINARY MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", + "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -194,9 +234,11 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", - "Algebra, geometry and mathematical analysis"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", + "Algebra, geometry and mathematical analysis", + "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", + "Research Subject Categories::MATHEMATICS::Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -208,7 +250,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.contains( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", + "Research Subject Categories::MATHEMATICS::Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -219,14 +262,18 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140202", "Numerical analysis"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140203", "Mathematical statistics"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140204", "Optimization, systems theory"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140205", "Theoretical computer science") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140202", "Numerical analysis", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Numerical analysis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140203", "Mathematical statistics", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Mathematical statistics"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140204", "Optimization, systems theory", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Optimization, systems theory"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140205", "Theoretical computer science", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Theoretical computer science") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @Test @@ -257,8 +304,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest String tokenEperson = getAuthToken(eperson.getEmail(), password); getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is(VocabularyEntryDedailsMatcher.matchAuthority( - "srsc:ResearchSubjectCategories", "Research Subject Categories") + .andExpect(jsonPath("$", is(VocabularyEntryDetailsMatcher.matchAuthorityEntry( + "srsc:ResearchSubjectCategories", "Research Subject Categories", "Research Subject Categories") ))); } @@ -268,7 +315,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) .andExpect(status().isOk()) .andExpect(jsonPath("$", is( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE") + VocabularyEntryDetailsMatcher.matchAuthorityEntry( + "srsc:SCB18", "MEDICINE","Research Subject Categories::MEDICINE") ))); } @@ -288,7 +336,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void findParentRootChildTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc/parent")) - .andExpect(status().isNoContent()); + getClient(tokenEperson) + .perform(get("/api/submission/vocabularyEntryDetails/srsc:ResearchSubjectCategory/parent")) + .andExpect(status().isNoContent()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java similarity index 65% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java index 323d5ab14a..67747cff66 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -18,9 +17,9 @@ import org.hamcrest.Matcher; /** * This matcher has been created so that we can use a predefined Matcher class to verify Authority Entries */ -public class VocabularyEntryDedailsMatcher { +public class VocabularyEntryDetailsMatcher { - private VocabularyEntryDedailsMatcher() { + private VocabularyEntryDetailsMatcher() { } public static Matcher matchAuthorityEntry(String id, String display, String value) { @@ -31,7 +30,7 @@ public class VocabularyEntryDedailsMatcher { public static Matcher matchLinks() { return allOf( - hasJsonPath("$._links.self.href", containsString("api/integration/authority/"))); + hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/"))); } private static Matcher matchProperties(String id, String display, String value) { @@ -39,24 +38,7 @@ public class VocabularyEntryDedailsMatcher { hasJsonPath("$.id", is(id)), hasJsonPath("$.display", is(display)), hasJsonPath("$.value", is(value)), - hasJsonPath("$.type", is("authority")) - ); - } - - /** - * Gets a matcher for all expected embeds when the full projection is requested. - */ - public static Matcher matchFullEmbeds() { - return matchEmbeds( - "authorityEntries" - ); - } - - public static Matcher matchAuthority(String id, String value) { - return allOf( - hasJsonPath("$.id", is(id)), - hasJsonPath("$.value", is(value)), hasJsonPath("$.type", is("vocabularyEntryDetail")) - ); + ); } } From 9f77864cea295b834fed3b244b965440679d5f1d Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Thu, 25 Jun 2020 09:15:30 +0200 Subject: [PATCH 128/465] 71410: Authorization for Downloads of restricted Bitstreams - Add test that the ePerson session salt isn't updated when requesting a short lived token --- .../dspace/app/rest/AuthenticationRestControllerIT.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index e69f859983..c3834f37ed 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -13,6 +13,7 @@ import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -779,10 +780,16 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio @Test public void testShortLivedToken() throws Exception { String token = getAuthToken(eperson.getEmail(), password); + + // Verify the main session salt doesn't change + String salt = eperson.getSessionSalt(); + getClient(token).perform(post("/api/authn/shortlivedtokens")) .andExpect(jsonPath("$.token", notNullValue())) .andExpect(jsonPath("$.type", is("shortlivedtoken"))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authn/shortlivedtokens"))); + + assertEquals(salt, eperson.getSessionSalt()); } @Test From 6b923e0cb53cb344b8e8e3e7738c0b458c2b4e2b Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 09:23:34 +0200 Subject: [PATCH 129/465] Fix test - DSpaceControlledVocabulary now honor the pagination --- .../content/authority/DSpaceControlledVocabularyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 0d431a5a5b..84306ac034 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -78,7 +78,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { String text = "north 40"; Collection collection = null; int start = 0; - int limit = 0; + int limit = 10; String locale = null; // This "farm" Controlled Vocab is included in TestEnvironment data // (under /src/test/data/dspaceFolder/) and it should be auto-loaded From 9208879abffaf2866a3e7460ad3a4f32c0fc5f00 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 25 Jun 2020 11:57:05 +0200 Subject: [PATCH 130/465] Update test --- .../rest/WorkspaceItemRestRepositoryIT.java | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index ba5f3226d0..9dbdbb3138 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -892,23 +892,29 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .build(); InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); - final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", - bibtex); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "/local/path/bibtex-test.bib", + "application/x-bibtex", bibtex); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); // bulk create workspaceitems in the default collection (col1) getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") - .file(bibtexFile).param("projection", "full")) + .file(bibtexFile)) // bulk create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", is("My Article"))) .andExpect( jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("bibtex-test.bib"))) .andExpect( - jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); ; // bulk create workspaceitems explicitly in the col2 @@ -920,6 +926,12 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration is("My Article"))) .andExpect( jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col2.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload" + + ".files[0].metadata['dc.title'][0].value", + is("bibtex-test.bib"))) .andExpect( jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); @@ -996,7 +1008,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withSubmitterGroup(eperson) .build(); InputStream xmlIS = getClass().getResourceAsStream("pubmed-test.xml"); - final MockMultipartFile pubmedFile = new MockMultipartFile("file", "pubmed-test.xml", + final MockMultipartFile pubmedFile = new MockMultipartFile("file", "/local/path/pubmed-test.xml", "application/xml", xmlIS); context.restoreAuthSystemState(); @@ -1015,7 +1027,13 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration is("15117179"))) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + "['dc.contributor.author'][0].value", - is("Astorga-Wells, Juan"))); + is("Astorga-Wells, Juan"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/pubmed-test.xml"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("pubmed-test.xml"))); // bulk create workspaceitems explicitly in the col2 getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") @@ -1030,7 +1048,11 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration is("15117179"))) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + "['dc.contributor.author'][0].value", - is("Astorga-Wells, Juan"))); + is("Astorga-Wells, Juan"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0].metadata['dc.source'][0].value", + is("/local/path/pubmed-test.xml"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0].metadata['dc.title'][0].value", + is("pubmed-test.xml"))); xmlIS.close(); } From 82aae2395f22ca3d7631664ff4d14e56b77f5d8c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 13:33:06 +0200 Subject: [PATCH 131/465] Remove unnecessary change in the MetadataAuthorityService --- .../MetadataAuthorityServiceImpl.java | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java index 6a5b17a029..c542c6a89e 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java @@ -14,12 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.util.DCInput; -import org.dspace.app.util.DCInputSet; -import org.dspace.app.util.DCInputsReader; -import org.dspace.app.util.DCInputsReaderException; import org.dspace.content.MetadataField; import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.service.MetadataFieldService; @@ -144,8 +139,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService { if (dmc >= Choices.CF_UNSET) { defaultMinConfidence = dmc; } - - autoRegisterAuthorityFromInputReader(); } } @@ -205,7 +198,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService { } } - /** * Give the minimal level of confidence required to consider valid an authority value * for the given metadata. @@ -229,35 +221,4 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService { } return copy; } - - - private void autoRegisterAuthorityFromInputReader() { - try { - DCInputsReader dcInputsReader = new DCInputsReader(); - for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) { - DCInput[][] dcinputs = dcinputSet.getFields(); - for (DCInput[] dcrows : dcinputs) { - for (DCInput dcinput : dcrows) { - if (StringUtils.isNotBlank(dcinput.getPairsType()) - || StringUtils.isNotBlank(dcinput.getVocabulary())) { - String authorityName = dcinput.getPairsType(); - if (StringUtils.isBlank(authorityName)) { - authorityName = dcinput.getVocabulary(); - } - if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { - String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), - dcinput.getQualifier()); - boolean req = ConfigurationManager - .getBooleanProperty("authority.required." + fieldKey, false); - controlled.put(fieldKey, true); - isAuthorityRequired.put(fieldKey, req); - } - } - } - } - } - } catch (DCInputsReaderException e) { - throw new IllegalStateException(e.getMessage(), e); - } - } } From c0b92196f3e1489bac5a475369890c0df3fd60cc Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 25 Jun 2020 13:43:26 +0200 Subject: [PATCH 132/465] Fix multifile import --- .../app/rest/RestResourceController.java | 2 +- .../rest/repository/DSpaceRestRepository.java | 4 +- .../WorkspaceItemRestRepository.java | 4 +- .../rest/WorkspaceItemRestRepositoryIT.java | 89 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index a1684d782e..dbd9421bb2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -616,7 +616,7 @@ public class RestResourceController implements InitializingBean { HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model, - @RequestParam("file") MultipartFile uploadfile) + @RequestParam("file") List uploadfile) throws SQLException, FileNotFoundException, IOException, AuthorizeException { checkModelPluralForm(apiCategory, model); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 5dacf5a61c..149855c488 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -463,7 +463,7 @@ public abstract class DSpaceRestRepository upload(HttpServletRequest request, MultipartFile uploadfile) + public Iterable upload(HttpServletRequest request, List uploadfile) throws SQLException, FileNotFoundException, IOException, AuthorizeException { Context context = obtainContext(); Iterable entity = upload(context, request, uploadfile); @@ -486,7 +486,7 @@ public abstract class DSpaceRestRepository upload(Context context, HttpServletRequest request, - MultipartFile ... uploadfile) + List uploadfile) throws SQLException, FileNotFoundException, IOException, AuthorizeException { throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index 5827d8987e..4798d138b9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -348,7 +348,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository upload(Context context, HttpServletRequest request, - MultipartFile... uploadfiles) + List uploadfiles) throws SQLException, FileNotFoundException, IOException, AuthorizeException { List results = new ArrayList<>(); @@ -375,6 +375,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository(); } records.add(importService.getRecord(file)); + break; } finally { file.delete(); } @@ -394,6 +395,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository errors = new ArrayList(); + wi.setMultipleFiles(uploadfiles.size() > 1); //load bitstream into bundle ORIGINAL only if there is one result (approximately this is the // right behaviour for pdf file but not for other bibliographic format e.g. bibtex) if (result.size() == 1) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 9dbdbb3138..9b097db62c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -938,6 +938,95 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration bibtex.close(); } + + + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a + * bibtex and pubmed files + * + * @throws Exception + */ + public void createSingleWorkspaceItemFromMultipleFilesWithOneEntryTest() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "/local/path/bibtex-test.bib", + "application/x-bibtex", bibtex); + InputStream xmlIS = getClass().getResourceAsStream("pubmed-test.xml"); + final MockMultipartFile pubmedFile = new MockMultipartFile("file", "/local/path/pubmed-test.xml", + "application/xml", xmlIS); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + // bulk create workspaceitems in the default collection (col1) + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(bibtexFile).file(pubmedFile)) + // bulk create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("bibtex-test.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][1].value") + .doesNotExist()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[1]" + + ".metadata['dc.source'][0].value", + is("/local/path/pubmed-test.xml"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[1]" + + ".metadata['dc.title'][0].value", + is("pubmed-test.xml"))); + + // bulk create workspaceitems explicitly in the col2 + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(bibtexFile).file(pubmedFile) + .param("owningCollection", col2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col2.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload" + + ".files[0].metadata['dc.title'][0].value", + is("bibtex-test.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][1].value") + .doesNotExist()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[1]" + + ".metadata['dc.source'][0].value", + is("/local/path/pubmed-test.xml"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[1]" + + ".metadata['dc.title'][0].value", + is("pubmed-test.xml"))); + bibtex.close(); + xmlIS.close(); + } + @Test /** * Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file From 014c623256364e8088d710c48c7c73a7de624db4 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 25 Jun 2020 14:15:20 +0200 Subject: [PATCH 133/465] Manage empty or invalid files in multiple submission --- .../app/rest/repository/WorkspaceItemRestRepository.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index 4798d138b9..e66f67fd9a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -374,8 +374,11 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository(); } - records.add(importService.getRecord(file)); - break; + ImportRecord record = importService.getRecord(file); + if (record != null) { + records.add(record); + break; + } } finally { file.delete(); } From 32a03eed36a9fbe08e44d0bc6a945a4b5853a8d6 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 25 Jun 2020 16:17:36 +0200 Subject: [PATCH 134/465] [Task 71604] moved RegistrationRestControllerIT tests to RegistrationRestRepositoryIT and added extra cleanup --- .../rest/RegistrationRestControllerIT.java | 108 ------------------ .../rest/RegistrationRestRepositoryIT.java | 81 +++++++++++++ 2 files changed, 81 insertions(+), 108 deletions(-) delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java deleted file mode 100644 index d02dceea20..0000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestControllerIT.java +++ /dev/null @@ -1,108 +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.app.rest; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.Iterator; -import java.util.List; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.model.RegistrationRest; -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.eperson.RegistrationData; -import org.dspace.eperson.dao.RegistrationDataDAO; -import org.dspace.services.ConfigurationService; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -public class RegistrationRestControllerIT extends AbstractControllerIntegrationTest { - - @Autowired - private RegistrationDataDAO registrationDataDAO; - - @Autowired - private ConfigurationService configurationService; - - @Test - public void registrationFlowTest() throws Exception { - List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(0, registrationDataList.size()); - - ObjectMapper mapper = new ObjectMapper(); - RegistrationRest registrationRest = new RegistrationRest(); - registrationRest.setEmail(eperson.getEmail()); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(1, registrationDataList.size()); - assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); - - String newEmail = "newEPersonTest@gmail.com"; - registrationRest.setEmail(newEmail); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationDataList.size() == 2); - assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) || - StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); - configurationService.setProperty("user.registration", false); - - newEmail = "newEPersonTestTwo@gmail.com"; - registrationRest.setEmail(newEmail); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().is(401)); - - assertEquals(2, registrationDataList.size()); - assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && - !StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); - - Iterator iterator = registrationDataList.iterator(); - while (iterator.hasNext()) { - RegistrationData registrationData = iterator.next(); - registrationDataDAO.delete(context, registrationData); - } - } - - @Test - public void forgotPasswordTest() throws Exception { - context.turnOffAuthorisationSystem(); - configurationService.setProperty("user.registration", false); - context.restoreAuthSystemState(); - - List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(0, registrationDataList.size()); - - ObjectMapper mapper = new ObjectMapper(); - RegistrationRest registrationRest = new RegistrationRest(); - registrationRest.setEmail(eperson.getEmail()); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(1, registrationDataList.size()); - assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); - Iterator iterator = registrationDataList.iterator(); - while (iterator.hasNext()) { - RegistrationData registrationData = iterator.next(); - registrationDataDAO.delete(context, registrationData); - } - context.complete(); - } -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java index 88feebb286..2709042cbd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java @@ -7,19 +7,24 @@ */ package org.dspace.app.rest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; 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.Iterator; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.matcher.RegistrationMatcher; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.eperson.RegistrationData; import org.dspace.eperson.dao.RegistrationDataDAO; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +34,9 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT @Autowired private RegistrationDataDAO registrationDataDAO; + @Autowired + private ConfigurationService configurationService; + @Test public void findByTokenTestExistingUserTest() throws Exception { String email = eperson.getEmail(); @@ -41,6 +49,8 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT .andExpect( jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, eperson.getID())))); + registrationDataDAO.delete(context, registrationData); + email = "newUser@testnewuser.com"; createTokenForEmail(email); registrationData = registrationDataDAO.findByEmail(context, email); @@ -87,4 +97,75 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT .contentType(contentType)) .andExpect(status().isCreated()); } + + @Test + public void registrationFlowTest() throws Exception { + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(0, registrationDataList.size()); + + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); + + String newEmail = "newEPersonTest@gmail.com"; + registrationRest.setEmail(newEmail); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationDataList.size() == 2); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) || + StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); + configurationService.setProperty("user.registration", false); + + newEmail = "newEPersonTestTwo@gmail.com"; + registrationRest.setEmail(newEmail); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().is(401)); + + assertEquals(2, registrationDataList.size()); + assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && + !StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); + + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + + @Test + public void forgotPasswordTest() throws Exception { + configurationService.setProperty("user.registration", false); + + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(0, registrationDataList.size()); + + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } From 76181a682919be046a4540a1c9ffe18d1a53dcbc Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 17:37:24 +0200 Subject: [PATCH 135/465] Restore test for authority as was before - no authorities for value pairs --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 5 +++++ dspace-api/src/test/java/org/dspace/content/ItemTest.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 51ce1a0165..029c4be664 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -109,6 +109,11 @@ plugin.sequence.java.util.Collection = \ java.util.Stack, \ java.util.TreeSet +# Enable a test authority control on dc.language.iso field +choices.plugin.dc.language.iso = common_iso_languages +choices.presentation.dc.language.iso = select +authority.controlled.dc.language.iso = true + ########################################### # PROPERTIES USED TO TEST CONFIGURATION # # PROPERTY EXPOSURE VIA REST # diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 8c3cfa5a04..494368230b 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -490,8 +490,8 @@ public class ItemTest extends AbstractDSpaceObjectTest { // Set the item to have two pieces of metadata for dc.type and dc2.type String dcType = "DC-TYPE"; String testType = "TEST-TYPE"; - itemService.addMetadata(context, it, "dc", "type", null, null, dcType, "accepted", 0); - itemService.addMetadata(context, it, "test", "type", null, null, testType, "accepted", 0); + itemService.addMetadata(context, it, "dc", "type", null, null, dcType); + itemService.addMetadata(context, it, "test", "type", null, null, testType); // Check that only one is returned when we ask for all dc.type values List values = itemService.getMetadata(it, "dc", "type", null, null); From 42dd930060cfaad1dc742c8b9821fa4453e523da Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 17:47:17 +0200 Subject: [PATCH 136/465] Rename authority in controlledVocabulary, add related IT --- .../converter/SubmissionFormConverter.java | 4 +- .../rest/model/submit/SelectableMetadata.java | 15 ++++--- .../app/rest/SubmissionFormsControllerIT.java | 26 +++++++++++ .../matcher/SubmissionFormFieldMatcher.java | 44 ++++++++++++++++--- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index bb817bffea..ce54169cf0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -118,7 +118,7 @@ public class SubmissionFormConverter implements DSpaceConverter matchFormFieldDefinition(String type, String label, String mandatoryMessage, - boolean repeatable, - String hints, String style, String metadata) { + boolean repeatable, + String hints, String style, String metadata) { + return matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, style, metadata, null); + } + + /** + * Check the json representation of a submission form + * + * @param type + * the expected input type + * @param label + * the expected label + * @param mandatoryMessage + * the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as + * mandatory + * @param repeatable + * the expected repeatable flag + * @param hints + * the expected hints message + * @param style + * the expected style for the field, can be null. If null the corresponding json path is expected to be + * missing + * @param metadata + * the expected metadata + * @param controlled vocabulary + * the expected controlled vocabulary, can be null. If null the corresponding json path is expected to be + * missing + * @return a Matcher for all the condition above + */ + public static Matcher matchFormFieldDefinition(String type, String label, String mandatoryMessage, + boolean repeatable, String hints, String style, + String metadata, String controlledVocabulary) { return allOf( // check each field definition hasJsonPath("$.input.type", is(type)), hasJsonPath("$.label", containsString(label)), hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)), + controlledVocabulary != null ? hasJsonPath("$.selectableMetadata[0].controlledVocabulary", + is(controlledVocabulary)) : hasNoJsonPath("$.selectableMetadata[0].controlledVocabulary"), mandatoryMessage != null ? hasJsonPath("$.mandatoryMessage", containsString(mandatoryMessage)) : hasNoJsonPath("$.mandatoryMessage"), hasJsonPath("$.mandatory", is(mandatoryMessage != null)), From a8234f2004cfdd0fce7c496b8718379bb67f71ab Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 19:25:27 +0200 Subject: [PATCH 137/465] Fix vocabulary related links test in the root endpoint --- .../java/org/dspace/app/rest/RootRestResourceControllerIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java index 6c1c4a9427..b4cee1672e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java @@ -49,7 +49,8 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //Check that all required root links are present and that they are absolute - .andExpect(jsonPath("$._links.authorities.href", startsWith(BASE_REST_SERVER_URL))) + .andExpect(jsonPath("$._links.vocabularies.href", startsWith(BASE_REST_SERVER_URL))) + .andExpect(jsonPath("$._links.vocabularyEntryDetails.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.bitstreamformats.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.bitstreams.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.browses.href", startsWith(BASE_REST_SERVER_URL))) From ba150b1ec7e33e9a2c1cf38b94927f016ff03862 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 19:26:52 +0200 Subject: [PATCH 138/465] Fix cleanup testenv --- .../dspace/app/rest/VocabularyRestRepositoryIT.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index bddfb7f5ba..898e63cf78 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -26,6 +26,7 @@ import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -96,6 +97,15 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes AuthorityServiceFactory.getInstance().getAuthorityIndexingService().commit(); } + @Override + @After + // We need to cleanup the authorities cache once than the configuration has been restored + public void destroy() throws Exception { + super.destroy(); + pluginService.clearNamedPluginClasses(); + cas.clearCache(); + } + @Test public void findAllTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); From 322b56b42275c0fdc2cdd7809ee653acab4392d7 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 20:14:01 +0200 Subject: [PATCH 139/465] Expose the vocabularyName to the submission form definition --- .../app/rest/converter/SubmissionFormConverter.java | 11 ++++++++++- .../dspace/app/rest/SubmissionFormsControllerIT.java | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index ce54169cf0..3f233a34c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -115,7 +115,8 @@ public class SubmissionFormConverter implements DSpaceConverter getModelClass() { return DCInputSet.class; 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 73b6224d9b..dee7d4c611 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 @@ -160,7 +160,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe null, true, "Select the type(s) of content of the item. To select more than one value in the " + "list, you may have to hold down the \"CTRL\" or \"Shift\" key.", - "dc.type") + null, "dc.type", "common_types") ))) ; } From 27e733c1e8c557284bb756f32bc09c6a937bc4ee Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Fri, 26 Jun 2020 14:20:06 +0200 Subject: [PATCH 140/465] 71410: Authorization for Downloads of restricted Bitstreams - Add test that the ePerson session salt isn't updated when requesting a short lived token --- .../rest/AuthenticationRestController.java | 2 +- .../AuthenticationTokenHalLinkFactory.java | 2 +- .../rest/model/AuthenticationStatusRest.java | 2 +- .../rest/model/AuthenticationTokenRest.java | 2 +- .../org/dspace/app/rest/model/AuthnRest.java | 2 +- .../org/dspace/app/rest/model/RestModel.java | 1 + ...JWTTokenRestAuthenticationServiceImpl.java | 14 +++---- ...Handler.java => LoginJWTTokenHandler.java} | 14 +++---- .../rest/AuthenticationRestControllerIT.java | 39 ++++++++++++++++--- .../security/jwt/JWTTokenHandlerTest.java | 28 ++++++------- dspace/config/modules/authentication.cfg | 12 +++--- 11 files changed, 74 insertions(+), 44 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/{SessionJWTTokenHandler.java => LoginJWTTokenHandler.java} (72%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index 0b60fa1cd4..3038011009 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -138,7 +138,7 @@ public class AuthenticationRestController implements InitializingBean { */ @PreAuthorize("hasAuthority('AUTHENTICATED')") @RequestMapping(value = "/shortlivedtokens", method = RequestMethod.POST) - public AuthenticationTokenResource shortLivedLogin(HttpServletRequest request) { + public AuthenticationTokenResource shortLivedToken(HttpServletRequest request) { Projection projection = utils.obtainProjection(); AuthenticationToken shortLivedToken = restAuthenticationService.getShortLivedAuthenticationToken(ContextUtil.obtainContext(request), request); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java index 180a3b7e47..ea70f08923 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthenticationTokenHalLinkFactory.java @@ -27,7 +27,7 @@ public class AuthenticationTokenHalLinkFactory protected void addLinks(AuthenticationTokenResource halResource, Pageable pageable, LinkedList list) throws Exception { - list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().shortLivedLogin(null))); + list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().shortLivedToken(null))); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java index cddfe34a22..a137620e6b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java @@ -18,7 +18,7 @@ public class AuthenticationStatusRest extends BaseObjectRest { private boolean authenticated; public static final String NAME = "status"; - public static final String CATEGORY = "authn"; + public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java index 130b5cc162..0599e09565 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java @@ -15,7 +15,7 @@ import org.dspace.app.rest.RestResourceController; */ public class AuthenticationTokenRest extends RestAddressableModel { public static final String NAME = "shortlivedtoken"; - public static final String CATEGORY = "authn"; + public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; private String token; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java index dd225de1c7..fade90fe4d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java @@ -18,7 +18,7 @@ import org.dspace.app.rest.AuthenticationRestController; public class AuthnRest extends BaseObjectRest { public static final String NAME = "authn"; - public static final String CATEGORY = "authn"; + public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index 940f4a4deb..0b32aedf92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -32,6 +32,7 @@ public interface RestModel extends Serializable { public static final String WORKFLOW = "workflow"; public static final String AUTHORIZATION = "authz"; public static final String VERSIONING = "versioning"; + public static final String AUTHENTICATION = "authn"; public String getType(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index 123f96f89b..bce2724c8d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -51,7 +51,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication private static final String AUTHORIZATION_TOKEN_PARAMETER = "token"; @Autowired - private SessionJWTTokenHandler sessionJWTTokenHandler; + private LoginJWTTokenHandler loginJWTTokenHandler; @Autowired private ShortLivedJWTTokenHandler shortLivedJWTTokenHandler; @@ -76,7 +76,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication List groups = authenticationService.getSpecialGroups(context, request); - String token = sessionJWTTokenHandler.createTokenForEPerson(context, request, + String token = loginJWTTokenHandler.createTokenForEPerson(context, request, authentication.getPreviousLoginDate(), groups); addTokenToResponse(response, token, addCookie); @@ -115,13 +115,13 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication @Override public EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context) { try { - String token = getSessionToken(request); + String token = getLoginToken(request); EPerson ePerson = null; if (token == null) { token = getShortLivedToken(request); ePerson = shortLivedJWTTokenHandler.parseEPersonFromToken(token, request, context); } else { - ePerson = sessionJWTTokenHandler.parseEPersonFromToken(token, request, context); + ePerson = loginJWTTokenHandler.parseEPersonFromToken(token, request, context); } return ePerson; } catch (JOSEException e) { @@ -144,9 +144,9 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication @Override public void invalidateAuthenticationData(HttpServletRequest request, HttpServletResponse response, Context context) throws Exception { - String token = getSessionToken(request); + String token = getLoginToken(request); invalidateAuthenticationCookie(response); - sessionJWTTokenHandler.invalidateToken(token, request, context); + loginJWTTokenHandler.invalidateToken(token, request, context); } @Override @@ -201,7 +201,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication response.setHeader(AUTHORIZATION_HEADER, String.format("%s %s", AUTHORIZATION_TYPE, token)); } - private String getSessionToken(HttpServletRequest request) { + private String getLoginToken(HttpServletRequest request) { String tokenValue = null; String authHeader = request.getHeader(AUTHORIZATION_HEADER); String authCookie = getAuthorizationCookie(request); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java similarity index 72% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java index 4d70c4b10d..9446834519 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SessionJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java @@ -14,34 +14,34 @@ import org.springframework.stereotype.Component; * https://jwt.io/ */ @Component -public class SessionJWTTokenHandler extends JWTTokenHandler { +public class LoginJWTTokenHandler extends JWTTokenHandler { @Override protected String getTokenSecretConfigurationKey() { - return "jwt.session.token.secret"; + return "jwt.login.token.secret"; } @Override protected String getEncryptionSecretConfigurationKey() { - return "jwt.session.encryption.secret"; + return "jwt.login.encryption.secret"; } @Override protected String getTokenExpirationConfigurationKey() { - return "jwt.session.token.expiration"; + return "jwt.login.token.expiration"; } @Override protected String getTokenIncludeIPConfigurationKey() { - return "jwt.session.token.include.ip"; + return "jwt.login.token.include.ip"; } @Override protected String getEncryptionEnabledConfigurationKey() { - return "jwt.session.encryption.enabled"; + return "jwt.login.encryption.enabled"; } @Override protected String getCompressionEnabledConfigurationKey() { - return "jwt.session.compression.enabled"; + return "jwt.login.compression.enabled"; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index c3834f37ed..b04c99b0e8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -34,6 +34,7 @@ import org.dspace.app.rest.builder.BitstreamBuilder; import org.dspace.app.rest.builder.BundleBuilder; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.AuthenticationStatusMatcher; @@ -808,11 +809,28 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio } @Test - public void testSessionTokenToDowloadBitstream() throws Exception { + public void testShortLivedTokenToDowloadBitstreamUnauthorized() throws Exception { Bitstream bitstream = createPrivateBitstream(); - String sessionToken = getAuthToken(eperson.getEmail(), password); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + sessionToken)) + context.turnOffAuthorisationSystem(); + EPerson testEPerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("UnauthorizedUser@example.com") + .withPassword(password) + .build(); + context.restoreAuthSystemState(); + + String shortLivedToken = getShortLivedToken(testEPerson); + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + shortLivedToken)) + .andExpect(status().isForbidden()); + } + + @Test + public void testLoginTokenToDowloadBitstream() throws Exception { + Bitstream bitstream = createPrivateBitstream(); + + String loginToken = getAuthToken(eperson.getEmail(), password); + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + loginToken)) .andExpect(status().isForbidden()); } @@ -826,10 +844,21 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isForbidden()); } - private String getShortLivedToken(EPerson ePerson) throws Exception { - ObjectMapper mapper = new ObjectMapper(); + @Test + public void testShortLivedAndLoginTokenSeparation() throws Exception { + configurationService.setProperty("jwt.shortLived.token.expiration", "1"); String token = getAuthToken(eperson.getEmail(), password); + Thread.sleep(2); + getClient(token).perform(get("/api/authn/status").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authenticated", is(true))); + } + + private String getShortLivedToken(EPerson requestUser) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + String token = getAuthToken(requestUser.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/authn/shortlivedtokens")) .andReturn(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java index 1a33ae7484..3b0eb84793 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java @@ -47,7 +47,7 @@ public class JWTTokenHandlerTest { @InjectMocks @Spy - private SessionJWTTokenHandler sessionJWTTokenHandler; + private LoginJWTTokenHandler loginJWTTokenHandler; @Mock protected ConfigurationService configurationService; @@ -91,7 +91,7 @@ public class JWTTokenHandlerTest { @Test public void testJWTNoEncryption() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); - String token = sessionJWTTokenHandler + String token = loginJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); SignedJWT signedJWT = SignedJWT.parse(token); String personId = (String) signedJWT.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID); @@ -100,11 +100,11 @@ public class JWTTokenHandlerTest { @Test(expected = ParseException.class) public void testJWTEncrypted() throws Exception { - when(sessionJWTTokenHandler.isEncryptionEnabled()).thenReturn(true); + when(loginJWTTokenHandler.isEncryptionEnabled()).thenReturn(true); Date previous = new Date(System.currentTimeMillis() - 10000000000L); StringKeyGenerator keyGenerator = KeyGenerators.string(); - when(configurationService.getProperty("jwt.session.encryption.secret")).thenReturn(keyGenerator.generateKey()); - String token = sessionJWTTokenHandler + when(configurationService.getProperty("jwt.login.encryption.secret")).thenReturn(keyGenerator.generateKey()); + String token = loginJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); SignedJWT signedJWT = SignedJWT.parse(token); } @@ -112,12 +112,12 @@ public class JWTTokenHandlerTest { //temporary set a negative expiration time so the token is invalid immediately @Test public void testExpiredToken() throws Exception { - when(configurationService.getLongProperty("jwt.session.token.expiration", 1800000)).thenReturn(-99999999L); + when(configurationService.getLongProperty("jwt.login.token.expiration", 1800000)).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); - String token = sessionJWTTokenHandler + String token = loginJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); - EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); + EPerson parsed = loginJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); assertEquals(null, parsed); } @@ -125,17 +125,17 @@ public class JWTTokenHandlerTest { //Try if we can change the expiration date @Test public void testTokenTampering() throws Exception { - when(sessionJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); + when(loginJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); - String token = sessionJWTTokenHandler + String token = loginJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().claim("eid", "epersonID").expirationTime( new Date(System.currentTimeMillis() + 99999999)).build(); String tamperedPayload = new String(Base64.getUrlEncoder().encode(jwtClaimsSet.toString().getBytes())); String[] splitToken = token.split("\\."); String tamperedToken = splitToken[0] + "." + tamperedPayload + "." + splitToken[2]; - EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context); + EPerson parsed = loginJWTTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context); assertEquals(null, parsed); } @@ -143,12 +143,12 @@ public class JWTTokenHandlerTest { public void testInvalidatedToken() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); // create a new token - String token = sessionJWTTokenHandler + String token = loginJWTTokenHandler .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); // immediately invalidate it - sessionJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); + loginJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); // Check if it is still valid by trying to parse the EPerson from it (should return null) - EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); + EPerson parsed = loginJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); assertEquals(null, parsed); } diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index e3077f4a57..f22e2eaf19 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -57,28 +57,28 @@ plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authen # Server key part that is a part of the key used to sign the authentication tokens. # If this property is not set or empty, DSpace will generate a random key on startup. # IF YOU ARE RUNNING DSPACE IN A CLUSTER, you need to set a value for this property here or as an environment variable -# jwt.session.token.secret = +# jwt.login.token.secret = # This property enables/disables encryption of the payload in a stateless token. Enabling this makes the data encrypted # and unreadable by the receiver, but makes the token larger in size. false by default -jwt.session.encryption.enabled = false +jwt.login.encryption.enabled = false # Encryption key to use when JWT token encryption is enabled (JWE). Note that encrypting tokens might required additional # configuration in the REST clients -# jwt.session.encryption.secret = +# jwt.login.encryption.secret = # This enables compression of the payload of a jwt, enabling this will make the jwt token a little smaller at the cost # of some performance, this setting WILL ONLY BE used when encrypting the jwt. -jwt.session.compression.enabled = true +jwt.login.compression.enabled = true # Expiration time of a token in milliseconds -jwt.session.token.expiration = 1800000 +jwt.login.token.expiration = 1800000 # Restrict tokens to a specific ip-address to prevent theft/session hijacking. This is achieved by making the ip-address # a part of the JWT siging key. If this property is set to false then the ip-address won't be used as part of # the signing key of a jwt token and tokens can be shared over multiple ip-addresses. # For security reasons, this defaults to true -jwt.session.token.include.ip = true +jwt.login.token.include.ip = true #---------------------------------------------------------------# From 22678a7ef54984198e9ee08623ccdfc84ff96887 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 26 Jun 2020 09:41:50 -0500 Subject: [PATCH 141/465] Configure LGTM --- .lgtm.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .lgtm.yml diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 0000000000..132de8a6de --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,9 @@ +# LGTM Settings (https://lgtm.com/) +# For reference, see https://lgtm.com/help/lgtm/lgtm.yml-configuration-file +# or template at https://lgtm.com/static/downloads/lgtm.template.yml + +extraction: + java: + index: + # Specify the Java version required to build the project + java_version: 11 From ff1b40d85998ebe6e2ec671dddcff0c20d228c7b Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 29 Jun 2020 16:18:05 +0200 Subject: [PATCH 142/465] remove unused attributes --- .../app/rest/model/SubmissionFormInputTypeRest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java index cda0f6fbaa..ff5481443b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; public class SubmissionFormInputTypeRest { private String type; private String regex; - private VocabularyRest authority; public String getType() { return type; @@ -39,11 +38,4 @@ public class SubmissionFormInputTypeRest { this.regex = regex; } - public VocabularyRest getAuthority() { - return authority; - } - - public void setAuthority(VocabularyRest authority) { - this.authority = authority; - } } From 2271237bc6729ad6508f7482e7fe6f67c5919d6e Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 29 Jun 2020 23:48:37 +0200 Subject: [PATCH 143/465] Add javadoc for the reload method --- .../app/rest/repository/SubmissionFormRestRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 770438e183..76d680cf27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -86,6 +86,13 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository Date: Tue, 30 Jun 2020 08:30:01 +0200 Subject: [PATCH 144/465] [Task 71627] addressed feedback on the new registration functionaliity --- .../repository/EPersonRestRepository.java | 2 +- .../app/rest/EPersonRestRepositoryIT.java | 221 ++++++++++-------- .../rest/RegistrationRestRepositoryIT.java | 147 ++++++------ 3 files changed, 202 insertions(+), 168 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index b57df5f73d..e4a1ca1389 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -199,7 +199,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository epersonLastName = metadataRest.getMap().get("eperson.lastname"); if (epersonFirstName == null || epersonLastName == null || epersonFirstName.isEmpty() || epersonLastName.isEmpty()) { - throw new DSpaceBadRequestException("The eperson.firstname and eperson.lastname values need to be " + + throw new UnprocessableEntityException("The eperson.firstname and eperson.lastname values need to be " + "filled in"); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index b681d5fe0d..d2b599e477 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -2044,22 +2044,24 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { accountService.sendRegistrationInfo(context, ePerson.getEmail()); String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken(); PasswordHash oldPassword = ePersonService.getPasswordHash(ePerson); - // updates password - getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) - .content(patchBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) - .param("token", newRegisterToken)) - .andExpect(status().isUnauthorized()); + try { + // updates password + getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + .param("token", newRegisterToken)) + .andExpect(status().isUnauthorized()); - PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); - assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); - assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); - assertFalse(registrationDataService.findByEmail(context, newRegisterEmail) == null); - - context.turnOffAuthorisationSystem(); - registrationDataService.delete(context, registrationDataService.findByEmail(context, ePerson.getEmail())); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); + PasswordHash newPasswordHash = ePersonService.getPasswordHash(ePerson); + assertTrue(StringUtils.equalsIgnoreCase(oldPassword.getHashString(),newPasswordHash.getHashString())); + assertFalse(registrationDataService.findByEmail(context, ePerson.getEmail()) == null); + assertFalse(registrationDataService.findByEmail(context, newRegisterEmail) == null); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.delete(context, registrationDataService.findByEmail(context, ePerson.getEmail())); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } } @Test @@ -2118,10 +2120,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, newRegisterToken); context.restoreAuthSystemState(); - } finally { EPersonBuilder.deleteEPerson(idRef.get()); } } @@ -2179,10 +2181,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); assertNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, newRegisterToken); context.restoreAuthSystemState(); - } finally { EPersonBuilder.deleteEPerson(idRef.get()); } @@ -2245,10 +2247,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); assertNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, newRegisterToken); context.restoreAuthSystemState(); - } finally { EPersonBuilder.deleteEPerson(idRef.get()); } @@ -2293,21 +2295,24 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); - EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmailTwo); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); - assertNotNull(registrationDataService.findByToken(context, newRegisterTokenTwo)); + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmailTwo); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + assertNotNull(registrationDataService.findByToken(context, newRegisterTokenTwo)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + registrationDataService.deleteByToken(context, newRegisterTokenTwo); + context.restoreAuthSystemState(); - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - registrationDataService.deleteByToken(context, newRegisterTokenTwo); - context.restoreAuthSystemState(); + } } @Test @@ -2340,19 +2345,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", "randomToken") - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", "randomToken") + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); - EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); } @Test @@ -2386,19 +2394,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); - EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); } @Test @@ -2429,19 +2440,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnprocessableEntity()); - EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); } @Test @@ -2472,19 +2486,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnprocessableEntity()); - EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); } @Test @@ -2516,19 +2533,21 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", newRegisterToken) - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", newRegisterToken) + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); - EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); - - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, newRegisterToken); - context.restoreAuthSystemState(); + EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, newRegisterToken); + context.restoreAuthSystemState(); + } } @@ -2562,19 +2581,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess()); - getClient().perform(post("/api/eperson/epersons") - .param("token", forgotPasswordToken) - .content(mapper.writeValueAsBytes(ePersonRest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + try { + getClient().perform(post("/api/eperson/epersons") + .param("token", forgotPasswordToken) + .content(mapper.writeValueAsBytes(ePersonRest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); - EPerson createdEPerson = ePersonService.findByEmail(context, newEmail); - assertNull(createdEPerson); - assertNotNull(registrationDataService.findByToken(context, forgotPasswordToken)); + EPerson createdEPerson = ePersonService.findByEmail(context, newEmail); + assertNull(createdEPerson); + assertNotNull(registrationDataService.findByToken(context, forgotPasswordToken)); + } finally { + context.turnOffAuthorisationSystem(); + registrationDataService.deleteByToken(context, forgotPasswordToken); + context.restoreAuthSystemState(); + } - context.turnOffAuthorisationSystem(); - registrationDataService.deleteByToken(context, forgotPasswordToken); - context.restoreAuthSystemState(); } @@ -2633,11 +2655,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson createdEPerson = ePersonService.find(context, UUID.fromString(epersonUuid)); assertTrue(ePersonService.checkPassword(context, createdEPerson, "somePassword")); assertNull(registrationDataService.findByToken(context, newRegisterToken)); - + } finally { context.turnOffAuthorisationSystem(); registrationDataService.deleteByToken(context, newRegisterToken); context.restoreAuthSystemState(); - } finally { EPersonBuilder.deleteEPerson(idRef.get()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java index 2709042cbd..1c3ae58374 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java @@ -16,6 +16,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Iterator; import java.util.List; +import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; @@ -43,25 +44,28 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT createTokenForEmail(email); RegistrationData registrationData = registrationDataDAO.findByEmail(context, email); - getClient().perform(get("/api/eperson/registrations/search/findByToken") - .param("token", registrationData.getToken())) - .andExpect(status().isOk()) - .andExpect( - jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, eperson.getID())))); + try { + getClient().perform(get("/api/eperson/registrations/search/findByToken") + .param("token", registrationData.getToken())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, eperson.getID())))); - registrationDataDAO.delete(context, registrationData); + registrationDataDAO.delete(context, registrationData); - email = "newUser@testnewuser.com"; - createTokenForEmail(email); - registrationData = registrationDataDAO.findByEmail(context, email); + email = "newUser@testnewuser.com"; + createTokenForEmail(email); + registrationData = registrationDataDAO.findByEmail(context, email); - getClient().perform(get("/api/eperson/registrations/search/findByToken") - .param("token", registrationData.getToken())) - .andExpect(status().isOk()) - .andExpect( - jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + getClient().perform(get("/api/eperson/registrations/search/findByToken") + .param("token", registrationData.getToken())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + } finally { + registrationDataDAO.delete(context, registrationData); + } - registrationDataDAO.delete(context, registrationData); } @@ -71,13 +75,16 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT createTokenForEmail(email); RegistrationData registrationData = registrationDataDAO.findByEmail(context, email); - getClient().perform(get("/api/eperson/registrations/search/findByToken") - .param("token", registrationData.getToken())) - .andExpect(status().isOk()) - .andExpect( - jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + try { + getClient().perform(get("/api/eperson/registrations/search/findByToken") + .param("token", registrationData.getToken())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + } finally { + registrationDataDAO.delete(context, registrationData); + } - registrationDataDAO.delete(context, registrationData); } @Test @@ -106,41 +113,44 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT ObjectMapper mapper = new ObjectMapper(); RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(eperson.getEmail()); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(1, registrationDataList.size()); - assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); - String newEmail = "newEPersonTest@gmail.com"; - registrationRest.setEmail(newEmail); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertTrue(registrationDataList.size() == 2); - assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) || - StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); - configurationService.setProperty("user.registration", false); + try { + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); - newEmail = "newEPersonTestTwo@gmail.com"; - registrationRest.setEmail(newEmail); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().is(401)); + String newEmail = "newEPersonTest@gmail.com"; + registrationRest.setEmail(newEmail); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertTrue(registrationDataList.size() == 2); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) || + StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); + configurationService.setProperty("user.registration", false); - assertEquals(2, registrationDataList.size()); - assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && - !StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); + newEmail = "newEPersonTestTwo@gmail.com"; + registrationRest.setEmail(newEmail); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().is(HttpServletResponse.SC_UNAUTHORIZED)); - Iterator iterator = registrationDataList.iterator(); - while (iterator.hasNext()) { - RegistrationData registrationData = iterator.next(); - registrationDataDAO.delete(context, registrationData); + assertEquals(2, registrationDataList.size()); + assertTrue(!StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), newEmail) && + !StringUtils.equalsIgnoreCase(registrationDataList.get(1).getEmail(), newEmail)); + } finally { + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } } } @@ -149,22 +159,25 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT configurationService.setProperty("user.registration", false); List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(0, registrationDataList.size()); + try { + assertEquals(0, registrationDataList.size()); - ObjectMapper mapper = new ObjectMapper(); - RegistrationRest registrationRest = new RegistrationRest(); - registrationRest.setEmail(eperson.getEmail()); - getClient().perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); - assertEquals(1, registrationDataList.size()); - assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); - Iterator iterator = registrationDataList.iterator(); - while (iterator.hasNext()) { - RegistrationData registrationData = iterator.next(); - registrationDataDAO.delete(context, registrationData); + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); + } finally { + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } } } From 2a6731297cc8c7c7f1387cf214800e42a9672071 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 30 Jun 2020 10:27:47 +0200 Subject: [PATCH 145/465] Fixed LGTM issue with an error String --- .../org/dspace/app/rest/repository/EPersonRestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index e4a1ca1389..a312fd4f28 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -164,7 +164,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository Date: Tue, 30 Jun 2020 10:39:39 -0500 Subject: [PATCH 146/465] set result to empty iterator --- .../org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 85cfcba2e0..1750938937 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -9,6 +9,7 @@ package org.dspace.content; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -101,7 +102,7 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo throws SQLException { // Add all the collections List collections = community.getCollections(); - Iterator result = null; + Iterator result = Collections.emptyIterator(); for (Collection collection : collections) { Iterator items = itemService.findByCollection(context, collection); result = addItemsToResult(result, items); From d364ac63837985f1b2162ac5e7e7cb6662cf3efd Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Tue, 30 Jun 2020 18:20:06 +0200 Subject: [PATCH 147/465] 71654: Authorization for Downloads of restricted Bitstreams - Short lived tokens can't be used to login, or generate other tokens --- .../rest/security/jwt/JWTTokenHandler.java | 8 ++++++++ .../rest/AuthenticationRestControllerIT.java | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index ab24a7cbae..6edd471e36 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -43,6 +43,7 @@ import org.dspace.services.ConfigurationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -57,6 +58,8 @@ import org.springframework.security.crypto.keygen.KeyGenerators; public abstract class JWTTokenHandler { private static final int MAX_CLOCK_SKEW_SECONDS = 60; + private static final String AUTHORIZATION_TOKEN_PARAMETER = "token"; + private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); @Autowired @@ -165,6 +168,11 @@ public abstract class JWTTokenHandler { public String createTokenForEPerson(Context context, HttpServletRequest request, Date previousLoginDate, List groups) throws JOSEException, SQLException { + // Verify that the user isn't trying to use a short lived token to generate another token + if (StringUtils.isNotBlank(request.getParameter(AUTHORIZATION_TOKEN_PARAMETER))) { + throw new AccessDeniedException("Short lived tokens can't be used to generate other tokens"); + } + // Update the saved session salt for the currently logged in user, returning the user object EPerson ePerson = updateSessionSalt(context, previousLoginDate); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index b04c99b0e8..7361dd9d6a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -855,6 +855,25 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(true))); } + // TODO: fix the exception. For now we want to verify a short lived token can't be used to login + @Test(expected = Exception.class) + public void testLoginWithShortLivedToken() throws Exception { + String shortLivedToken = getShortLivedToken(eperson); + + getClient().perform(post("/api/authn/login?token=" + shortLivedToken)) + .andExpect(status().isInternalServerError()); + // TODO: This internal server error needs to be fixed. This should actually produce a forbidden status + //.andExpect(status().isForbidden()); + } + + @Test + public void testGenerateShortLivedTokenWithShortLivedToken() throws Exception { + String shortLivedToken = getShortLivedToken(eperson); + + getClient().perform(post("/api/authn/shortlivedtokens?token=" + shortLivedToken)) + .andExpect(status().isForbidden()); + } + private String getShortLivedToken(EPerson requestUser) throws Exception { ObjectMapper mapper = new ObjectMapper(); From 6333fdc499e73c23da778a51edac27d00be4ccda Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 30 Jun 2020 21:43:57 +0200 Subject: [PATCH 148/465] Add test to check links in the vocabulary entry details and relative fix --- .../app/rest/model/VocabularyEntryDetailsRest.java | 11 +---------- .../dspace/app/rest/VocabularyEntryDetailsIT.java | 14 +++++++++----- .../matcher/VocabularyEntryDetailsMatcher.java | 6 +++--- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 1418bd8216..42644c8c85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -21,11 +21,10 @@ import org.dspace.app.rest.RestResourceController; @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") }) -public class VocabularyEntryDetailsRest extends RestAddressableModel { +public class VocabularyEntryDetailsRest extends BaseObjectRest { public static final String NAME = "vocabularyEntryDetail"; public static final String PARENT = "parent"; public static final String CHILDREN = "children"; - private String id; private String display; private String value; private Map otherInformation; @@ -36,14 +35,6 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { @JsonIgnore private String vocabularyName; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getDisplay() { return display; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 7af95646e8..a50ccea072 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -40,15 +41,18 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is("srsc:SCB110"))) - .andExpect(jsonPath("$.value", - is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) - .andExpect(jsonPath("$.display", is("Religion/Theology"))) + .andExpect(jsonPath("$", + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB110", "Religion/Theology", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) .andExpect(jsonPath("$.otherInformation.parent", - is("Research Subject Categories::HUMANITIES and RELIGION"))); + is("Research Subject Categories::HUMANITIES and RELIGION"))) + .andExpect(jsonPath("$._links.parent.href", + endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/parent"))) + .andExpect(jsonPath("$._links.children.href", + endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/children"))); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java index 67747cff66..8eb2cba3c4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java @@ -25,12 +25,12 @@ public class VocabularyEntryDetailsMatcher { public static Matcher matchAuthorityEntry(String id, String display, String value) { return allOf( matchProperties(id, display, value), - matchLinks()); + matchLinks(id)); } - public static Matcher matchLinks() { + public static Matcher matchLinks(String id) { return allOf( - hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/"))); + hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/" + id))); } private static Matcher matchProperties(String id, String display, String value) { From 808c4633f159246543b79c723f3617e06c70c0db Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 1 Jul 2020 10:36:43 +0200 Subject: [PATCH 149/465] submit external suorce partial implementation --- .../metadatamapping/ArXivFieldMapping.java | 23 ++ .../transform/GenerateArXivQueryService.java | 53 +++ .../ArXivImportMetadataSourceServiceImpl.java | 310 ++++++++++++++++++ .../AbstractImportMetadataSourceService.java | 2 - .../spring-dspace-addon-import-services.xml | 21 +- .../config/spring/api/arxiv-integration.xml | 136 ++++++++ .../config/spring/api/external-services.xml | 9 +- 7 files changed, 547 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java create mode 100644 dspace/config/spring/api/arxiv-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java new file mode 100644 index 0000000000..c4f6996a27 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java @@ -0,0 +1,23 @@ +/** + * 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.importer.external.arxiv.metadatamapping; + +import java.util.Map; +import javax.annotation.Resource; + +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; + +public class ArXivFieldMapping extends AbstractMetadataFieldMapping { + + @Override + @Resource(name = "arxivMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java new file mode 100644 index 0000000000..7f5e08cb5a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java @@ -0,0 +1,53 @@ +/** + * 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.importer.external.arxiv.metadatamapping.transform; + +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.metadatamapping.transform.GenerateQueryService; + +public class GenerateArXivQueryService implements GenerateQueryService { + + /** + * Create a Query object based on a given item. + * If the item has at least 1 value for dc.identifier.doi, the first one will be used. + * If no DOI is found, the title will be used. + * When no DOI or title is found, an null object is returned instead. + * + * @param item the Item to create a Query from + */ + @Override + public Query generateQueryForItem(Item item) throws MetadataSourceException { + Query query = new Query(); + + // Retrieve an instance of the ItemService to access business calls on an item. + ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + List doi = itemService.getMetadata(item, "dc", "identifier", "doi", Item.ANY); + + if (doi.size() > 0) { + query.addParameter("term", doi.get(0).getValue()); + query.addParameter("field", "ELocationID"); + return query; + } + + List title = itemService.getMetadata(item, "dc", "title", null, Item.ANY); + + if (title.size() > 0) { + query.addParameter("term", title.get(0).getValue()); + query.addParameter("field", "title"); + return query; + } + return null; + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..567cce1b9a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -0,0 +1,310 @@ +/** + * 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.importer.external.arxiv.service; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axiom.om.OMXMLParserWrapper; +import org.apache.axiom.om.xpath.AXIOMXPath; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.jaxen.JaxenException; + +public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService { + private int timeout = 1000; + + /** + * How long to wait for a connection to be established. + * + * @param timeout milliseconds + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public int getNbRecords(String query) throws MetadataSourceException { + List records = retry(new SearchByQueryCallable(query, null, null)); + return records != null ? records.size() : 0; + } + + @Override + public int getNbRecords(Query query) throws MetadataSourceException { + List records = retry(new SearchByQueryCallable(query)); + return records != null ? records.size() : 0; + } + + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(id)); + if (records != null && records.size() > 1) { + throw new MetadataSourceException("More than one result found"); + } + return records == null ? null : records.get(0); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(query)); + if (records != null && records.size() > 1) { + throw new MetadataSourceException("More than one result found"); + } + return records == null ? null : records.get(0); + } + + + @Override + public void init() throws Exception { + + } + + + + + + + @Override + public String getImportSource() { + return "arxiv"; + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new RuntimeException(); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + return null; + } + + private class SearchByQueryCallable implements Callable> { + private Query query; + + + private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { + query = new Query(); + query.addParameter("query", queryString); + query.addParameter("start", start); + query.addParameter("count", maxResult); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + + @Override + public List call() throws Exception { + List results = new ArrayList(); + String queryString = query.getParameterAsClass("query", String.class); + Integer start = query.getParameterAsClass("start", Integer.class); + Integer maxResult = query.getParameterAsClass("count", Integer.class); + + HttpGet method = null; + try { + HttpClient client = new DefaultHttpClient(); + HttpParams params = client.getParams(); + params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); + + try { + URIBuilder uriBuilder = new URIBuilder("http://export.arxiv.org/api/query"); + uriBuilder.addParameter("search_query", queryString); + if (maxResult != null) { + uriBuilder.addParameter("max_results", String.valueOf(maxResult)); + } + if (start != null) { + uriBuilder.addParameter("start", String.valueOf(start)); + } + method = new HttpGet(uriBuilder.build()); + } catch (URISyntaxException ex) { + throw new HttpException(ex.getMessage()); + } + + // Execute the method. + HttpResponse response = client.execute(method); + StatusLine responseStatus = response.getStatusLine(); + int statusCode = responseStatus.getStatusCode(); + + if (statusCode != HttpStatus.SC_OK) { + if (statusCode == HttpStatus.SC_BAD_REQUEST) { + throw new RuntimeException("arXiv query is not valid"); + } else { + throw new RuntimeException("Http call failed: " + + responseStatus); + } + } + + try { + InputStreamReader isReader = new InputStreamReader(response.getEntity().getContent()); + BufferedReader reader = new BufferedReader(isReader); + StringBuilder sb = new StringBuilder(); + String str; + while ((str = reader.readLine()) != null) { + sb.append(str); + } + System.out.println("XML: " + sb.toString()); + List omElements = splitToRecords(sb.toString()); + for (OMElement record : omElements) { + results.add(transformSourceRecords(record)); + } + } catch (Exception e) { + throw new RuntimeException( + "ArXiv identifier is not valid or not exist"); + } + } finally { + if (method != null) { + method.releaseConnection(); + } + } + return results; + } + } + + private class SearchByIdCallable implements Callable> { + private Query query; + + private SearchByIdCallable(Query query) { + this.query = query; + } + + private SearchByIdCallable(String id) { + this.query = new Query(); + query.addParameter("id", id); + } + + @Override + public List call() throws Exception { + List results = new ArrayList(); + String arxivid = query.getParameterAsClass("id", String.class); + HttpGet method = null; + try { + HttpClient client = new DefaultHttpClient(); + HttpParams params = client.getParams(); + params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); + try { + URIBuilder uriBuilder = new URIBuilder("http://export.arxiv.org/api/query"); + if (StringUtils.isNotBlank(arxivid)) { + arxivid = arxivid.trim(); + if (arxivid.startsWith("http://arxiv.org/abs/")) { + arxivid = arxivid.substring("http://arxiv.org/abs/".length()); + } else if (arxivid.toLowerCase().startsWith("arxiv:")) { + arxivid = arxivid.substring("arxiv:".length()); + } + uriBuilder.addParameter("id_list", arxivid); + method = new HttpGet(uriBuilder.build()); + } + } catch (URISyntaxException ex) { + throw new HttpException(ex.getMessage()); + } + + // Execute the method. + HttpResponse response = client.execute(method); + StatusLine responseStatus = response.getStatusLine(); + int statusCode = responseStatus.getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + if (statusCode == HttpStatus.SC_BAD_REQUEST) { + throw new RuntimeException("arXiv query is not valid"); + } else { + throw new RuntimeException("Http call failed: " + + responseStatus); + } + } + try { + InputStreamReader isReader = new InputStreamReader(response.getEntity().getContent()); + BufferedReader reader = new BufferedReader(isReader); + StringBuffer sb = new StringBuffer(); + String str; + while ((str = reader.readLine()) != null) { + sb.append(str); + } + List omElements = splitToRecords(sb.toString()); + for (OMElement record : omElements) { + results.add(transformSourceRecords(record)); + } + } catch (Exception e) { + throw new RuntimeException( + "ArXiv identifier is not valid or not exist"); + } + } finally { + if (method != null) { + method.releaseConnection(); + } + } + return results; + } + } + + private class FindMatchingRecordCallable implements Callable> { + private Query query; + + private FindMatchingRecordCallable(Item item) throws MetadataSourceException { + query = getGenerateQueryForItem().generateQueryForItem(item); + } + + public FindMatchingRecordCallable(Query q) { + query = q; + } + + @Override + public List call() throws Exception { + return null; + } + } + + private static List splitToRecords(String recordsSrc) { + OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(recordsSrc)); + OMElement element = records.getDocumentElement(); + AXIOMXPath xpath = null; + try { + xpath = new AXIOMXPath("ns:entry"); + xpath.addNamespace("ns", "http://www.w3.org/2005/Atom"); + List recordsList = xpath.selectNodes(element); + return recordsList; + } catch (JaxenException e) { + return null; + } + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java index a803958a9d..3bf76438cd 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/AbstractImportMetadataSourceService.java @@ -16,7 +16,6 @@ import org.dspace.importer.external.metadatamapping.contributor.MetadataContribu import org.dspace.importer.external.metadatamapping.transform.GenerateQueryService; import org.dspace.importer.external.service.components.AbstractRemoteMetadataSource; import org.dspace.importer.external.service.components.MetadataSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; /** @@ -49,7 +48,6 @@ public abstract class AbstractImportMetadataSourceService extends Ab * * @param generateQueryForItem the query generator to be used. */ - @Autowired public void setGenerateQueryForItem(GenerateQueryService generateQueryForItem) { this.generateQueryForItem = generateQueryForItem; } diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index bbdf085619..a351280b98 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -19,10 +19,6 @@ - - - + + + + + + + + + + + + + + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines how an org.dspace.content.Item is mapped to a query in scopus. Please note that exactly one of + these must be present. If multiple are present the result is undefined. + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 098b53c2ca..af24e41980 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -31,10 +31,17 @@ - + + + + + + + + From cfc53c98d21b1726c7a921d3ea8f30e1176b2874 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 1 Jul 2020 10:52:49 +0200 Subject: [PATCH 150/465] Live import minor changes + LGTM alert fix --- .../rest/repository/WorkspaceItemRestRepository.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index e66f67fd9a..09a9f8febf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -41,12 +41,12 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Item; -import org.dspace.content.ItemServiceImpl; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CollectionService; +import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -87,7 +87,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository result = null; - List records = null; + List records = new ArrayList<>(); try { for (MultipartFile mpFile : uploadfiles) { File file = Utils.getFile(mpFile, "upload-loader", "filedataloader"); try { - if (records == null) { - records = new ArrayList<>(); - } ImportRecord record = importService.getRecord(file); if (record != null) { records.add(record); @@ -395,7 +392,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository errors = new ArrayList(); wi.setMultipleFiles(uploadfiles.size() > 1); From be89cc815975530e06489af79b0a4b6962f7bfd9 Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Wed, 1 Jul 2020 10:59:18 +0200 Subject: [PATCH 151/465] 71672: Authorization for Downloads of restricted Bitstreams - Rename url parameter "token" to "authentication-token" --- .../app/rest/security/jwt/JWTTokenHandler.java | 2 +- .../jwt/JWTTokenRestAuthenticationServiceImpl.java | 2 +- .../app/rest/AuthenticationRestControllerIT.java | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 6edd471e36..dcfb364fdd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -58,7 +58,7 @@ import org.springframework.security.crypto.keygen.KeyGenerators; public abstract class JWTTokenHandler { private static final int MAX_CLOCK_SKEW_SECONDS = 60; - private static final String AUTHORIZATION_TOKEN_PARAMETER = "token"; + private static final String AUTHORIZATION_TOKEN_PARAMETER = "authentication-token"; private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index bce2724c8d..1b5ee6a0c5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -48,7 +48,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication private static final String AUTHORIZATION_COOKIE = "Authorization-cookie"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORIZATION_TYPE = "Bearer"; - private static final String AUTHORIZATION_TOKEN_PARAMETER = "token"; + private static final String AUTHORIZATION_TOKEN_PARAMETER = "authentication-token"; @Autowired private LoginJWTTokenHandler loginJWTTokenHandler; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 7361dd9d6a..6976bbf350 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -804,7 +804,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio Bitstream bitstream = createPrivateBitstream(); String shortLivedToken = getShortLivedToken(eperson); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + shortLivedToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + shortLivedToken)) .andExpect(status().isOk()); } @@ -821,7 +821,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String shortLivedToken = getShortLivedToken(testEPerson); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + shortLivedToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + shortLivedToken)) .andExpect(status().isForbidden()); } @@ -830,7 +830,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio Bitstream bitstream = createPrivateBitstream(); String loginToken = getAuthToken(eperson.getEmail(), password); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + loginToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + loginToken)) .andExpect(status().isForbidden()); } @@ -840,7 +840,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio configurationService.setProperty("jwt.shortLived.token.expiration", "1"); String shortLivedToken = getShortLivedToken(eperson); Thread.sleep(1); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?token=" + shortLivedToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + shortLivedToken)) .andExpect(status().isForbidden()); } @@ -860,7 +860,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public void testLoginWithShortLivedToken() throws Exception { String shortLivedToken = getShortLivedToken(eperson); - getClient().perform(post("/api/authn/login?token=" + shortLivedToken)) + getClient().perform(post("/api/authn/login?authentication-token=" + shortLivedToken)) .andExpect(status().isInternalServerError()); // TODO: This internal server error needs to be fixed. This should actually produce a forbidden status //.andExpect(status().isForbidden()); @@ -870,7 +870,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public void testGenerateShortLivedTokenWithShortLivedToken() throws Exception { String shortLivedToken = getShortLivedToken(eperson); - getClient().perform(post("/api/authn/shortlivedtokens?token=" + shortLivedToken)) + getClient().perform(post("/api/authn/shortlivedtokens?authentication-token=" + shortLivedToken)) .andExpect(status().isForbidden()); } From 9044daf50eb1b4044b6b4d80ce12b32e0a399712 Mon Sep 17 00:00:00 2001 From: Peter Nijs Date: Wed, 1 Jul 2020 11:50:57 +0200 Subject: [PATCH 152/465] 71672: Authorization for Downloads of restricted Bitstreams - Fix checkstyle --- .../app/rest/AuthenticationRestControllerIT.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 6976bbf350..513ca5ebfd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -804,7 +804,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio Bitstream bitstream = createPrivateBitstream(); String shortLivedToken = getShortLivedToken(eperson); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + shortLivedToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + + "/content?authentication-token=" + shortLivedToken)) .andExpect(status().isOk()); } @@ -821,7 +822,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String shortLivedToken = getShortLivedToken(testEPerson); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + shortLivedToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + + "/content?authentication-token=" + shortLivedToken)) .andExpect(status().isForbidden()); } @@ -830,7 +832,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio Bitstream bitstream = createPrivateBitstream(); String loginToken = getAuthToken(eperson.getEmail(), password); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + loginToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + + "/content?authentication-token=" + loginToken)) .andExpect(status().isForbidden()); } @@ -840,7 +843,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio configurationService.setProperty("jwt.shortLived.token.expiration", "1"); String shortLivedToken = getShortLivedToken(eperson); Thread.sleep(1); - getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content?authentication-token=" + shortLivedToken)) + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + + "/content?authentication-token=" + shortLivedToken)) .andExpect(status().isForbidden()); } From 8c35f6d8a36d199b0b16f6843f2071ec6497e1c6 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 1 Jul 2020 12:32:32 +0200 Subject: [PATCH 153/465] DS-3679 improved test to show the bug with auth session --- .../services/email/EmailServiceImplTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dspace-services/src/test/java/org/dspace/services/email/EmailServiceImplTest.java b/dspace-services/src/test/java/org/dspace/services/email/EmailServiceImplTest.java index 0be0bfe81e..958e4f8106 100644 --- a/dspace-services/src/test/java/org/dspace/services/email/EmailServiceImplTest.java +++ b/dspace-services/src/test/java/org/dspace/services/email/EmailServiceImplTest.java @@ -10,6 +10,7 @@ package org.dspace.services.email; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; @@ -65,11 +66,42 @@ public class EmailServiceImplTest // Try to get a Session session = instance.getSession(); assertNotNull(" getSession returned null", session); + assertNull(" getSession returned authenticated session", + session.getProperties().getProperty("mail.smtp.auth")); } private static final String CFG_USERNAME = "mail.server.username"; private static final String CFG_PASSWORD = "mail.server.password"; + /** + * Test of testGetSession method, of class EmailServiceImpl when an smtp + * username is provided. + */ + @Test + public void testGetAuthenticatedInstance() { + System.out.println("getSession"); + ConfigurationService cfg = getKernel().getConfigurationService(); + + // Save existing values. + String oldUsername = cfg.getProperty(CFG_USERNAME); + String oldPassword = cfg.getProperty(CFG_PASSWORD); + + // Set known values. + cfg.setProperty(CFG_USERNAME, USERNAME); + cfg.setProperty(CFG_PASSWORD, PASSWORD); + + EmailServiceImpl instance = (EmailServiceImpl) getService(EmailServiceImpl.class); + instance.reset(); + assertNotNull(" getSession returned null", instance); + assertEquals(" authenticated session ", "true", + instance.getSession().getProperties().getProperty("mail.smtp.auth")); + + // Restore old values, if any. + cfg.setProperty(CFG_USERNAME, oldUsername); + cfg.setProperty(CFG_PASSWORD, oldPassword); + instance.reset(); + } + /** * Test of getPasswordAuthentication method, of class EmailServiceImpl. */ From fe051662528f0f186a5f88c21a1ef01bca4bbb2d Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 1 Jul 2020 12:34:09 +0200 Subject: [PATCH 154/465] DS-3679 fix check for blank username --- .../org/dspace/services/email/EmailServiceImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dspace-services/src/main/java/org/dspace/services/email/EmailServiceImpl.java b/dspace-services/src/main/java/org/dspace/services/email/EmailServiceImpl.java index f20458f51a..d4f7851425 100644 --- a/dspace-services/src/main/java/org/dspace/services/email/EmailServiceImpl.java +++ b/dspace-services/src/main/java/org/dspace/services/email/EmailServiceImpl.java @@ -16,6 +16,7 @@ import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.naming.NoInitialContextException; +import org.apache.commons.lang3.StringUtils; import org.dspace.kernel.mixins.InitializedService; import org.dspace.services.ConfigurationService; import org.dspace.services.EmailService; @@ -106,7 +107,7 @@ public class EmailServiceImpl props.put(key, value); } } - if (null == cfg.getProperty("mail.server.username")) { + if (StringUtils.isBlank(cfg.getProperty("mail.server.username"))) { session = Session.getInstance(props); } else { props.put("mail.smtp.auth", "true"); @@ -125,4 +126,12 @@ public class EmailServiceImpl cfg.getProperty("mail.server.username"), cfg.getProperty("mail.server.password")); } + + /** + * Force a new initialization of the session, useful for testing purpose + */ + public void reset() { + session = null; + init(); + } } From a2de3c682e5b257da319bdfb67ac31769d5b840c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 23 Jun 2020 16:59:52 +0200 Subject: [PATCH 155/465] 71513: curation script to DSpaceRunnable --- .../java/org/dspace/curate/CurationCli.java | 445 ++++++++++-------- .../dspace/curate/CurationClientOptions.java | 69 +++ .../curate/CurationScriptConfiguration.java | 56 +++ .../config/spring/api/scripts.xml | 5 + dspace/config/launcher.xml | 7 - dspace/config/modules/curate.cfg | 3 + dspace/config/spring/api/scripts.xml | 5 + 7 files changed, 376 insertions(+), 214 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java create mode 100644 dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index 3832ddf3ec..0ffb7756fb 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -8,21 +8,21 @@ package org.dspace.curate; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.FileReader; +import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; +import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -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.PosixParser; +import org.apache.commons.cli.ParseException; import org.apache.commons.io.output.NullOutputStream; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; @@ -30,183 +30,93 @@ import org.dspace.curate.factory.CurateServiceFactory; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; /** * CurationCli provides command-line access to Curation tools and processes. * * @author richardrodgers */ -public class CurationCli { +public class CurationCli extends DSpaceRunnable { + + private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + private Context context; + private CurationClientOptions curationClientOptions; + + private String scope; + private String reporter; + private Map parameters; + private boolean verbose; + + @Override + public void internalRun() throws Exception { + if (curationClientOptions == CurationClientOptions.HELP) { + printHelp(); + return; + } + + if (!this.initLineOptionsAndCheckIfValid()) { + return; + } + + Curator curator = initCurator(); + + // load curation tasks + if (curationClientOptions == CurationClientOptions.TASK) { + long start = System.currentTimeMillis(); + if (!handleCurationTaskAndReturnSuccess(curator)) { + return; + } + this.endScript(start); + } + + // process task queue + if (curationClientOptions == CurationClientOptions.QUEUE) { + // process the task queue + TaskQueue queue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService() + .getSinglePlugin(TaskQueue.class); + if (queue == null) { + super.handler.logError("No implementation configured for queue"); + throw new UnsupportedOperationException("No queue service available"); + } + long timeRun = this.runQueue(queue, curator); + this.endScript(timeRun); + } + } /** - * Default constructor + * Does the curation task (-t) or the task in the given file (-T). + * Returns false if required option -i is missing. + * + * @return False if there are missing/invalid command line options (-i); otherwise true */ - private CurationCli() { } - - public static void main(String[] args) throws Exception { - // create an options object and populate it - CommandLineParser parser = new PosixParser(); - - Options options = new Options(); - - options.addOption("t", "task", true, - "curation task name"); - options.addOption("T", "taskfile", true, - "file containing curation task names"); - options.addOption("i", "id", true, - "Id (handle) of object to perform task on, or 'all' to perform on whole repository"); - options.addOption("p", "parameter", true, - "a task parameter 'NAME=VALUE'"); - options.addOption("q", "queue", true, - "name of task queue to process"); - options.addOption("e", "eperson", true, - "email address of curating eperson"); - options.addOption("r", "reporter", true, - "relative or absolute path to the desired report file. " - + "Use '-' to report to console. " - + "If absent, no reporting"); - options.addOption("s", "scope", true, - "transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' " + - "applies"); - options.addOption("v", "verbose", false, - "report activity to stdout"); - options.addOption("h", "help", false, "help"); - - CommandLine line = parser.parse(options, args); - - String taskName = null; - String taskFileName = null; - String idName = null; - String taskQueueName = null; - String ePersonName = null; - String reporterName = null; - String scope = null; - boolean verbose = false; - final Map parameters = new HashMap<>(); - - if (line.hasOption('h')) { - HelpFormatter help = new HelpFormatter(); - help.printHelp("CurationCli\n", options); - System.out - .println("\nwhole repo: CurationCli -t estimate -i all"); - System.out - .println("single item: CurationCli -t generate -i itemId"); - System.out - .println("task queue: CurationCli -q monthly"); - System.exit(0); + private boolean handleCurationTaskAndReturnSuccess(Curator curator) throws IOException, SQLException { + if (!commandLine.hasOption('i')) { + super.handler.logWarning("Id must be specified: a handle, 'all', or a task queue (-h for help)"); + return false; } - - if (line.hasOption('t')) { // task - taskName = line.getOptionValue('t'); - } - - if (line.hasOption('T')) { // task file - taskFileName = line.getOptionValue('T'); - } - - if (line.hasOption('i')) { // id - idName = line.getOptionValue('i'); - } - - if (line.hasOption('q')) { // task queue - taskQueueName = line.getOptionValue('q'); - } - - if (line.hasOption('e')) { // eperson - ePersonName = line.getOptionValue('e'); - } - - if (line.hasOption('p')) { // parameter - for (String parameter : line.getOptionValues('p')) { - String[] parts = parameter.split("=", 2); - String name = parts[0].trim(); - String value; - if (parts.length > 1) { - value = parts[1].trim(); - } else { - value = "true"; - } - parameters.put(name, value); - } - } - if (line.hasOption('r')) { // report file - reporterName = line.getOptionValue('r'); - } - - - if (line.hasOption('s')) { // transaction scope - scope = line.getOptionValue('s'); - } - - if (line.hasOption('v')) { // verbose - verbose = true; - } - - // now validate the args - if (idName == null && taskQueueName == null) { - System.out.println("Id must be specified: a handle, 'all', or a task queue (-h for help)"); - System.exit(1); - } - - if (taskName == null && taskFileName == null && taskQueueName == null) { - System.out.println("A curation task or queue must be specified (-h for help)"); - System.exit(1); - } - - if (scope != null && Curator.TxScope.valueOf(scope.toUpperCase()) == null) { - System.out.println("Bad transaction scope '" + scope + "': only 'object', 'curation' or 'open' recognized"); - System.exit(1); - } - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Context c = new Context(Context.Mode.BATCH_EDIT); - if (ePersonName != null) { - EPerson ePerson = ePersonService.findByEmail(c, ePersonName); - if (ePerson == null) { - System.out.println("EPerson not found: " + ePersonName); - System.exit(1); - } - c.setCurrentUser(ePerson); - } else { - c.turnOffAuthorisationSystem(); - } - - Curator curator = new Curator(); - OutputStream reporter; - if (null == reporterName) { - reporter = new NullOutputStream(); - } else if ("-".equals(reporterName)) { - reporter = System.out; - } else { - reporter = new PrintStream(reporterName); - } - Writer reportWriter = new OutputStreamWriter(reporter); - curator.setReporter(reportWriter); - - if (scope != null) { - Curator.TxScope txScope = Curator.TxScope.valueOf(scope.toUpperCase()); - curator.setTransactionScope(txScope); - } - curator.addParameters(parameters); - // we are operating in batch mode, if anyone cares. - curator.setInvoked(Curator.Invoked.BATCH); - // load curation tasks - if (taskName != null) { + String idName = this.commandLine.getOptionValue('i'); + String taskName; + if (commandLine.hasOption('t')) { + taskName = commandLine.getOptionValue('t'); if (verbose) { - System.out.println("Adding task: " + taskName); + handler.logInfo("Adding task: " + taskName); } curator.addTask(taskName); if (verbose && !curator.hasTask(taskName)) { - System.out.println("Task: " + taskName + " not resolved"); + handler.logInfo("Task: " + taskName + " not resolved"); } - } else if (taskQueueName == null) { + } else if (commandLine.hasOption('T')) { // load taskFile + String taskFileName = commandLine.getOptionValue('T'); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(taskFileName)); while ((taskName = reader.readLine()) != null) { if (verbose) { - System.out.println("Adding task: " + taskName); + super.handler.logInfo("Adding task: " + taskName); } curator.addTask(taskName); } @@ -217,59 +127,180 @@ public class CurationCli { } } // run tasks against object - long start = System.currentTimeMillis(); if (verbose) { - System.out.println("Starting curation"); - } - if (idName != null) { - if (verbose) { - System.out.println("Curating id: " + idName); - } + super.handler.logInfo("Starting curation"); + super.handler.logInfo("Curating id: " + idName); if ("all".equals(idName)) { // run on whole Site - curator.curate(c, ContentServiceFactory.getInstance().getSiteService().findSite(c).getHandle()); + curator.curate(context, + ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle()); } else { - curator.curate(c, idName); + curator.curate(context, idName); } - } else { - // process the task queue - TaskQueue queue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService() - .getSinglePlugin(TaskQueue.class); - if (queue == null) { - System.out.println("No implementation configured for queue"); - throw new UnsupportedOperationException("No queue service available"); - } - // use current time as our reader 'ticket' - long ticket = System.currentTimeMillis(); - Iterator entryIter = queue.dequeue(taskQueueName, ticket).iterator(); - while (entryIter.hasNext()) { - TaskQueueEntry entry = entryIter.next(); - if (verbose) { - System.out.println("Curating id: " + entry.getObjectId()); - } - curator.clear(); - // does entry relate to a DSO or workflow object? - if (entry.getObjectId().indexOf("/") > 0) { - for (String task : entry.getTaskNames()) { - curator.addTask(task); - } - curator.curate(c, entry.getObjectId()); - } else { - // make eperson who queued task the effective user - EPerson agent = ePersonService.findByEmail(c, entry.getEpersonId()); - if (agent != null) { - c.setCurrentUser(agent); - } - CurateServiceFactory.getInstance().getWorkflowCuratorService() - .curate(curator, c, entry.getObjectId()); - } - } - queue.release(taskQueueName, ticket, true); } - c.complete(); + return true; + } + + /** + * Runs task queue (-q set) + * + * @param queue The task cueue + * @param curator The curator + * @return Time when queue started + */ + private long runQueue(TaskQueue queue, Curator curator) throws SQLException, AuthorizeException, IOException { + String taskQueueName = this.commandLine.getOptionValue('q'); + // use current time as our reader 'ticket' + long ticket = System.currentTimeMillis(); + Iterator entryIter = queue.dequeue(taskQueueName, ticket).iterator(); + while (entryIter.hasNext()) { + TaskQueueEntry entry = entryIter.next(); + if (verbose) { + super.handler.logInfo("Curating id: " + entry.getObjectId()); + } + curator.clear(); + // does entry relate to a DSO or workflow object? + if (entry.getObjectId().indexOf('/') > 0) { + for (String task : entry.getTaskNames()) { + curator.addTask(task); + } + curator.curate(context, entry.getObjectId()); + } else { + // make eperson who queued task the effective user + EPerson agent = ePersonService.findByEmail(context, entry.getEpersonId()); + if (agent != null) { + context.setCurrentUser(agent); + } + CurateServiceFactory.getInstance().getWorkflowCuratorService() + .curate(curator, context, entry.getObjectId()); + } + } + queue.release(taskQueueName, ticket, true); + return ticket; + } + + /** + * End of curation script; logs script time if -v verbose is set + * + * @param timeRun Time script was started + * @throws SQLException If DSpace contextx can't complete + */ + private void endScript(long timeRun) throws SQLException { + context.complete(); if (verbose) { - long elapsed = System.currentTimeMillis() - start; - System.out.println("Ending curation. Elapsed time: " + elapsed); + long elapsed = System.currentTimeMillis() - timeRun; + this.handler.logInfo("Ending curation. Elapsed time: " + elapsed); } } + + /** + * Fills in some optional command line options; to be used by curator. And check if no required options are + * missing; or if they have an invalid value. If they are missing/invalid it returns false; otherwise true + * + * @return False if there are missing required options or invalid values for options. + */ + private boolean initLineOptionsAndCheckIfValid() { + // report file + if (this.commandLine.hasOption('r')) { + this.reporter = this.commandLine.getOptionValue('r'); + } + + // parameters + this.parameters = new HashMap<>(); + if (this.commandLine.hasOption('p')) { + for (String parameter : this.commandLine.getOptionValues('p')) { + String[] parts = parameter.split("=", 2); + String name = parts[0].trim(); + String value; + if (parts.length > 1) { + value = parts[1].trim(); + } else { + value = "true"; + } + this.parameters.put(name, value); + } + } + + // verbose + verbose = false; + if (commandLine.hasOption('v')) { + verbose = true; + } + + // scope + if (this.commandLine.getOptionValue('s') != null) { + this.scope = this.commandLine.getOptionValue('s'); + if (this.scope != null && Curator.TxScope.valueOf(this.scope.toUpperCase()) == null) { + this.handler.logError("Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " + + "'open' recognized"); + return false; + } + } + + return true; + } + + /** + * Initialize the curator with command line variables + * + * @return Initialised curator + * @throws FileNotFoundException If file of command line variable -r reporter is not found + */ + private Curator initCurator() throws FileNotFoundException { + Curator curator = new Curator(); + OutputStream reporterStream; + if (null == this.reporter) { + reporterStream = new NullOutputStream(); + } else if ("-".equals(this.reporter)) { + reporterStream = System.out; + } else { + reporterStream = new PrintStream(this.reporter); + } + Writer reportWriter = new OutputStreamWriter(reporterStream); + curator.setReporter(reportWriter); + + if (this.scope != null) { + Curator.TxScope txScope = Curator.TxScope.valueOf(this.scope.toUpperCase()); + curator.setTransactionScope(txScope); + } + + curator.addParameters(parameters); + // we are operating in batch mode, if anyone cares. + curator.setInvoked(Curator.Invoked.BATCH); + return curator; + } + + @Override + public void printHelp() { + super.printHelp(); + super.handler.logInfo("\nwhole repo: CurationCli -t estimate -i all"); + super.handler.logInfo("single item: CurationCli -t generate -i itemId"); + super.handler.logInfo("task queue: CurationCli -q monthly"); + } + + @Override + public CurationScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("curation", CurationScriptConfiguration.class); + } + + @Override + public void setup() throws ParseException { + try { + this.context = new Context(Context.Mode.BATCH_EDIT); + if (this.commandLine.hasOption('e')) { + String ePersonEmail = this.commandLine.getOptionValue('e'); + EPerson ePerson = ePersonService.findByEmail(this.context, ePersonEmail); + if (ePerson == null) { + super.handler.logError("EPerson not found: " + ePersonEmail); + throw new ParseException("EPerson not found: " + ePersonEmail); + } + this.context.setCurrentUser(ePerson); + } else { + this.context.turnOffAuthorisationSystem(); + } + } catch (Exception e) { + throw new ParseException("Unable to create a new DSpace Context: " + e.getMessage()); + } + this.curationClientOptions = CurationClientOptions.getClientOption(commandLine); + } } diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java new file mode 100644 index 0000000000..a10dbba021 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java @@ -0,0 +1,69 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.curate; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * This Enum holds all the possible options and combinations for the Curation script + * + * @author Maria Verdonck (Atmire) on 23/06/2020 + */ +public enum CurationClientOptions { + TASK, + QUEUE, + HELP; + + /** + * This method resolves the CommandLine parameters to figure out which action the curation script should perform + * + * @param commandLine The relevant CommandLine for the curation script + * @return The curation option to be ran, parsed from the CommandLine + */ + protected static CurationClientOptions getClientOption(CommandLine commandLine) { + if (commandLine.hasOption("h")) { + return CurationClientOptions.HELP; + } else if (commandLine.hasOption("t") || commandLine.hasOption("T")) { + return CurationClientOptions.TASK; + } else if (commandLine.hasOption("q")) { + return CurationClientOptions.QUEUE; + } + return CurationClientOptions.HELP; + } + + protected static Options constructOptions() { + Options options = new Options(); + + options.addOption("t", "task", true, "curation task name; options: " + Arrays.toString(getTaskOptions())); + options.addOption("T", "taskfile", true, "file containing curation task names"); + options.addOption("i", "id", true, + "Id (handle) of object to perform task on, or 'all' to perform on whole repository"); + options.addOption("p", "parameter", true, "a task parameter 'NAME=VALUE'"); + options.addOption("q", "queue", true, "name of task queue to process"); + options.addOption("e", "eperson", true, "email address of curating eperson"); + options.addOption("r", "reporter", true, + "relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " + + "reporting"); + options.addOption("s", "scope", true, + "transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' applies"); + options.addOption("v", "verbose", false, "report activity to stdout"); + options.addOption("h", "help", false, "help"); + + return options; + } + + private static String[] getTaskOptions() { + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + return configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask"); + } +} diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java new file mode 100644 index 0000000000..541ed3f8e9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java @@ -0,0 +1,56 @@ +/** + * 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.curate; + +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The {@link ScriptConfiguration} for the {@link CurationCli} script + * + * @author Maria Verdonck (Atmire) on 23/06/2020 + */ +public class CurationScriptConfiguration extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return this.dspaceRunnableClass; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + super.options = CurationClientOptions.constructOptions(); + } + return 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 30fb69a3c4..4ae3d63a02 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 @@ -19,6 +19,11 @@ + + + + + diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index f06225e5cc..4dd49ac146 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -54,13 +54,6 @@ org.dspace.administer.CreateAdministrator - - curate - Perform curation tasks on DSpace objects - - org.dspace.curate.CurationCli - - database Perform database tasks like test database connection, migrate/repair database, remove database diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index cf1a25410c..c6b7914c8f 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -52,3 +52,6 @@ curate.ui.statusmessages = \ 2 = Skip, \ other = Invalid Status + +rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 0713baad19..271fef4d6f 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -18,4 +18,9 @@ + + + + + From 107a19e909b96621ad25af652b73149b6a5e2310 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 24 Jun 2020 13:10:33 +0200 Subject: [PATCH 156/465] 71513: small fix and script call name change --- .../java/org/dspace/curate/CurationCli.java | 21 ++++++++++--------- .../config/spring/api/scripts.xml | 2 +- dspace/config/spring/api/scripts.xml | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index 0ffb7756fb..8c9b118a12 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -94,7 +94,8 @@ public class CurationCli extends DSpaceRunnable { */ private boolean handleCurationTaskAndReturnSuccess(Curator curator) throws IOException, SQLException { if (!commandLine.hasOption('i')) { - super.handler.logWarning("Id must be specified: a handle, 'all', or a task queue (-h for help)"); + super.handler.logWarning("Id must be specified: a handle, 'all', or no -i and a -T task queue (-h for " + + "help)"); return false; } String idName = this.commandLine.getOptionValue('i'); @@ -130,13 +131,13 @@ public class CurationCli extends DSpaceRunnable { if (verbose) { super.handler.logInfo("Starting curation"); super.handler.logInfo("Curating id: " + idName); - if ("all".equals(idName)) { - // run on whole Site - curator.curate(context, - ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle()); - } else { - curator.curate(context, idName); - } + } + if ("all".equals(idName)) { + // run on whole Site + curator.curate(context, + ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle()); + } else { + curator.curate(context, idName); } return true; } @@ -280,7 +281,7 @@ public class CurationCli extends DSpaceRunnable { @Override public CurationScriptConfiguration getScriptConfiguration() { - return new DSpace().getServiceManager().getServiceByName("curation", CurationScriptConfiguration.class); + return new DSpace().getServiceManager().getServiceByName("curate", CurationScriptConfiguration.class); } @Override @@ -296,7 +297,7 @@ public class CurationCli extends DSpaceRunnable { } this.context.setCurrentUser(ePerson); } else { - this.context.turnOffAuthorisationSystem(); + throw new IllegalArgumentException("Needs an -e to set eperson (admin)"); } } catch (Exception e) { throw new ParseException("Unable to create a new DSpace Context: " + e.getMessage()); 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 4ae3d63a02..b717240783 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 @@ -19,7 +19,7 @@ - + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 271fef4d6f..31d457f09d 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -19,7 +19,7 @@ - + From 351363b79eca7b2b5e819e2efeea1c3f124298dc Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 24 Jun 2020 13:35:24 +0200 Subject: [PATCH 157/465] 71513: spelling --- dspace-api/src/main/java/org/dspace/curate/CurationCli.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index 8c9b118a12..66eb532042 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -145,7 +145,7 @@ public class CurationCli extends DSpaceRunnable { /** * Runs task queue (-q set) * - * @param queue The task cueue + * @param queue The task queue * @param curator The curator * @return Time when queue started */ From 79c317154ef435613efbd70b24c0a277ce3a35ad Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 24 Jun 2020 17:38:07 +0200 Subject: [PATCH 158/465] 71513: ITs for curate script --- .../java/org/dspace/curate/CurationCli.java | 211 +++++---- .../dspace/curate/CurationClientOptions.java | 4 +- .../data/dspaceFolder/assetstore/curate.txt | 2 + .../org/dspace/curate/CurationScriptIT.java | 407 ++++++++++++++++++ .../src/test/resources/test-config.properties | 3 + 5 files changed, 537 insertions(+), 90 deletions(-) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/curate.txt create mode 100644 dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index 66eb532042..2f7fb1c532 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -8,6 +8,7 @@ package org.dspace.curate; import java.io.BufferedReader; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -16,11 +17,11 @@ import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; import java.sql.SQLException; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import org.apache.commons.cli.ParseException; import org.apache.commons.io.output.NullOutputStream; import org.dspace.authorize.AuthorizeException; import org.dspace.content.factory.ContentServiceFactory; @@ -45,6 +46,10 @@ public class CurationCli extends DSpaceRunnable { private Context context; private CurationClientOptions curationClientOptions; + private String task; + private String taskFile; + private String id; + private String queue; private String scope; private String reporter; private Map parameters; @@ -57,18 +62,12 @@ public class CurationCli extends DSpaceRunnable { return; } - if (!this.initLineOptionsAndCheckIfValid()) { - return; - } - Curator curator = initCurator(); // load curation tasks if (curationClientOptions == CurationClientOptions.TASK) { long start = System.currentTimeMillis(); - if (!handleCurationTaskAndReturnSuccess(curator)) { - return; - } + handleCurationTask(curator); this.endScript(start); } @@ -88,33 +87,25 @@ public class CurationCli extends DSpaceRunnable { /** * Does the curation task (-t) or the task in the given file (-T). - * Returns false if required option -i is missing. - * - * @return False if there are missing/invalid command line options (-i); otherwise true + * Checks: + * - if required option -i is missing. + * - if option -t has a valid task option */ - private boolean handleCurationTaskAndReturnSuccess(Curator curator) throws IOException, SQLException { - if (!commandLine.hasOption('i')) { - super.handler.logWarning("Id must be specified: a handle, 'all', or no -i and a -T task queue (-h for " + - "help)"); - return false; - } - String idName = this.commandLine.getOptionValue('i'); + private void handleCurationTask(Curator curator) throws IOException, SQLException { String taskName; if (commandLine.hasOption('t')) { - taskName = commandLine.getOptionValue('t'); if (verbose) { - handler.logInfo("Adding task: " + taskName); + handler.logInfo("Adding task: " + this.task); } - curator.addTask(taskName); - if (verbose && !curator.hasTask(taskName)) { - handler.logInfo("Task: " + taskName + " not resolved"); + curator.addTask(this.task); + if (verbose && !curator.hasTask(this.task)) { + handler.logInfo("Task: " + this.task + " not resolved"); } } else if (commandLine.hasOption('T')) { // load taskFile - String taskFileName = commandLine.getOptionValue('T'); BufferedReader reader = null; try { - reader = new BufferedReader(new FileReader(taskFileName)); + reader = new BufferedReader(new FileReader(this.taskFile)); while ((taskName = reader.readLine()) != null) { if (verbose) { super.handler.logInfo("Adding task: " + taskName); @@ -130,16 +121,15 @@ public class CurationCli extends DSpaceRunnable { // run tasks against object if (verbose) { super.handler.logInfo("Starting curation"); - super.handler.logInfo("Curating id: " + idName); + super.handler.logInfo("Curating id: " + this.id); } - if ("all".equals(idName)) { + if ("all".equals(this.id)) { // run on whole Site curator.curate(context, ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle()); } else { - curator.curate(context, idName); + curator.curate(context, this.id); } - return true; } /** @@ -150,10 +140,9 @@ public class CurationCli extends DSpaceRunnable { * @return Time when queue started */ private long runQueue(TaskQueue queue, Curator curator) throws SQLException, AuthorizeException, IOException { - String taskQueueName = this.commandLine.getOptionValue('q'); // use current time as our reader 'ticket' long ticket = System.currentTimeMillis(); - Iterator entryIter = queue.dequeue(taskQueueName, ticket).iterator(); + Iterator entryIter = queue.dequeue(this.queue, ticket).iterator(); while (entryIter.hasNext()) { TaskQueueEntry entry = entryIter.next(); if (verbose) { @@ -176,7 +165,7 @@ public class CurationCli extends DSpaceRunnable { .curate(curator, context, entry.getObjectId()); } } - queue.release(taskQueueName, ticket, true); + queue.release(this.queue, ticket, true); return ticket; } @@ -194,53 +183,6 @@ public class CurationCli extends DSpaceRunnable { } } - /** - * Fills in some optional command line options; to be used by curator. And check if no required options are - * missing; or if they have an invalid value. If they are missing/invalid it returns false; otherwise true - * - * @return False if there are missing required options or invalid values for options. - */ - private boolean initLineOptionsAndCheckIfValid() { - // report file - if (this.commandLine.hasOption('r')) { - this.reporter = this.commandLine.getOptionValue('r'); - } - - // parameters - this.parameters = new HashMap<>(); - if (this.commandLine.hasOption('p')) { - for (String parameter : this.commandLine.getOptionValues('p')) { - String[] parts = parameter.split("=", 2); - String name = parts[0].trim(); - String value; - if (parts.length > 1) { - value = parts[1].trim(); - } else { - value = "true"; - } - this.parameters.put(name, value); - } - } - - // verbose - verbose = false; - if (commandLine.hasOption('v')) { - verbose = true; - } - - // scope - if (this.commandLine.getOptionValue('s') != null) { - this.scope = this.commandLine.getOptionValue('s'); - if (this.scope != null && Curator.TxScope.valueOf(this.scope.toUpperCase()) == null) { - this.handler.logError("Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " + - "'open' recognized"); - return false; - } - } - - return true; - } - /** * Initialize the curator with command line variables * @@ -285,23 +227,116 @@ public class CurationCli extends DSpaceRunnable { } @Override - public void setup() throws ParseException { - try { + public void setup() { + if (this.commandLine.hasOption('e')) { + String ePersonEmail = this.commandLine.getOptionValue('e'); this.context = new Context(Context.Mode.BATCH_EDIT); - if (this.commandLine.hasOption('e')) { - String ePersonEmail = this.commandLine.getOptionValue('e'); + try { EPerson ePerson = ePersonService.findByEmail(this.context, ePersonEmail); if (ePerson == null) { super.handler.logError("EPerson not found: " + ePersonEmail); - throw new ParseException("EPerson not found: " + ePersonEmail); + throw new IllegalArgumentException("Unable to find a user with email: " + ePersonEmail); } this.context.setCurrentUser(ePerson); - } else { - throw new IllegalArgumentException("Needs an -e to set eperson (admin)"); + } catch (SQLException e) { + throw new IllegalArgumentException("SQLException trying to find user with email: " + ePersonEmail); } - } catch (Exception e) { - throw new ParseException("Unable to create a new DSpace Context: " + e.getMessage()); + } else { + throw new IllegalArgumentException("Needs an -e to set eperson (admin)"); } this.curationClientOptions = CurationClientOptions.getClientOption(commandLine); + + if (this.curationClientOptions != null) { + this.initGeneralLineOptionsAndCheckIfValid(); + if (curationClientOptions == CurationClientOptions.TASK) { + this.initTaskLineOptionsAndCheckIfValid(); + } else if (curationClientOptions == CurationClientOptions.QUEUE) { + this.queue = this.commandLine.getOptionValue('q'); + } + } else { + throw new IllegalArgumentException("[--help || --task|--taskfile <> -identifier <> || -queue <> ] must be" + + " specified"); + } + } + + /** + * Fills in some optional command line options. + * Checks if there are missing required options or invalid values for options. + */ + private void initGeneralLineOptionsAndCheckIfValid() { + // report file + if (this.commandLine.hasOption('r')) { + this.reporter = this.commandLine.getOptionValue('r'); + } + + // parameters + this.parameters = new HashMap<>(); + if (this.commandLine.hasOption('p')) { + for (String parameter : this.commandLine.getOptionValues('p')) { + String[] parts = parameter.split("=", 2); + String name = parts[0].trim(); + String value; + if (parts.length > 1) { + value = parts[1].trim(); + } else { + value = "true"; + } + this.parameters.put(name, value); + } + } + + // verbose + verbose = false; + if (commandLine.hasOption('v')) { + verbose = true; + } + + // scope + if (this.commandLine.getOptionValue('s') != null) { + this.scope = this.commandLine.getOptionValue('s'); + if (this.scope != null && Curator.TxScope.valueOf(this.scope.toUpperCase()) == null) { + this.handler.logError("Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " + + "'open' recognized"); + throw new IllegalArgumentException( + "Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " + + "'open' recognized"); + } + } + } + + /** + * Fills in required command line options for the task or taskFile option. + * Checks if there are is a missing required -i option. + * Checks if -t task has a valid task option. + * Checks if -T taskfile is a valid file. + */ + private void initTaskLineOptionsAndCheckIfValid() { + // task or taskFile + if (this.commandLine.hasOption('t')) { + this.task = this.commandLine.getOptionValue('t'); + if (!Arrays.asList(CurationClientOptions.getTaskOptions()).contains(this.task)) { + super.handler + .logError("-t task must be one of: " + Arrays.toString(CurationClientOptions.getTaskOptions())); + throw new IllegalArgumentException( + "-t task must be one of: " + Arrays.toString(CurationClientOptions.getTaskOptions())); + } + } else if (this.commandLine.hasOption('T')) { + this.taskFile = this.commandLine.getOptionValue('T'); + if (!(new File(this.taskFile).isFile())) { + super.handler + .logError("-T taskFile must be valid file: " + this.taskFile); + throw new IllegalArgumentException("-T taskFile must be valid file: " + this.taskFile); + } + } + + if (this.commandLine.hasOption('i')) { + this.id = this.commandLine.getOptionValue('i'); + } else { + super.handler.logError("Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " + + "help)"); + throw new IllegalArgumentException( + "Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " + + "help)"); + } } } diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java index a10dbba021..be46518349 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java @@ -38,7 +38,7 @@ public enum CurationClientOptions { } else if (commandLine.hasOption("q")) { return CurationClientOptions.QUEUE; } - return CurationClientOptions.HELP; + return null; } protected static Options constructOptions() { @@ -62,7 +62,7 @@ public enum CurationClientOptions { return options; } - private static String[] getTaskOptions() { + public static String[] getTaskOptions() { ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); return configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask"); } diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/curate.txt b/dspace-api/src/test/data/dspaceFolder/assetstore/curate.txt new file mode 100644 index 0000000000..ff2cb89ef6 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/curate.txt @@ -0,0 +1,2 @@ +checklinks +requiredmetadata diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java new file mode 100644 index 0000000000..ecce4320d2 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -0,0 +1,407 @@ +package org.dspace.curate; + +import static org.hamcrest.Matchers.is; +import static com.jayway.jsonpath.JsonPath.read; +import static org.hamcrest.Matchers.containsString; +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.File; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import com.google.gson.Gson; +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.ProcessBuilder; +import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; +import org.dspace.app.rest.matcher.ProcessMatcher; +import org.dspace.app.rest.model.ParameterValueRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.model.ScriptRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.ProcessStatus; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Maria Verdonck (Atmire) on 24/06/2020 + */ +public class CurationScriptIT extends AbstractControllerIntegrationTest { + + @Autowired + private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter; + + private final static String SCRIPTS_ENDPOINT = "/api/" + ScriptRest.CATEGORY + "/" + ScriptRest.PLURAL_NAME; + private final static String CURATE_SCRIPT_ENDPOINT = SCRIPTS_ENDPOINT + "/curate/" + ProcessRest.PLURAL_NAME; + + @Test + public void curateScript_invalidTaskOption() throws Exception { + context.turnOffAuthorisationSystem(); + + String token = getAuthToken(admin.getEmail(), password); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); + parameters.add(new DSpaceCommandLineParameter("-t", "invalidTaskOption")); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + // Request with -t + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains the valid options + .andExpect(status().reason(containsString(Arrays.toString(CurationClientOptions.getTaskOptions())))); + } + + @Test + public void curateScript_MissingEperson() throws Exception { + context.turnOffAuthorisationSystem(); + + String token = getAuthToken(admin.getEmail(), password); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + // Request with missing required -e + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains -e (the missing required cl option + .andExpect(status().reason(containsString("-e"))); + } + + @Test + public void curateScript_NonExistentEPerson() throws Exception { + context.turnOffAuthorisationSystem(); + + String token = getAuthToken(admin.getEmail(), password); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", "nonExistentEmail@test.com")); + parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + // Request with -e + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains email + .andExpect(status().reason(containsString("email"))); + } + + @Test + public void curateScript_MissingHandle() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + // Request with missing required -i + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains handle + .andExpect(status().reason(containsString("handle"))); + } + + @Test + public void curateScript_MissingTaskOrTaskFile() throws Exception { + context.turnOffAuthorisationSystem(); + + String token = getAuthToken(admin.getEmail(), password); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + // Request without -t or -T (and no -q ) + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains task + .andExpect(status().reason(containsString("task"))); + } + + @Test + public void curateScript_InvalidScope() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-i", "all")); + parameters.add(new DSpaceCommandLineParameter("-s", "invalidScope")); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + // Request with invalid -s ; must be object, curation or open + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()); + } + + @Test + public void curateScript_InvalidTaskFile() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-i", "all")); + parameters.add(new DSpaceCommandLineParameter("-T", "invalidTaskFile")); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + // Request with invalid -s ; must be object, curation or open + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains taskFile + .andExpect(status().reason(containsString("taskFile"))); + } + + @Test + public void curateScript_validRequest_Task() throws Exception { + context.turnOffAuthorisationSystem(); + + String token = getAuthToken(admin.getEmail(), password); + AtomicReference idRef = new AtomicReference<>(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(admin.getID()), parameters, + ProcessStatus.SCHEDULED)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @Test + public void curateScript_validRequest_TaskFile() throws Exception { + context.turnOffAuthorisationSystem(); + + String token = getAuthToken(admin.getEmail(), password); + AtomicReference idRef = new AtomicReference<>(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + File taskFile = new File(testProps.get("test.curateTaskFile").toString()); + + LinkedList parameters = new LinkedList<>(); + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); + parameters.add(new DSpaceCommandLineParameter("-T", taskFile.getAbsolutePath())); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(admin.getID()), parameters, + ProcessStatus.SCHEDULED)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + +} diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index 273d93c968..3af96b20fc 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -11,3 +11,6 @@ test.folder.assetstore = ./target/testing/dspace/assetstore #Path for a test file to create bitstreams test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf + +#Path for a test Taskfile for the curate script +test.curateTaskFile = ./target/testing/dspace/assetstore/curate.txt From 85e31825cc6ab6890bf6825c5424a5f332ca000d Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 24 Jun 2020 17:47:21 +0200 Subject: [PATCH 159/465] 71513: test fix from adding curate script to test script definitions --- .../test/data/dspaceFolder/config/spring/api/scripts.xml | 1 + .../java/org/dspace/app/rest/ScriptRestRepositoryIT.java | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) 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 b717240783..76cb57a40d 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 @@ -24,6 +24,7 @@ + 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 c1ba31305e..aeecd4bbe2 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 @@ -82,7 +82,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), scriptConfigurations.get(2).getDescription()), ScriptMatcher.matchScript(scriptConfigurations.get(3).getName(), - scriptConfigurations.get(3).getDescription()) + scriptConfigurations.get(3).getDescription()), + ScriptMatcher.matchScript(scriptConfigurations.get(4).getName(), + scriptConfigurations.get(4).getDescription()) ))); } @@ -139,7 +141,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts/mock-script")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ScriptMatcher - .matchMockScript(scriptConfigurations.get(3).getOptions()))); + .matchMockScript(scriptConfigurations.get(scriptConfigurations.size() - 1).getOptions()))); } @Test From 9ef83bf185a2270661bc25378044ca5d8cdcc239 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 25 Jun 2020 16:32:44 +0200 Subject: [PATCH 160/465] 71513: -i valid dso or all check + test & fix so that -t plugin-key instead of entire config & Added test voor noop curate output --- .../java/org/dspace/curate/CurationCli.java | 42 ++++++++++++++----- .../dspace/curate/CurationClientOptions.java | 26 +++++++++--- .../java/org/dspace/curate/CuratorTest.java | 41 ++++++++++++++++-- .../org/dspace/curate/CurationScriptIT.java | 38 ++++++++++++++--- 4 files changed, 121 insertions(+), 26 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index 2f7fb1c532..8f5d91cc1c 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -17,13 +17,13 @@ import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; import java.sql.SQLException; -import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.commons.io.output.NullOutputStream; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; @@ -31,6 +31,8 @@ import org.dspace.curate.factory.CurateServiceFactory; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.utils.DSpace; @@ -74,13 +76,13 @@ public class CurationCli extends DSpaceRunnable { // process task queue if (curationClientOptions == CurationClientOptions.QUEUE) { // process the task queue - TaskQueue queue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService() + TaskQueue taskQueue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService() .getSinglePlugin(TaskQueue.class); - if (queue == null) { + if (taskQueue == null) { super.handler.logError("No implementation configured for queue"); throw new UnsupportedOperationException("No queue service available"); } - long timeRun = this.runQueue(queue, curator); + long timeRun = this.runQueue(taskQueue, curator); this.endScript(timeRun); } } @@ -151,8 +153,8 @@ public class CurationCli extends DSpaceRunnable { curator.clear(); // does entry relate to a DSO or workflow object? if (entry.getObjectId().indexOf('/') > 0) { - for (String task : entry.getTaskNames()) { - curator.addTask(task); + for (String taskName : entry.getTaskNames()) { + curator.addTask(taskName); } curator.curate(context, entry.getObjectId()); } else { @@ -306,7 +308,7 @@ public class CurationCli extends DSpaceRunnable { /** * Fills in required command line options for the task or taskFile option. - * Checks if there are is a missing required -i option. + * Checks if there are is a missing required -i option and if -i is either 'all' or a valid dso handle. * Checks if -t task has a valid task option. * Checks if -T taskfile is a valid file. */ @@ -314,11 +316,11 @@ public class CurationCli extends DSpaceRunnable { // task or taskFile if (this.commandLine.hasOption('t')) { this.task = this.commandLine.getOptionValue('t'); - if (!Arrays.asList(CurationClientOptions.getTaskOptions()).contains(this.task)) { + if (!CurationClientOptions.getTaskOptions().contains(this.task)) { super.handler - .logError("-t task must be one of: " + Arrays.toString(CurationClientOptions.getTaskOptions())); + .logError("-t task must be one of: " + CurationClientOptions.getTaskOptions()); throw new IllegalArgumentException( - "-t task must be one of: " + Arrays.toString(CurationClientOptions.getTaskOptions())); + "-t task must be one of: " + CurationClientOptions.getTaskOptions()); } } else if (this.commandLine.hasOption('T')) { this.taskFile = this.commandLine.getOptionValue('T'); @@ -330,7 +332,25 @@ public class CurationCli extends DSpaceRunnable { } if (this.commandLine.hasOption('i')) { - this.id = this.commandLine.getOptionValue('i'); + this.id = this.commandLine.getOptionValue('i').toLowerCase(); + if (!this.id.equalsIgnoreCase("all")) { + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + DSpaceObject dso; + try { + dso = handleService.resolveToObject(this.context, id); + } catch (SQLException e) { + super.handler.logError("SQLException trying to resolve handle " + id + " to a valid dso"); + throw new IllegalArgumentException( + "SQLException trying to resolve handle " + id + " to a valid dso"); + } + if (dso == null) { + super.handler.logError("Id must be specified: a valid dso handle or 'all'; " + this.id + " could " + + "not be resolved to valid dso handle"); + throw new IllegalArgumentException( + "Id must be specified: a valid dso handle or 'all'; " + this.id + " could " + + "not be resolved to valid dso handle"); + } + } } else { super.handler.logError("Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " + "help)"); diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java index be46518349..7daf107aad 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java @@ -7,10 +7,12 @@ */ package org.dspace.curate; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; +import org.apache.commons.lang3.StringUtils; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -24,6 +26,8 @@ public enum CurationClientOptions { QUEUE, HELP; + private static List taskOptions; + /** * This method resolves the CommandLine parameters to figure out which action the curation script should perform * @@ -44,7 +48,7 @@ public enum CurationClientOptions { protected static Options constructOptions() { Options options = new Options(); - options.addOption("t", "task", true, "curation task name; options: " + Arrays.toString(getTaskOptions())); + options.addOption("t", "task", true, "curation task name; options: " + getTaskOptions()); options.addOption("T", "taskfile", true, "file containing curation task names"); options.addOption("i", "id", true, "Id (handle) of object to perform task on, or 'all' to perform on whole repository"); @@ -62,8 +66,20 @@ public enum CurationClientOptions { return options; } - public static String[] getTaskOptions() { - ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - return configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask"); + /** + * Creates list of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask + * + * @return List of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask + */ + public static List getTaskOptions() { + if (taskOptions == null) { + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + String[] taskConfigs = configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask"); + taskOptions = new ArrayList<>(); + for (String taskConfig : taskConfigs) { + taskOptions.add(StringUtils.substringAfterLast(taskConfig, "=").trim()); + } + } + return taskOptions; } } diff --git a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java index 8ca6b6c172..96cf2d9d7a 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java +++ b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java @@ -8,23 +8,42 @@ package org.dspace.curate; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.HashMap; import java.util.Map; import org.dspace.AbstractUnitTest; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.SiteService; +import org.dspace.ctask.general.NoOpCurationTask; 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.Spy; +import org.mockito.junit.MockitoJUnitRunner; /** * * @author mhwood */ -public class CuratorTest - extends AbstractUnitTest { +@RunWith(MockitoJUnitRunner.class) +public class CuratorTest extends AbstractUnitTest { + + @InjectMocks + private Curator curator; + + @Mock + NoOpCurationTask noOpCurationTask; + + @Spy + TaskResolver taskResolver; + private static final SiteService SITE_SERVICE = ContentServiceFactory.getInstance().getSiteService(); static final String RUN_PARAMETER_NAME = "runParameter"; @@ -44,8 +63,7 @@ public class CuratorTest * @throws java.lang.Exception passed through. */ @Test - public void testCurate_DSpaceObject() - throws Exception { + public void testCurate_DSpaceObject() throws Exception { System.out.println("curate"); final String TASK_NAME = "dummyTask"; @@ -80,4 +98,19 @@ public class CuratorTest assertEquals("Wrong run parameter", RUN_PARAMETER_VALUE, runParameter); assertEquals("Wrong task property", TASK_PROPERTY_VALUE, taskProperty); } + + @Test + public void testCurate_NoOpTask() throws Exception { + StringBuilder reporterOutput = new StringBuilder(); + curator.setReporter(reporterOutput); // Send any report to our StringBuilder. + + curator.addTask("noop"); + Item item = mock(Item.class); + when(item.getType()).thenReturn(2); + when(item.getHandle()).thenReturn("testHandle"); + curator.curate(context, item); + + assertEquals(Curator.CURATE_SUCCESS, curator.getStatus("noop")); + assertEquals(reporterOutput.toString(), "No operation performed on testHandle"); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index ecce4320d2..b745f8d75d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -1,5 +1,6 @@ package org.dspace.curate; +import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.is; import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.containsString; @@ -8,7 +9,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.File; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -87,7 +87,7 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Illegal Argument Exception .andExpect(status().isBadRequest()) // Contains the valid options - .andExpect(status().reason(containsString(Arrays.toString(CurationClientOptions.getTaskOptions())))); + .andExpect(status().reason(containsString(CurationClientOptions.getTaskOptions().toString()))); } @Test @@ -114,7 +114,7 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { LinkedList parameters = new LinkedList<>(); parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); - parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions().get(0))); List list = parameters.stream() .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter @@ -159,7 +159,7 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { parameters.add(new DSpaceCommandLineParameter("-e", "nonExistentEmail@test.com")); parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); - parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions().get(0))); List list = parameters.stream() .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter @@ -186,7 +186,7 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { LinkedList parameters = new LinkedList<>(); parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); - parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions().get(0))); List list = parameters.stream() .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter @@ -204,6 +204,32 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { .andExpect(status().reason(containsString("handle"))); } + @Test + public void curateScript_invalidHandle() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-i", "invalidhandle")); + parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions().get(0))); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + // Request with missing required -i + getClient(token) + .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + // Illegal Argument Exception + .andExpect(status().isBadRequest()) + // Contains invalidHandle + .andExpect(status().reason(containsStringIgnoringCase("invalidhandle"))); + } + @Test public void curateScript_MissingTaskOrTaskFile() throws Exception { context.turnOffAuthorisationSystem(); @@ -324,7 +350,7 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { parameters.add(new DSpaceCommandLineParameter("-e", admin.getEmail())); parameters.add(new DSpaceCommandLineParameter("-i", publicItem1.getHandle())); - parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions()[0])); + parameters.add(new DSpaceCommandLineParameter("-t", CurationClientOptions.getTaskOptions().get(0))); List list = parameters.stream() .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter From a437cf41c3ec3488d9d7516045c41d484e3f5489 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 25 Jun 2020 16:34:06 +0200 Subject: [PATCH 161/465] missing license --- .../src/test/java/org/dspace/curate/CurationScriptIT.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index b745f8d75d..ac5f149c48 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.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.curate; import static org.hamcrest.Matchers.containsStringIgnoringCase; From 238317bc8df250130af162de845623702b43cd81 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 1 Jul 2020 16:50:45 +0200 Subject: [PATCH 162/465] Arxiv Live import integration --- .../ArXivImportMetadataSourceServiceImpl.java | 207 ++++++------------ .../importer/external/datamodel/Query.java | 2 +- .../config/spring/api/arxiv-integration.xml | 49 +++-- 3 files changed, 96 insertions(+), 162 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 567cce1b9a..3ef9a2999f 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -7,30 +7,23 @@ */ package org.dspace.importer.external.arxiv.service; -import java.io.BufferedReader; -import java.io.InputStreamReader; import java.io.StringReader; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMXMLBuilderFactory; import org.apache.axiom.om.OMXMLParserWrapper; import org.apache.axiom.om.xpath.AXIOMXPath; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.HttpParams; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -39,17 +32,8 @@ import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.jaxen.JaxenException; public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService { - private int timeout = 1000; - - /** - * How long to wait for a connection to be established. - * - * @param timeout milliseconds - */ - public void setTimeout(int timeout) { - this.timeout = timeout; - } + private WebTarget webTarget; @Override public Collection getRecords(String query, int start, int count) throws MetadataSourceException { @@ -95,7 +79,8 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata @Override public void init() throws Exception { - + Client client = ClientBuilder.newClient(); + webTarget = client.target("http://export.arxiv.org/api/query"); } @@ -115,7 +100,7 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata @Override public Collection findMatchingRecords(Query query) throws MetadataSourceException { - return null; + return retry(new FindMatchingRecordCallable(query)); } private class SearchByQueryCallable implements Callable> { @@ -140,62 +125,19 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata String queryString = query.getParameterAsClass("query", String.class); Integer start = query.getParameterAsClass("start", Integer.class); Integer maxResult = query.getParameterAsClass("count", Integer.class); - - HttpGet method = null; - try { - HttpClient client = new DefaultHttpClient(); - HttpParams params = client.getParams(); - params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); - - try { - URIBuilder uriBuilder = new URIBuilder("http://export.arxiv.org/api/query"); - uriBuilder.addParameter("search_query", queryString); - if (maxResult != null) { - uriBuilder.addParameter("max_results", String.valueOf(maxResult)); - } - if (start != null) { - uriBuilder.addParameter("start", String.valueOf(start)); - } - method = new HttpGet(uriBuilder.build()); - } catch (URISyntaxException ex) { - throw new HttpException(ex.getMessage()); - } - - // Execute the method. - HttpResponse response = client.execute(method); - StatusLine responseStatus = response.getStatusLine(); - int statusCode = responseStatus.getStatusCode(); - - if (statusCode != HttpStatus.SC_OK) { - if (statusCode == HttpStatus.SC_BAD_REQUEST) { - throw new RuntimeException("arXiv query is not valid"); - } else { - throw new RuntimeException("Http call failed: " - + responseStatus); - } - } - - try { - InputStreamReader isReader = new InputStreamReader(response.getEntity().getContent()); - BufferedReader reader = new BufferedReader(isReader); - StringBuilder sb = new StringBuilder(); - String str; - while ((str = reader.readLine()) != null) { - sb.append(str); - } - System.out.println("XML: " + sb.toString()); - List omElements = splitToRecords(sb.toString()); - for (OMElement record : omElements) { - results.add(transformSourceRecords(record)); - } - } catch (Exception e) { - throw new RuntimeException( - "ArXiv identifier is not valid or not exist"); - } - } finally { - if (method != null) { - method.releaseConnection(); - } + WebTarget local = webTarget.queryParam("search_query", queryString); + if (maxResult != null) { + local = local.queryParam("max_results", String.valueOf(maxResult)); + } + if (start != null) { + local = local.queryParam("start", String.valueOf(start)); + } + Invocation.Builder invocationBuilder = local.request(MediaType.TEXT_PLAIN_TYPE); + Response response = invocationBuilder.get(); + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + for (OMElement record : omElements) { + results.add(transformSourceRecords(record)); } return results; } @@ -217,78 +159,64 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata public List call() throws Exception { List results = new ArrayList(); String arxivid = query.getParameterAsClass("id", String.class); - HttpGet method = null; - try { - HttpClient client = new DefaultHttpClient(); - HttpParams params = client.getParams(); - params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); - try { - URIBuilder uriBuilder = new URIBuilder("http://export.arxiv.org/api/query"); - if (StringUtils.isNotBlank(arxivid)) { - arxivid = arxivid.trim(); - if (arxivid.startsWith("http://arxiv.org/abs/")) { - arxivid = arxivid.substring("http://arxiv.org/abs/".length()); - } else if (arxivid.toLowerCase().startsWith("arxiv:")) { - arxivid = arxivid.substring("arxiv:".length()); - } - uriBuilder.addParameter("id_list", arxivid); - method = new HttpGet(uriBuilder.build()); - } - } catch (URISyntaxException ex) { - throw new HttpException(ex.getMessage()); - } - - // Execute the method. - HttpResponse response = client.execute(method); - StatusLine responseStatus = response.getStatusLine(); - int statusCode = responseStatus.getStatusCode(); - if (statusCode != HttpStatus.SC_OK) { - if (statusCode == HttpStatus.SC_BAD_REQUEST) { - throw new RuntimeException("arXiv query is not valid"); - } else { - throw new RuntimeException("Http call failed: " - + responseStatus); - } - } - try { - InputStreamReader isReader = new InputStreamReader(response.getEntity().getContent()); - BufferedReader reader = new BufferedReader(isReader); - StringBuffer sb = new StringBuffer(); - String str; - while ((str = reader.readLine()) != null) { - sb.append(str); - } - List omElements = splitToRecords(sb.toString()); - for (OMElement record : omElements) { - results.add(transformSourceRecords(record)); - } - } catch (Exception e) { - throw new RuntimeException( - "ArXiv identifier is not valid or not exist"); - } - } finally { - if (method != null) { - method.releaseConnection(); + if (StringUtils.isNotBlank(arxivid)) { + arxivid = arxivid.trim(); + if (arxivid.startsWith("http://arxiv.org/abs/")) { + arxivid = arxivid.substring("http://arxiv.org/abs/".length()); + } else if (arxivid.toLowerCase().startsWith("arxiv:")) { + arxivid = arxivid.substring("arxiv:".length()); } } + WebTarget local = webTarget.queryParam("id_list", arxivid); + Invocation.Builder invocationBuilder = local.request(MediaType.TEXT_PLAIN_TYPE); + Response response = invocationBuilder.get(); + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + for (OMElement record : omElements) { + results.add(transformSourceRecords(record)); + } return results; } } private class FindMatchingRecordCallable implements Callable> { + private Query query; - private FindMatchingRecordCallable(Item item) throws MetadataSourceException { - query = getGenerateQueryForItem().generateQueryForItem(item); - } - - public FindMatchingRecordCallable(Query q) { + private FindMatchingRecordCallable(Query q) { query = q; } @Override public List call() throws Exception { - return null; + String queryString = getQuery(this.query); + List results = new ArrayList(); + WebTarget local = webTarget.queryParam("search_query", queryString); + Invocation.Builder invocationBuilder = local.request(MediaType.TEXT_PLAIN_TYPE); + Response response = invocationBuilder.get(); + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + for (OMElement record : omElements) { + results.add(transformSourceRecords(record)); + } + return results; + } + + private String getQuery(Query query) { + String title = query.getParameterAsClass("title", String.class); + String author = query.getParameterAsClass("author", String.class); + StringBuffer queryString = new StringBuffer(); + if (StringUtils.isNotBlank(title)) { + queryString.append("ti:\"").append(title).append("\""); + } + if (StringUtils.isNotBlank(author)) { + // [FAU] + if (queryString.length() > 0) { + queryString.append(" AND "); + } + queryString.append("au:\"").append(author).append("\""); + } + return queryString.toString(); } } @@ -306,5 +234,4 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata } } - } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/Query.java b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/Query.java index 8c5e1b394a..8f392bdb52 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/Query.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/Query.java @@ -71,7 +71,7 @@ public class Query { return null; } else { Object o = c.iterator().next(); - if (clazz.isAssignableFrom(o.getClass())) { + if (o != null && clazz.isAssignableFrom(o.getClass())) { return (T) o; } else { return null; diff --git a/dspace/config/spring/api/arxiv-integration.xml b/dspace/config/spring/api/arxiv-integration.xml index d16a1ae52a..a22bfe9eeb 100644 --- a/dspace/config/spring/api/arxiv-integration.xml +++ b/dspace/config/spring/api/arxiv-integration.xml @@ -30,55 +30,55 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + @@ -118,8 +118,15 @@ - - + + + + + + + @@ -133,4 +140,4 @@ - \ No newline at end of file + From e7b33c5fb5bca314384c97088ea43a4dbd93cf6c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 1 Jul 2020 17:53:06 +0200 Subject: [PATCH 163/465] 71629: fix testCurate_NoOpTask --- .../curate/CurationScriptConfiguration.java | 5 +++ .../main/java/org/dspace/curate/Curator.java | 20 ++++------- .../java/org/dspace/curate/CuratorTest.java | 33 ++++++++++++++----- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java index 541ed3f8e9..785926908e 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java @@ -37,6 +37,11 @@ public class CurationScriptConfiguration extends ScriptCo this.dspaceRunnableClass = dspaceRunnableClass; } + /** + * Only admin can run Curation script via the scripts and processes endpoints. + * @param context The relevant DSpace context + * @return True if currentUser is admin, otherwise false + */ @Override public boolean isAllowedToExecute(Context context) { try { diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java index 44733174df..8f12750bae 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curator.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java @@ -98,6 +98,7 @@ public class Curator { communityService = ContentServiceFactory.getInstance().getCommunityService(); itemService = ContentServiceFactory.getInstance().getItemService(); handleService = HandleServiceFactory.getInstance().getHandleService(); + resolver = new TaskResolver(); } /** @@ -142,10 +143,10 @@ public class Curator { // performance order currently FIFO - to be revisited perfList.add(taskName); } catch (IOException ioE) { - log.error("Task: '" + taskName + "' initialization failure: " + ioE.getMessage()); + System.out.println("Task: '" + taskName + "' initialization failure: " + ioE.getMessage()); } } else { - log.error("Task: '" + taskName + "' does not resolve"); + System.out.println("Task: '" + taskName + "' does not resolve"); } return this; } @@ -259,13 +260,6 @@ public class Curator { /** * Performs all configured tasks upon DSpace object * (Community, Collection or Item). - *

- * Note: Site-wide tasks will default to running as - * an Anonymous User unless you call the Site-wide task - * via the {@link curate(Context,String)} or - * {@link #curate(Context, DSpaceObject)} method with an - * authenticated Context object. - * * @param dso the DSpace object * @throws IOException if IO error */ @@ -325,7 +319,7 @@ public class Curator { taskQ.enqueue(queueId, new TaskQueueEntry(c.getCurrentUser().getName(), System.currentTimeMillis(), perfList, id)); } else { - log.error("curate - no TaskQueue implemented"); + System.out.println("curate - no TaskQueue implemented"); } } @@ -346,7 +340,7 @@ public class Curator { try { reporter.append(message); } catch (IOException ex) { - log.error("Task reporting failure", ex); + System.out.println("Task reporting failure: " + ex); } } @@ -552,7 +546,7 @@ public class Curator { return !suspend(statusCode); } catch (IOException ioe) { //log error & pass exception upwards - log.error("Error executing curation task '" + task.getName() + "'", ioe); + System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe); throw ioe; } } @@ -568,7 +562,7 @@ public class Curator { return !suspend(statusCode); } catch (IOException ioe) { //log error & pass exception upwards - log.error("Error executing curation task '" + task.getName() + "'", ioe); + System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe); throw ioe; } } diff --git a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java index 96cf2d9d7a..ddfa751322 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java +++ b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java @@ -19,6 +19,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.SiteService; +import org.dspace.core.factory.CoreServiceFactory; import org.dspace.ctask.general.NoOpCurationTask; import org.dspace.services.ConfigurationService; import org.junit.Test; @@ -29,7 +30,6 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; /** - * * @author mhwood */ @RunWith(MockitoJUnitRunner.class) @@ -51,15 +51,20 @@ public class CuratorTest extends AbstractUnitTest { static final String TASK_PROPERTY_NAME = "taskProperty"; static final String TASK_PROPERTY_VALUE = "a property"; - /** Value of a known runtime parameter, if any. */ + /** + * Value of a known runtime parameter, if any. + */ static String runParameter; - /** Value of a known task property, if any. */ + /** + * Value of a known task property, if any. + */ static String taskProperty; /** * Test of curate method, of class Curator. * Currently this just tests task properties and run parameters. + * * @throws java.lang.Exception passed through. */ @Test @@ -71,7 +76,7 @@ public class CuratorTest extends AbstractUnitTest { // Configure the task to be run. ConfigurationService cfg = kernelImpl.getConfigurationService(); cfg.setProperty("plugin.named.org.dspace.curate.CurationTask", - DummyTask.class.getName() + " = " + TASK_NAME); + DummyTask.class.getName() + " = " + TASK_NAME); cfg.setProperty(TASK_NAME + '.' + TASK_PROPERTY_NAME, TASK_PROPERTY_VALUE); // Get and configure a Curator. @@ -90,27 +95,37 @@ public class CuratorTest extends AbstractUnitTest { // Check the result. System.out.format("Task %s result was '%s'%n", - TASK_NAME, instance.getResult(TASK_NAME)); + TASK_NAME, instance.getResult(TASK_NAME)); System.out.format("Task %s status was %d%n", - TASK_NAME, instance.getStatus(TASK_NAME)); + TASK_NAME, instance.getStatus(TASK_NAME)); assertEquals("Unexpected task status", - Curator.CURATE_SUCCESS, instance.getStatus(TASK_NAME)); + Curator.CURATE_SUCCESS, instance.getStatus(TASK_NAME)); assertEquals("Wrong run parameter", RUN_PARAMETER_VALUE, runParameter); assertEquals("Wrong task property", TASK_PROPERTY_VALUE, taskProperty); } @Test public void testCurate_NoOpTask() throws Exception { + + CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); + + final String TASK_NAME = "noop"; + + // Configure the noop task to be run. + ConfigurationService cfg = kernelImpl.getConfigurationService(); + cfg.setProperty("plugin.named.org.dspace.curate.CurationTask", + NoOpCurationTask.class.getName() + " = " + TASK_NAME); + StringBuilder reporterOutput = new StringBuilder(); curator.setReporter(reporterOutput); // Send any report to our StringBuilder. - curator.addTask("noop"); + curator.addTask(TASK_NAME); Item item = mock(Item.class); when(item.getType()).thenReturn(2); when(item.getHandle()).thenReturn("testHandle"); curator.curate(context, item); - assertEquals(Curator.CURATE_SUCCESS, curator.getStatus("noop")); + assertEquals(Curator.CURATE_SUCCESS, curator.getStatus(TASK_NAME)); assertEquals(reporterOutput.toString(), "No operation performed on testHandle"); } } From e4c98b4742309511eb96b2ed844a928508ba7dc0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 1 Jul 2020 21:34:09 +0200 Subject: [PATCH 164/465] Fixed issue with DSpaceControlledVocabulary getBestMatch --- .../dspace/content/authority/DSpaceControlledVocabulary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index c729c547c7..ce2237dd93 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -190,7 +190,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera String xpathExpression = ""; String[] textHierarchy = text.split(hierarchyDelimiter, -1); for (int i = 0; i < textHierarchy.length; i++) { - xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); + xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'")); } XPath xpath = XPathFactory.newInstance().newXPath(); List choices = new ArrayList(); From 5a0844429a8704826e4bd7d2c2454120b5765d9a Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 1 Jul 2020 23:12:34 +0200 Subject: [PATCH 165/465] Add missing javadocs --- .../org/dspace/content/authority/Choice.java | 5 +++- .../content/authority/ChoiceAuthority.java | 27 +++++++++++++++++++ .../authority/HierarchicalAuthority.java | 5 ++++ .../service/ChoiceAuthorityService.java | 4 ++- .../dspace/app/rest/utils/AuthorityUtils.java | 12 +++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index ff09e7553a..697642798b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -34,7 +34,10 @@ public class Choice { public String value = null; /** - * A boolean representing if choice entry value can selected + * A boolean representing if choice entry value can selected (usually true). + * Hierarchical authority can flag some choice as not selectable to force the + * use to choice a more detailed terms in the tree, such a leaf or a deeper + * branch */ public boolean selectable = true; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index a3bfeb9e83..ff196e2b79 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -88,18 +88,45 @@ public interface ChoiceAuthority { return getLabel(key, locale); } + /** + * Get a map of additional information related to the specified key in the + * authority. + * + * @param key the key of the entry + * @param locale explicit localization key if available, or null + * @return a map of additional information related to the key + */ default Map getExtra(String key, String locale) { return new HashMap(); } + /** + * Return true for hierarchical authorities + * + * @return true if hierarchical, default false + */ default boolean isHierarchical() { return false; } + /** + * Scrollable authorities allows the scroll of the entries without applying + * filter/query to the + * {@link #getMatches(String, String, Collection, int, int, String)} + * + * @return true if scrollable, default false + */ default boolean isScrollable() { return false; } + /** + * Hierarchical authority can provide an hint for the UI about how many levels + * preload to improve the UX. It provides a valid default for hierarchical + * authorities + * + * @return 0 if hierarchical, null otherwise + */ default Integer getPreloadLevel() { return isHierarchical() ? 0 : null; } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index 279f62d22f..ac6c59e9d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -70,8 +70,13 @@ public interface HierarchicalAuthority extends ChoiceAuthority { */ public Choice getParentChoice(String authorityName, String vocabularyId, String locale); + /** + * Provides an hint for the UI to preload some levels to improve the UX. It + * usually mean that these preloaded level will be shown expanded by default + */ public Integer getPreloadLevel(); + @Override default boolean isHierarchical() { return true; } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index bfcee338b9..335a837ee9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -208,7 +208,9 @@ public interface ChoiceAuthorityService { public Choices getTopChoices(String authorityName, int start, int limit, String locale); /** - * + * Return the direct parent of an entry identified by its id in an hierarchical + * authority. + * * @param authorityName authority name * @param vocabularyId child id * @param locale explicit localization key if available, or null diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 22e1ff6101..fc54fe194e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -76,6 +76,18 @@ public class AuthorityUtils { return entry; } + /** + * This utility method is currently a workaround to enrich the REST object with + * information from the parent vocabulary that is not referenced by the Choice + * model + * + * @param choice the dspace-api choice to expose as vocabulary entry + * @param authorityName the name of the vocabulary + * @param storeAuthority true if the entry id should be exposed as + * an authority for storing it in the metadatavalue + * @param projection the rest projection to apply + * @return the vocabulary entry rest reppresentation of the provided choice + */ public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority, Projection projection) { if (choice == null) { From b15fa7c5b0c381dd05d6493d0b5dd3e08a402c02 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 2 Jul 2020 02:32:11 +0200 Subject: [PATCH 166/465] show ids and links in response --- .../ArXivIdMetadataContributor.java | 180 ++++++++++++++++++ .../ArXivImportMetadataSourceServiceImpl.java | 165 ++++++++++++++-- .../spring-dspace-addon-import-services.xml | 2 +- .../config/spring/api/arxiv-integration.xml | 131 ++++++------- 4 files changed, 384 insertions(+), 94 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java new file mode 100644 index 0000000000..018cde8ac7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java @@ -0,0 +1,180 @@ +/** + * 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.importer.external.arxiv.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Resource; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMText; +import org.apache.axiom.om.xpath.AXIOMXPath; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.metadatamapping.contributor.MetadataContributor; +import org.jaxen.JaxenException; +import org.springframework.beans.factory.annotation.Required; + +public class ArXivIdMetadataContributor implements MetadataContributor { + private MetadataFieldConfig field; + + /** + * Return prefixToNamespaceMapping + * + * @return a prefixToNamespaceMapping map + */ + public Map getPrefixToNamespaceMapping() { + return prefixToNamespaceMapping; + } + + private MetadataFieldMapping> metadataFieldMapping; + + /** + * Return metadataFieldMapping + * + * @return MetadataFieldMapping + */ + public MetadataFieldMapping> getMetadataFieldMapping() { + return metadataFieldMapping; + } + + /** + * Set the metadataFieldMapping of this ArXivIdMetadataContributor + * + * @param metadataFieldMapping the new mapping. + */ + public void setMetadataFieldMapping( + MetadataFieldMapping> metadataFieldMapping) { + this.metadataFieldMapping = metadataFieldMapping; + } + + /** + * Set the prefixToNamespaceMapping for this object, + * + * @param prefixToNamespaceMapping the new mapping. + */ + @Resource(name = "isiFullprefixMapping") + public void setPrefixToNamespaceMapping(Map prefixToNamespaceMapping) { + this.prefixToNamespaceMapping = prefixToNamespaceMapping; + } + + private Map prefixToNamespaceMapping; + + /** + * Initialize ArXivIdMetadataContributor with a query, prefixToNamespaceMapping and MetadataFieldConfig + * + * @param query query string + * @param prefixToNamespaceMapping metadata prefix to namespace mapping + * @param field + * MetadataFieldConfig + */ + public ArXivIdMetadataContributor(String query, Map prefixToNamespaceMapping, + MetadataFieldConfig field) { + this.query = query; + this.prefixToNamespaceMapping = prefixToNamespaceMapping; + this.field = field; + } + + /** + * Empty constructor for ArXivIdMetadataContributor + */ + public ArXivIdMetadataContributor() { + + } + + private String query; + + /** + * Return the MetadataFieldConfig used while retrieving MetadatumDTO + * + * @return MetadataFieldConfig + */ + public MetadataFieldConfig getField() { + return field; + } + + /** + * Setting the MetadataFieldConfig + * + * @param field MetadataFieldConfig used while retrieving MetadatumDTO + */ + @Required + public void setField(MetadataFieldConfig field) { + this.field = field; + } + + /** + * Return query used to create an xpathExpression on, this query is used to + * + * @return the query this instance is based on + */ + public String getQuery() { + return query; + } + + @Required + public void setQuery(String query) { + this.query = query; + } + + /** + * Retrieve the metadata associated with the given object. + * Depending on the retrieved node (using the query), different types of values will be added to the MetadatumDTO + * list + * + * @param t A class to retrieve metadata from. + * @return a collection of import records. Only the identifier of the found records may be put in the record. + */ + @Override + public Collection contributeMetadata(OMElement t) { + List values = new LinkedList<>(); + try { + AXIOMXPath xpath = new AXIOMXPath(query); + for (String ns : prefixToNamespaceMapping.keySet()) { + xpath.addNamespace(prefixToNamespaceMapping.get(ns), ns); + } + List nodes = xpath.selectNodes(t); + for (Object el : nodes) { + if (el instanceof OMElement) { + values.add(metadataFieldMapping.toDCValue(field, ((OMElement) el).getText())); + } else if (el instanceof OMAttribute) { + values.add(metadataFieldMapping.toDCValue(field, ((OMAttribute) el).getAttributeValue())); + } else if (el instanceof String) { + values.add(metadataFieldMapping.toDCValue(field, (String) el)); + } else if (el instanceof OMText) { + values.add(metadataFieldMapping.toDCValue(field, ((OMText) el).getText())); + } else { + System.err.println("node of type: " + el.getClass()); + } + } + parseValue(values); + return values; + } catch (JaxenException e) { + System.err.println(query); + throw new RuntimeException(e); + } + } + + private void parseValue(List dtos) { + if (dtos != null) { + for (MetadatumDTO dto : dtos) { + if (dto != null && dto.getValue() != null) { + int startIndex = dto.getValue().lastIndexOf('/'); + int endIndex = dto.getValue().length(); + String id = dto.getValue().substring(startIndex + 1, endIndex); + dto.setValue(id); + } + } + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 3ef9a2999f..429a54a013 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -31,78 +31,185 @@ import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.jaxen.JaxenException; +/** + * Implements a data source for querying ArXiv + * + * @author Pasquale Cavallo (pasquale.cavallo at 4Science dot it) + * + */ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService { private WebTarget webTarget; + private String baseAddress; + /** + * Find the number of records matching a string query. Supports pagination + * + * @param query a query string to base the search on. + * @param start offset to start at + * @param count number of records to retrieve. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public Collection getRecords(String query, int start, int count) throws MetadataSourceException { return retry(new SearchByQueryCallable(query, count, start)); } + /** + * Find records based on a object query. + * + * @param query a query object to base the search on. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public Collection getRecords(Query query) throws MetadataSourceException { return retry(new SearchByQueryCallable(query)); } + /** + * Find the number of records matching a query; + * + * @param query a query object to base the search on. + * @return the sum of the matching records over this import source + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public int getNbRecords(String query) throws MetadataSourceException { - List records = retry(new SearchByQueryCallable(query, null, null)); - return records != null ? records.size() : 0; + return retry(new CountByQueryCallable(query)); } + + /** + * Find the number of records matching a query; + * + * @param query a query string to base the search on. + * @return the sum of the matching records over this import source + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public int getNbRecords(Query query) throws MetadataSourceException { - List records = retry(new SearchByQueryCallable(query)); - return records != null ? records.size() : 0; + return retry(new CountByQueryCallable(query)); } + /** + * Get a single record from the source by id + * + * @param id id of the record in ArXiv + * @return the first matching record + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public ImportRecord getRecord(String id) throws MetadataSourceException { List records = retry(new SearchByIdCallable(id)); - if (records != null && records.size() > 1) { - throw new MetadataSourceException("More than one result found"); - } - return records == null ? null : records.get(0); + return records == null || records.isEmpty() ? null : records.get(0); } + /** + * Get a single record from the source. + * + * @param query a query matching a single record + * @return the first matching record + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public ImportRecord getRecord(Query query) throws MetadataSourceException { List records = retry(new SearchByIdCallable(query)); - if (records != null && records.size() > 1) { - throw new MetadataSourceException("More than one result found"); - } - return records == null ? null : records.get(0); + return records == null || records.isEmpty() ? null : records.get(0); } - + /** + * Initialize the class + * + * @throws Exception on generic exception + */ @Override public void init() throws Exception { Client client = ClientBuilder.newClient(); - webTarget = client.target("http://export.arxiv.org/api/query"); + webTarget = client.target(baseAddress); } - - - - - + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ @Override public String getImportSource() { return "arxiv"; } + /** + * NOT IMPLEMENTED: Finds records based on an item + * + * @param item an item to base the search on + * @return a collection of import records. Only the identifier of the found records may be put in the record. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { throw new RuntimeException(); } + /** + * Finds records based on query object. + * Supports search by title and/or author + * + * @param query a query object to base the search on. + * @return a collection of import records. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ @Override public Collection findMatchingRecords(Query query) throws MetadataSourceException { return retry(new FindMatchingRecordCallable(query)); } + private class CountByQueryCallable implements Callable { + private Query query; + + + private CountByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private CountByQueryCallable(Query query) { + this.query = query; + } + + + @Override + public Integer call() throws Exception { + String queryString = query.getParameterAsClass("query", String.class); + Integer start = query.getParameterAsClass("start", Integer.class); + Integer maxResult = query.getParameterAsClass("count", Integer.class); + WebTarget local = webTarget.queryParam("search_query", queryString); + if (maxResult != null) { + local = local.queryParam("max_results", String.valueOf(maxResult)); + } + if (start != null) { + local = local.queryParam("start", String.valueOf(start)); + } + Invocation.Builder invocationBuilder = local.request(MediaType.TEXT_PLAIN_TYPE); + Response response = invocationBuilder.get(); + String responseString = response.readEntity(String.class); + OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(responseString)); + OMElement element = records.getDocumentElement(); + AXIOMXPath xpath = null; + try { + xpath = new AXIOMXPath("opensearch:totalResults"); + xpath.addNamespace("opensearch", "http://a9.com/-/spec/opensearch/1.1/"); + OMElement count = (OMElement) xpath.selectSingleNode(element); + return Integer.parseInt(count.getText()); + } catch (JaxenException e) { + return null; + } + } + } + + private class SearchByQueryCallable implements Callable> { private Query query; @@ -220,7 +327,7 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata } } - private static List splitToRecords(String recordsSrc) { + private List splitToRecords(String recordsSrc) { OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(recordsSrc)); OMElement element = records.getDocumentElement(); AXIOMXPath xpath = null; @@ -234,4 +341,22 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata } } + /** + * Return the baseAddress set to this object + * + * @return The String object that represents the baseAddress of this object + */ + public String getBaseAddress() { + return baseAddress; + } + + /** + * Set the baseAddress to this object + * + * @param baseAddress The String object that represents the baseAddress of this object + */ + public void setBaseAddress(String baseAddress) { + this.baseAddress = baseAddress; + } + } diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index a351280b98..d2d6dd10c1 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -37,7 +37,7 @@ class="org.dspace.importer.external.arxiv.service.ArXivImportMetadataSourceServiceImpl" scope="singleton"> - + diff --git a/dspace/config/spring/api/arxiv-integration.xml b/dspace/config/spring/api/arxiv-integration.xml index a22bfe9eeb..9b453e3fc9 100644 --- a/dspace/config/spring/api/arxiv-integration.xml +++ b/dspace/config/spring/api/arxiv-integration.xml @@ -17,106 +17,92 @@ only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over what metadatafield is generated. - - - - - - + - - + + + + + + - - - - - - - - - - - - + + - - - - - + + - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -129,7 +115,6 @@ - From f71866b5e300453aec669e24ea087204d93ff90c Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 2 Jul 2020 10:30:49 +0200 Subject: [PATCH 167/465] Add comment --- .../contributor/ArXivIdMetadataContributor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java index 018cde8ac7..ed38f955c8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java @@ -24,6 +24,13 @@ import org.dspace.importer.external.metadatamapping.contributor.MetadataContribu import org.jaxen.JaxenException; import org.springframework.beans.factory.annotation.Required; +/** + * Arxiv specific implementation of {@link MetadataContributor} + * Responsible for generating the ArXiv Id from the retrieved item. + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ public class ArXivIdMetadataContributor implements MetadataContributor { private MetadataFieldConfig field; From 03ac31dc109d733917c94f1bf1cb8d26a6f14bcf Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 2 Jul 2020 12:05:50 +0200 Subject: [PATCH 168/465] Exception handling --- .../transform/GenerateArXivQueryService.java | 53 ------------------- .../ArXivImportMetadataSourceServiceImpl.java | 3 +- .../DSpaceApiExceptionControllerAdvice.java | 9 ++++ .../config/spring/api/arxiv-integration.xml | 9 ---- 4 files changed, 11 insertions(+), 63 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java deleted file mode 100644 index 7f5e08cb5a..0000000000 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/transform/GenerateArXivQueryService.java +++ /dev/null @@ -1,53 +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.importer.external.arxiv.metadatamapping.transform; - -import java.util.List; - -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.importer.external.datamodel.Query; -import org.dspace.importer.external.exception.MetadataSourceException; -import org.dspace.importer.external.metadatamapping.transform.GenerateQueryService; - -public class GenerateArXivQueryService implements GenerateQueryService { - - /** - * Create a Query object based on a given item. - * If the item has at least 1 value for dc.identifier.doi, the first one will be used. - * If no DOI is found, the title will be used. - * When no DOI or title is found, an null object is returned instead. - * - * @param item the Item to create a Query from - */ - @Override - public Query generateQueryForItem(Item item) throws MetadataSourceException { - Query query = new Query(); - - // Retrieve an instance of the ItemService to access business calls on an item. - ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - List doi = itemService.getMetadata(item, "dc", "identifier", "doi", Item.ANY); - - if (doi.size() > 0) { - query.addParameter("term", doi.get(0).getValue()); - query.addParameter("field", "ELocationID"); - return query; - } - - List title = itemService.getMetadata(item, "dc", "title", null, Item.ANY); - - if (title.size() > 0) { - query.addParameter("term", title.get(0).getValue()); - query.addParameter("field", "title"); - return query; - } - return null; - } -} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 429a54a013..2995466c3c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -27,6 +27,7 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.ExternalProviderMethodNotImplementedException; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.jaxen.JaxenException; @@ -150,7 +151,7 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata */ @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new RuntimeException(); + throw new ExternalProviderMethodNotImplementedException("This method is not implemented for ArXiv"); } /** 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 d255b6fe27..07cc31420e 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 @@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletResponse; import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.authorize.AuthorizeException; +import org.dspace.importer.external.exception.ExternalProviderMethodNotImplementedException; import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; @@ -38,6 +39,8 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep * @author Tom Desair (tom dot desair at atmire dot com) * @author Frederic Van Reet (frederic dot vanreet at atmire dot com) * @author Andrea Bollini (andrea.bollini at 4science.it) + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * */ @ControllerAdvice public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { @@ -45,6 +48,12 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH @Autowired private RestAuthenticationService restAuthenticationService; + @ExceptionHandler(ExternalProviderMethodNotImplementedException.class) + protected void externalResourceMethodNotImplementedException(HttpServletRequest request, + HttpServletResponse response, ExternalProviderMethodNotImplementedException ex) throws IOException { + sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_NOT_IMPLEMENTED); + } + @ExceptionHandler({AuthorizeException.class, RESTAuthorizationException.class, AccessDeniedException.class}) protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { diff --git a/dspace/config/spring/api/arxiv-integration.xml b/dspace/config/spring/api/arxiv-integration.xml index 9b453e3fc9..e963e73a20 100644 --- a/dspace/config/spring/api/arxiv-integration.xml +++ b/dspace/config/spring/api/arxiv-integration.xml @@ -101,9 +101,6 @@ - - - @@ -119,10 +116,4 @@ - - Defines how an org.dspace.content.Item is mapped to a query in scopus. Please note that exactly one of - these must be present. If multiple are present the result is undefined. - - - From 02953eb087c931dba77fd84d3e6ba788464b4458 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 2 Jul 2020 14:45:04 +0200 Subject: [PATCH 169/465] Update comment and minor fix --- .../metadatamapping/ArXivFieldMapping.java | 14 +++++++ .../ArXivIdMetadataContributor.java | 6 +-- ...ProviderMethodNotImplementedException.java | 40 +++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java index c4f6996a27..272b149015 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java @@ -12,8 +12,22 @@ import javax.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; +/** + * An implementation of {@link AbstractMetadataFieldMapping} + * Responsible for defining the mapping of the ArXiv metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ public class ArXivFieldMapping extends AbstractMetadataFieldMapping { + /** + * Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + * only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + * what metadatafield is generated. + * + * @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to + * the item. + */ @Override @Resource(name = "arxivMetadataFieldMap") public void setMetadataFieldMap(Map metadataFieldMap) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java index ed38f955c8..727a3fcf4e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java @@ -174,10 +174,10 @@ public class ArXivIdMetadataContributor implements MetadataContributor dtos) { if (dtos != null) { for (MetadatumDTO dto : dtos) { - if (dto != null && dto.getValue() != null) { - int startIndex = dto.getValue().lastIndexOf('/'); + if (dto != null && dto.getValue() != null && dto.getValue().contains("/")) { + int startIndex = dto.getValue().lastIndexOf('/') + 1; int endIndex = dto.getValue().length(); - String id = dto.getValue().substring(startIndex + 1, endIndex); + String id = dto.getValue().substring(startIndex, endIndex); dto.setValue(id); } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java b/dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java new file mode 100644 index 0000000000..e8df7a3861 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java @@ -0,0 +1,40 @@ +/** + * 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.importer.external.exception; + +/** + * Exception used when an External Provider don't implements the invoked method + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ +public class ExternalProviderMethodNotImplementedException extends RuntimeException { + + private static final long serialVersionUID = 5268699485635863003L; + + public ExternalProviderMethodNotImplementedException() { + super(); + } + + public ExternalProviderMethodNotImplementedException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public ExternalProviderMethodNotImplementedException(String message, Throwable cause) { + super(message, cause); + } + + public ExternalProviderMethodNotImplementedException(String message) { + super(message); + } + + public ExternalProviderMethodNotImplementedException(Throwable cause) { + super(cause); + } +} From fec04568ffcf761777702144ba47007293b39fb0 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 2 Jul 2020 15:48:00 +0200 Subject: [PATCH 170/465] 71629: checkstyle & removed unused annotations --- .../java/org/dspace/curate/CuratorTest.java | 18 +++--------------- .../org/dspace/curate/CurationScriptIT.java | 7 ++++--- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java index ddfa751322..4890fef26f 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java +++ b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java @@ -23,27 +23,12 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.ctask.general.NoOpCurationTask; 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.Spy; -import org.mockito.junit.MockitoJUnitRunner; /** * @author mhwood */ -@RunWith(MockitoJUnitRunner.class) public class CuratorTest extends AbstractUnitTest { - @InjectMocks - private Curator curator; - - @Mock - NoOpCurationTask noOpCurationTask; - - @Spy - TaskResolver taskResolver; - private static final SiteService SITE_SERVICE = ContentServiceFactory.getInstance().getSiteService(); static final String RUN_PARAMETER_NAME = "runParameter"; @@ -116,6 +101,9 @@ public class CuratorTest extends AbstractUnitTest { cfg.setProperty("plugin.named.org.dspace.curate.CurationTask", NoOpCurationTask.class.getName() + " = " + TASK_NAME); + // Get and configure a Curator. + Curator curator = new Curator(); + StringBuilder reporterOutput = new StringBuilder(); curator.setReporter(reporterOutput); // Send any report to our StringBuilder. diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index ac5f149c48..4a8e907c5e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -7,10 +7,10 @@ */ package org.dspace.curate; -import static org.hamcrest.Matchers.containsStringIgnoringCase; -import static org.hamcrest.Matchers.is; import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.is; 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; @@ -42,6 +42,8 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** + * IT for {@link CurationCli} + * * @author Maria Verdonck (Atmire) on 24/06/2020 */ public class CurationScriptIT extends AbstractControllerIntegrationTest { @@ -436,5 +438,4 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { } } - } From dbde82478b052371bfc1b3780a622797b3ebf333 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Thu, 2 Jul 2020 16:22:36 +0200 Subject: [PATCH 171/465] remove unused code --- .../ArXivImportMetadataSourceServiceImpl.java | 11 +- ...ProviderMethodNotImplementedException.java | 40 --- .../submit/lookup/ArXivFileDataLoader.java | 146 -------- .../submit/lookup/ArXivOnlineDataLoader.java | 84 ----- .../dspace/submit/lookup/ArXivService.java | 159 --------- .../org/dspace/submit/lookup/ArxivUtils.java | 151 --------- .../submit/lookup/PubmedFileDataLoader.java | 148 -------- .../submit/lookup/PubmedOnlineDataLoader.java | 116 ------- .../dspace/submit/lookup/PubmedService.java | 265 --------------- .../org/dspace/submit/lookup/PubmedUtils.java | 316 ------------------ .../spring-dspace-addon-import-services.xml | 1 - .../DSpaceApiExceptionControllerAdvice.java | 7 - dspace/config/spring/api/bte.xml | 107 ------ .../spring/api/step-processing-listener.xml | 2 - 14 files changed, 4 insertions(+), 1549 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/ArXivFileDataLoader.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/ArXivOnlineDataLoader.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/ArxivUtils.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/PubmedFileDataLoader.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/PubmedOnlineDataLoader.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java delete mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/PubmedUtils.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 2995466c3c..863a8144b4 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; @@ -27,7 +28,6 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; -import org.dspace.importer.external.exception.ExternalProviderMethodNotImplementedException; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.jaxen.JaxenException; @@ -143,15 +143,12 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata } /** - * NOT IMPLEMENTED: Finds records based on an item - * - * @param item an item to base the search on - * @return a collection of import records. Only the identifier of the found records may be put in the record. - * @throws MetadataSourceException if the underlying methods throw any exception. + * Expect this method will be not used and erased from the interface soon */ @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new ExternalProviderMethodNotImplementedException("This method is not implemented for ArXiv"); + // FIXME: we need this method? + throw new MethodNotFoundException("This method is not implemented for ArXiv"); } /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java b/dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java deleted file mode 100644 index e8df7a3861..0000000000 --- a/dspace-api/src/main/java/org/dspace/importer/external/exception/ExternalProviderMethodNotImplementedException.java +++ /dev/null @@ -1,40 +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.importer.external.exception; - -/** - * Exception used when an External Provider don't implements the invoked method - * - * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) - * - */ -public class ExternalProviderMethodNotImplementedException extends RuntimeException { - - private static final long serialVersionUID = 5268699485635863003L; - - public ExternalProviderMethodNotImplementedException() { - super(); - } - - public ExternalProviderMethodNotImplementedException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - - public ExternalProviderMethodNotImplementedException(String message, Throwable cause) { - super(message, cause); - } - - public ExternalProviderMethodNotImplementedException(String message) { - super(message); - } - - public ExternalProviderMethodNotImplementedException(Throwable cause) { - super(cause); - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivFileDataLoader.java b/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivFileDataLoader.java deleted file mode 100644 index ebc898e4cf..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivFileDataLoader.java +++ /dev/null @@ -1,146 +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.submit.lookup; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import gr.ekt.bte.core.DataLoadingSpec; -import gr.ekt.bte.core.Record; -import gr.ekt.bte.core.RecordSet; -import gr.ekt.bte.core.Value; -import gr.ekt.bte.dataloader.FileDataLoader; -import gr.ekt.bte.exceptions.MalformedSourceException; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.app.util.XMLUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class ArXivFileDataLoader extends FileDataLoader { - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ArXivFileDataLoader.class); - - Map fieldMap; // mapping between service fields and local - // intermediate fields - - /** - * Empty constructor - */ - public ArXivFileDataLoader() { - } - - /** - * @param filename Name of file to load ArXiv data from. - */ - public ArXivFileDataLoader(String filename) { - super(filename); - } - - /* - * {@see gr.ekt.bte.core.DataLoader#getRecords()} - * - * @throws MalformedSourceException - */ - @Override - public RecordSet getRecords() throws MalformedSourceException { - - RecordSet recordSet = new RecordSet(); - - try { - InputStream inputStream = new FileInputStream(new File(filename)); - - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder db = factory.newDocumentBuilder(); - Document inDoc = db.parse(inputStream); - - Element xmlRoot = inDoc.getDocumentElement(); - List dataRoots = XMLUtils.getElementList(xmlRoot, "entry"); - - for (Element dataRoot : dataRoots) { - Record record = ArxivUtils.convertArxixDomToRecord(dataRoot); - if (record != null) { - recordSet.addRecord(convertFields(record)); - } - } - } catch (FileNotFoundException e) { - log.error(e.getMessage(), e); - } catch (ParserConfigurationException e) { - log.error(e.getMessage(), e); - } catch (SAXException e) { - log.error(e.getMessage(), e); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - - return recordSet; - } - - /* - * (non-Javadoc) - * - * @see - * gr.ekt.bte.core.DataLoader#getRecords(gr.ekt.bte.core.DataLoadingSpec) - */ - @Override - public RecordSet getRecords(DataLoadingSpec spec) - throws MalformedSourceException { - if (spec.getOffset() > 0) { - return new RecordSet(); - } - return getRecords(); - } - - public Record convertFields(Record publication) { - for (String fieldName : fieldMap.keySet()) { - String md = null; - if (fieldMap != null) { - md = this.fieldMap.get(fieldName); - } - - if (StringUtils.isBlank(md)) { - continue; - } else { - md = md.trim(); - } - - if (publication.isMutable()) { - List values = publication.getValues(fieldName); - publication.makeMutable().removeField(fieldName); - publication.makeMutable().addField(md, values); - } - } - - return publication; - } - - public void setFieldMap(Map fieldMap) { - this.fieldMap = fieldMap; - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivOnlineDataLoader.java b/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivOnlineDataLoader.java deleted file mode 100644 index e477412621..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivOnlineDataLoader.java +++ /dev/null @@ -1,84 +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.submit.lookup; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import gr.ekt.bte.core.Record; -import org.apache.http.HttpException; -import org.dspace.core.Context; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class ArXivOnlineDataLoader extends NetworkSubmissionLookupDataLoader { - protected ArXivService arXivService = new ArXivService(); - - protected boolean searchProvider = true; - - public void setArXivService(ArXivService arXivService) { - this.arXivService = arXivService; - } - - @Override - public List getSupportedIdentifiers() { - return Arrays.asList(new String[] {ARXIV, DOI}); - } - - public void setSearchProvider(boolean searchProvider) { - this.searchProvider = searchProvider; - } - - @Override - public boolean isSearchProvider() { - return searchProvider; - } - - @Override - public List getByIdentifier(Context context, - Map> keys) throws HttpException, IOException { - List results = new ArrayList(); - if (keys != null) { - Set dois = keys.get(DOI); - Set arxivids = keys.get(ARXIV); - List items = new ArrayList(); - if (dois != null && dois.size() > 0) { - items.addAll(arXivService.getByDOIs(dois)); - } - if (arxivids != null && arxivids.size() > 0) { - for (String arxivid : arxivids) { - items.add(arXivService.getByArXivIDs(arxivid)); - } - } - - for (Record item : items) { - results.add(convertFields(item)); - } - } - return results; - } - - @Override - public List search(Context context, String title, String author, - int year) throws HttpException, IOException { - List results = new ArrayList(); - List items = arXivService.searchByTerm(title, author, year); - for (Record item : items) { - results.add(convertFields(item)); - } - return results; - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java b/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java deleted file mode 100644 index 0a32871758..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java +++ /dev/null @@ -1,159 +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.submit.lookup; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import gr.ekt.bte.core.Record; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.HttpParams; -import org.dspace.app.util.XMLUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class ArXivService { - private int timeout = 1000; - - /** - * How long to wait for a connection to be established. - * - * @param timeout milliseconds - */ - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public List getByDOIs(Set dois) throws HttpException, - IOException { - if (dois != null && dois.size() > 0) { - String doisQuery = StringUtils.join(dois.iterator(), " OR "); - return search(doisQuery, null, 100); - } - return null; - } - - public List searchByTerm(String title, String author, int year) - throws HttpException, IOException { - StringBuffer query = new StringBuffer(); - if (StringUtils.isNotBlank(title)) { - query.append("ti:\"").append(title).append("\""); - } - if (StringUtils.isNotBlank(author)) { - // [FAU] - if (query.length() > 0) { - query.append(" AND "); - } - query.append("au:\"").append(author).append("\""); - } - return search(query.toString(), "", 10); - } - - protected List search(String query, String arxivid, int max_result) - throws IOException, HttpException { - List results = new ArrayList(); - HttpGet method = null; - try { - HttpClient client = new DefaultHttpClient(); - HttpParams params = client.getParams(); - params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); - - try { - URIBuilder uriBuilder = new URIBuilder("http://export.arxiv.org/api/query"); - uriBuilder.addParameter("id_list", arxivid); - uriBuilder.addParameter("search_query", query); - uriBuilder.addParameter("max_results", String.valueOf(max_result)); - method = new HttpGet(uriBuilder.build()); - } catch (URISyntaxException ex) { - throw new HttpException(ex.getMessage()); - } - - // Execute the method. - HttpResponse response = client.execute(method); - StatusLine responseStatus = response.getStatusLine(); - int statusCode = responseStatus.getStatusCode(); - - if (statusCode != HttpStatus.SC_OK) { - if (statusCode == HttpStatus.SC_BAD_REQUEST) { - throw new RuntimeException("arXiv query is not valid"); - } else { - throw new RuntimeException("Http call failed: " - + responseStatus); - } - } - - try { - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder db = factory.newDocumentBuilder(); - Document inDoc = db.parse(response.getEntity().getContent()); - - Element xmlRoot = inDoc.getDocumentElement(); - List dataRoots = XMLUtils.getElementList(xmlRoot, - "entry"); - - for (Element dataRoot : dataRoots) { - Record crossitem = ArxivUtils - .convertArxixDomToRecord(dataRoot); - if (crossitem != null) { - results.add(crossitem); - } - } - } catch (Exception e) { - throw new RuntimeException( - "ArXiv identifier is not valid or not exist"); - } - } finally { - if (method != null) { - method.releaseConnection(); - } - } - - return results; - } - - public Record getByArXivIDs(String raw) throws HttpException, IOException { - if (StringUtils.isNotBlank(raw)) { - raw = raw.trim(); - if (raw.startsWith("http://arxiv.org/abs/")) { - raw = raw.substring("http://arxiv.org/abs/".length()); - } else if (raw.toLowerCase().startsWith("arxiv:")) { - raw = raw.substring("arxiv:".length()); - } - List result = search("", raw, 1); - if (result != null && result.size() > 0) { - return result.get(0); - } - } - return null; - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/ArxivUtils.java b/dspace-api/src/main/java/org/dspace/submit/lookup/ArxivUtils.java deleted file mode 100644 index 4caa0a957b..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/ArxivUtils.java +++ /dev/null @@ -1,151 +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.submit.lookup; - -import java.util.LinkedList; -import java.util.List; - -import gr.ekt.bte.core.MutableRecord; -import gr.ekt.bte.core.Record; -import gr.ekt.bte.core.StringValue; -import gr.ekt.bte.core.Value; -import org.dspace.app.util.XMLUtils; -import org.dspace.submit.util.SubmissionLookupPublication; -import org.w3c.dom.Element; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class ArxivUtils { - - /** - * Default constructor - */ - private ArxivUtils() { } - - public static Record convertArxixDomToRecord(Element dataRoot) { - MutableRecord record = new SubmissionLookupPublication(""); - - String articleTitle = XMLUtils.getElementValue(dataRoot, "title"); - if (articleTitle != null) { - record.addValue("title", new StringValue(articleTitle)); - } - String summary = XMLUtils.getElementValue(dataRoot, "summary"); - if (summary != null) { - record.addValue("summary", new StringValue(summary)); - } - String year = XMLUtils.getElementValue(dataRoot, "published"); - if (year != null) { - record.addValue("published", new StringValue(year)); - } - String splashPageUrl = XMLUtils.getElementValue(dataRoot, "id"); - if (splashPageUrl != null) { - record.addValue("id", new StringValue(splashPageUrl)); - } - String comment = XMLUtils.getElementValue(dataRoot, "arxiv:comment"); - if (comment != null) { - record.addValue("comment", new StringValue(comment)); - } - - List links = XMLUtils.getElementList(dataRoot, "link"); - if (links != null) { - for (Element link : links) { - if ("related".equals(link.getAttribute("rel")) - && "pdf".equals(link.getAttribute("title"))) { - String pdfUrl = link.getAttribute("href"); - if (pdfUrl != null) { - record.addValue("pdfUrl", new StringValue(pdfUrl)); - } - } - } - } - - String doi = XMLUtils.getElementValue(dataRoot, "arxiv:doi"); - if (doi != null) { - record.addValue("doi", new StringValue(doi)); - } - String journalRef = XMLUtils.getElementValue(dataRoot, - "arxiv:journal_ref"); - if (journalRef != null) { - record.addValue("journalRef", new StringValue(journalRef)); - } - - List primaryCategory = new LinkedList(); - List primaryCategoryList = XMLUtils.getElementList(dataRoot, - "arxiv:primary_category"); - if (primaryCategoryList != null) { - for (Element primaryCategoryElement : primaryCategoryList) { - primaryCategory - .add(primaryCategoryElement.getAttribute("term")); - } - } - - if (primaryCategory.size() > 0) { - List values = new LinkedList(); - for (String s : primaryCategory) { - values.add(new StringValue(s)); - } - record.addField("primaryCategory", values); - } - - List category = new LinkedList(); - List categoryList = XMLUtils.getElementList(dataRoot, - "category"); - if (categoryList != null) { - for (Element categoryElement : categoryList) { - category.add(categoryElement.getAttribute("term")); - } - } - - if (category.size() > 0) { - List values = new LinkedList(); - for (String s : category) { - values.add(new StringValue(s)); - } - record.addField("category", values); - } - - List authors = new LinkedList(); - List authorsWithAffiliations = new LinkedList(); - List authorList = XMLUtils.getElementList(dataRoot, "author"); - if (authorList != null) { - for (Element authorElement : authorList) { - String authorName = XMLUtils.getElementValue(authorElement, "name"); - String authorAffiliation = XMLUtils.getElementValue(authorElement, "arxiv:affiliation"); - - authors.add(authorName); - authorsWithAffiliations.add(authorName + ": " + authorAffiliation); - } - } - - if (authors.size() > 0) { - List values = new LinkedList(); - for (String sArray : authors) { - values.add(new StringValue(sArray)); - } - record.addField("author", values); - } - - if (authorsWithAffiliations.size() > 0) { - List values = new LinkedList(); - for (String sArray : authorsWithAffiliations) { - values.add(new StringValue(sArray)); - } - record.addField("authorWithAffiliation", values); - } - - return record; - } - -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedFileDataLoader.java b/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedFileDataLoader.java deleted file mode 100644 index 05a37e64d6..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedFileDataLoader.java +++ /dev/null @@ -1,148 +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.submit.lookup; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import gr.ekt.bte.core.DataLoadingSpec; -import gr.ekt.bte.core.Record; -import gr.ekt.bte.core.RecordSet; -import gr.ekt.bte.core.Value; -import gr.ekt.bte.dataloader.FileDataLoader; -import gr.ekt.bte.exceptions.MalformedSourceException; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.util.XMLUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class PubmedFileDataLoader extends FileDataLoader { - - Map fieldMap; // mapping between service fields and local - // intermediate fields - - /** - * - */ - public PubmedFileDataLoader() { - } - - /** - * @param filename Name of file to load CiNii data from. - */ - public PubmedFileDataLoader(String filename) { - super(filename); - } - - /* - * {@see gr.ekt.bte.core.DataLoader#getRecords()} - * - * @throws MalformedSourceException - */ - @Override - public RecordSet getRecords() throws MalformedSourceException { - - RecordSet recordSet = new RecordSet(); - - try { - InputStream inputStream = new FileInputStream(new File(filename)); - - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder builder = factory.newDocumentBuilder(); - Document inDoc = builder.parse(inputStream); - - Element xmlRoot = inDoc.getDocumentElement(); - List pubArticles = XMLUtils.getElementList(xmlRoot, - "PubmedArticle"); - - for (Element xmlArticle : pubArticles) { - Record record = null; - try { - record = PubmedUtils.convertPubmedDomToRecord(xmlArticle); - recordSet.addRecord(convertFields(record)); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - return recordSet; - - } - - /* - * (non-Javadoc) - * - * @see - * gr.ekt.bte.core.DataLoader#getRecords(gr.ekt.bte.core.DataLoadingSpec) - */ - @Override - public RecordSet getRecords(DataLoadingSpec spec) - throws MalformedSourceException { - if (spec.getOffset() > 0) { - return new RecordSet(); - } - return getRecords(); - } - - public Record convertFields(Record publication) { - for (String fieldName : fieldMap.keySet()) { - String md = null; - if (fieldMap != null) { - md = this.fieldMap.get(fieldName); - } - - if (StringUtils.isBlank(md)) { - continue; - } else { - md = md.trim(); - } - - if (publication.isMutable()) { - List values = publication.getValues(fieldName); - publication.makeMutable().removeField(fieldName); - publication.makeMutable().addField(md, values); - } - } - - return publication; - } - - public void setFieldMap(Map fieldMap) { - this.fieldMap = fieldMap; - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedOnlineDataLoader.java b/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedOnlineDataLoader.java deleted file mode 100644 index 094ce4e21d..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedOnlineDataLoader.java +++ /dev/null @@ -1,116 +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.submit.lookup; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import gr.ekt.bte.core.Record; -import org.apache.http.HttpException; -import org.apache.logging.log4j.Logger; -import org.dspace.core.Context; -import org.dspace.core.LogManager; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class PubmedOnlineDataLoader extends NetworkSubmissionLookupDataLoader { - protected boolean searchProvider = true; - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(PubmedOnlineDataLoader.class); - - protected PubmedService pubmedService = new PubmedService(); - - public void setPubmedService(PubmedService pubmedService) { - this.pubmedService = pubmedService; - } - - @Override - public List getSupportedIdentifiers() { - return Arrays.asList(new String[] {PUBMED, DOI}); - } - - public void setSearchProvider(boolean searchProvider) { - this.searchProvider = searchProvider; - } - - @Override - public boolean isSearchProvider() { - return searchProvider; - } - - @Override - public List getByIdentifier(Context context, - Map> keys) throws HttpException, IOException { - Set pmids = keys != null ? keys.get(PUBMED) : null; - Set dois = keys != null ? keys.get(DOI) : null; - List results = new ArrayList(); - if (pmids != null && pmids.size() > 0 - && (dois == null || dois.size() == 0)) { - for (String pmid : pmids) { - Record p = null; - try { - p = pubmedService.getByPubmedID(pmid); - } catch (Exception e) { - log.error(LogManager.getHeader(context, "getByIdentifier", - "pmid=" + pmid), e); - } - if (p != null) { - results.add(convertFields(p)); - } - } - } else if (dois != null && dois.size() > 0 - && (pmids == null || pmids.size() == 0)) { - StringBuffer query = new StringBuffer(); - for (String d : dois) { - if (query.length() > 0) { - query.append(" OR "); - } - query.append(d).append("[AI]"); - } - - List pubmedResults = pubmedService.search(query.toString()); - for (Record p : pubmedResults) { - results.add(convertFields(p)); - } - } else if (dois != null && dois.size() > 0 && pmids != null - && pmids.size() > 0) { - // EKT:ToDo: support list of dois and pmids in the search method of - // pubmedService - List pubmedResults = pubmedService.search(dois.iterator() - .next(), pmids.iterator().next()); - if (pubmedResults != null) { - for (Record p : pubmedResults) { - results.add(convertFields(p)); - } - } - } - - return results; - } - - @Override - public List search(Context context, String title, String author, - int year) throws HttpException, IOException { - List pubmedResults = pubmedService.search(title, author, year); - List results = new ArrayList(); - if (pubmedResults != null) { - for (Record p : pubmedResults) { - results.add(convertFields(p)); - } - } - return results; - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java b/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java deleted file mode 100644 index fa30ee8ea5..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java +++ /dev/null @@ -1,265 +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.submit.lookup; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import gr.ekt.bte.core.Record; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.logging.log4j.Logger; -import org.dspace.app.util.XMLUtils; -import org.dspace.core.ConfigurationManager; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class PubmedService { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(PubmedService.class); - - protected int timeout = 1000; - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public Record getByPubmedID(String pubmedid) throws HttpException, - IOException, ParserConfigurationException, SAXException { - List ids = new ArrayList(); - ids.add(pubmedid.trim()); - List items = getByPubmedIDs(ids); - if (items != null && items.size() > 0) { - return items.get(0); - } - return null; - } - - public List search(String title, String author, int year) - throws HttpException, IOException { - StringBuffer query = new StringBuffer(); - if (StringUtils.isNotBlank(title)) { - query.append("((").append(title).append("[TI]) OR ("); - // [TI] does not always work, book chapter title - query.append("(").append(title).append("[book]))"); - } - if (StringUtils.isNotBlank(author)) { - // [FAU] - if (query.length() > 0) { - query.append(" AND "); - } - query.append("(").append(author).append("[AU])"); - } - if (year != -1) { - // [DP] - if (query.length() > 0) { - query.append(" AND "); - } - query.append(year).append("[DP]"); - } - return search(query.toString()); - } - - public List search(String query) throws IOException, HttpException { - List results = new ArrayList<>(); - if (!ConfigurationManager.getBooleanProperty(SubmissionLookupService.CFG_MODULE, "remoteservice.demo")) { - HttpGet method = null; - try { - HttpClient client = new DefaultHttpClient(); - client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); - - URIBuilder uriBuilder = new URIBuilder( - "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"); - uriBuilder.addParameter("db", "pubmed"); - uriBuilder.addParameter("datetype", "edat"); - uriBuilder.addParameter("retmax", "10"); - uriBuilder.addParameter("term", query); - method = new HttpGet(uriBuilder.build()); - - // Execute the method. - HttpResponse response = client.execute(method); - StatusLine statusLine = response.getStatusLine(); - int statusCode = statusLine.getStatusCode(); - - if (statusCode != HttpStatus.SC_OK) { - throw new RuntimeException("WS call failed: " - + statusLine); - } - - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder builder; - try { - builder = factory.newDocumentBuilder(); - - Document inDoc = builder.parse(response.getEntity().getContent()); - - Element xmlRoot = inDoc.getDocumentElement(); - Element idList = XMLUtils.getSingleElement(xmlRoot, - "IdList"); - List pubmedIDs = XMLUtils.getElementValueList( - idList, "Id"); - results = getByPubmedIDs(pubmedIDs); - } catch (ParserConfigurationException e1) { - log.error(e1.getMessage(), e1); - } catch (SAXException e1) { - log.error(e1.getMessage(), e1); - } - } catch (Exception e1) { - log.error(e1.getMessage(), e1); - } finally { - if (method != null) { - method.releaseConnection(); - } - } - } else { - InputStream stream = null; - try { - File file = new File( - ConfigurationManager.getProperty("dspace.dir") - + "/config/crosswalks/demo/pubmed-search.xml"); - stream = new FileInputStream(file); - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder builder = factory.newDocumentBuilder(); - Document inDoc = builder.parse(stream); - - Element xmlRoot = inDoc.getDocumentElement(); - Element idList = XMLUtils.getSingleElement(xmlRoot, "IdList"); - List pubmedIDs = XMLUtils.getElementValueList(idList, - "Id"); - results = getByPubmedIDs(pubmedIDs); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - return results; - } - - public List getByPubmedIDs(List pubmedIDs) - throws HttpException, IOException, ParserConfigurationException, - SAXException { - List results = new ArrayList(); - HttpGet method = null; - try { - HttpClient client = new DefaultHttpClient(); - client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5 * timeout); - - try { - URIBuilder uriBuilder = new URIBuilder( - "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"); - uriBuilder.addParameter("db", "pubmed"); - uriBuilder.addParameter("retmode", "xml"); - uriBuilder.addParameter("rettype", "full"); - uriBuilder.addParameter("id", StringUtils.join( - pubmedIDs.iterator(), ",")); - method = new HttpGet(uriBuilder.build()); - } catch (URISyntaxException ex) { - throw new RuntimeException("Request not sent", ex); - } - - // Execute the method. - HttpResponse response = client.execute(method); - StatusLine statusLine = response.getStatusLine(); - int statusCode = statusLine.getStatusCode(); - - if (statusCode != HttpStatus.SC_OK) { - throw new RuntimeException("WS call failed: " + statusLine); - } - - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder builder = factory.newDocumentBuilder(); - Document inDoc = builder - .parse(response.getEntity().getContent()); - - Element xmlRoot = inDoc.getDocumentElement(); - List pubArticles = XMLUtils.getElementList(xmlRoot, - "PubmedArticle"); - - for (Element xmlArticle : pubArticles) { - Record pubmedItem = null; - try { - pubmedItem = PubmedUtils - .convertPubmedDomToRecord(xmlArticle); - results.add(pubmedItem); - } catch (Exception e) { - throw new RuntimeException( - "PubmedID is not valid or not exist: " - + e.getMessage(), e); - } - } - - return results; - } finally { - if (method != null) { - method.releaseConnection(); - } - } - } - - public List search(String doi, String pmid) throws HttpException, - IOException { - StringBuffer query = new StringBuffer(); - if (StringUtils.isNotBlank(doi)) { - query.append(doi); - query.append("[AID]"); - } - if (StringUtils.isNotBlank(pmid)) { - // [FAU] - if (query.length() > 0) { - query.append(" OR "); - } - query.append(pmid).append("[PMID]"); - } - return search(query.toString()); - } -} diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedUtils.java b/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedUtils.java deleted file mode 100644 index bca34de295..0000000000 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedUtils.java +++ /dev/null @@ -1,316 +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.submit.lookup; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import gr.ekt.bte.core.MutableRecord; -import gr.ekt.bte.core.Record; -import gr.ekt.bte.core.StringValue; -import gr.ekt.bte.core.Value; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.util.XMLUtils; -import org.dspace.submit.util.SubmissionLookupPublication; -import org.w3c.dom.Element; - -/** - * @author Andrea Bollini - * @author Kostas Stamatis - * @author Luigi Andrea Pascarelli - * @author Panagiotis Koutsourakis - */ -public class PubmedUtils { - - /** - * Default constructor - */ - private PubmedUtils() { } - - public static Record convertPubmedDomToRecord(Element pubArticle) { - MutableRecord record = new SubmissionLookupPublication(""); - - Map monthToNum = new HashMap(); - monthToNum.put("Jan", "01"); - monthToNum.put("Feb", "02"); - monthToNum.put("Mar", "03"); - monthToNum.put("Apr", "04"); - monthToNum.put("May", "05"); - monthToNum.put("Jun", "06"); - monthToNum.put("Jul", "07"); - monthToNum.put("Aug", "08"); - monthToNum.put("Sep", "09"); - monthToNum.put("Oct", "10"); - monthToNum.put("Nov", "11"); - monthToNum.put("Dec", "12"); - - Element medline = XMLUtils.getSingleElement(pubArticle, - "MedlineCitation"); - - Element article = XMLUtils.getSingleElement(medline, "Article"); - Element pubmed = XMLUtils.getSingleElement(pubArticle, "PubmedData"); - - Element identifierList = XMLUtils.getSingleElement(pubmed, - "ArticleIdList"); - if (identifierList != null) { - List identifiers = XMLUtils.getElementList(identifierList, - "ArticleId"); - if (identifiers != null) { - for (Element id : identifiers) { - if ("pubmed".equals(id.getAttribute("IdType"))) { - String pubmedID = id.getTextContent().trim(); - if (pubmedID != null) { - record.addValue("pubmedID", new StringValue( - pubmedID)); - } - } else if ("doi".equals(id.getAttribute("IdType"))) { - String doi = id.getTextContent().trim(); - if (doi != null) { - record.addValue("doi", new StringValue(doi)); - } - } - } - } - } - - String status = XMLUtils.getElementValue(pubmed, "PublicationStatus"); - if (status != null) { - record.addValue("publicationStatus", new StringValue(status)); - } - - String pubblicationModel = XMLUtils.getElementAttribute(medline, - "Article", "PubModel"); - if (pubblicationModel != null) { - record.addValue("pubModel", new StringValue( - pubblicationModel)); - } - - String title = XMLUtils.getElementValue(article, "ArticleTitle"); - if (title != null) { - record.addValue("articleTitle", new StringValue(title)); - } - - Element abstractElement = XMLUtils - .getSingleElement(article, "Abstract"); - if (abstractElement == null) { - abstractElement = XMLUtils.getSingleElement(medline, - "OtherAbstract"); - } - if (abstractElement != null) { - String summary = XMLUtils.getElementValue(abstractElement, - "AbstractText"); - if (summary != null) { - record.addValue("abstractText", new StringValue(summary)); - } - } - - List authors = new LinkedList(); - Element authorList = XMLUtils.getSingleElement(article, "AuthorList"); - if (authorList != null) { - List authorsElement = XMLUtils.getElementList(authorList, - "Author"); - if (authorsElement != null) { - for (Element author : authorsElement) { - if (StringUtils.isBlank(XMLUtils.getElementValue(author, - "CollectiveName"))) { - authors.add(new String[] { - XMLUtils.getElementValue(author, "ForeName"), - XMLUtils.getElementValue(author, "LastName")}); - } - } - } - } - if (authors.size() > 0) { - List values = new LinkedList(); - for (String[] sArray : authors) { - values.add(new StringValue(sArray[1] + ", " + sArray[0])); - } - record.addField("author", values); - } - - Element journal = XMLUtils.getSingleElement(article, "Journal"); - if (journal != null) { - List jnumbers = XMLUtils.getElementList(journal, "ISSN"); - if (jnumbers != null) { - for (Element jnumber : jnumbers) { - if ("Print".equals(jnumber.getAttribute("IssnType"))) { - String issn = jnumber.getTextContent().trim(); - if (issn != null) { - record.addValue("printISSN", new StringValue(issn)); - } - } else { - String eissn = jnumber.getTextContent().trim(); - if (eissn != null) { - record.addValue("electronicISSN", new StringValue(eissn)); - } - } - } - } - - String journalTitle = XMLUtils.getElementValue(journal, "Title"); - if (journalTitle != null) { - record.addValue("journalTitle", new StringValue(journalTitle)); - } - - Element journalIssueElement = XMLUtils.getSingleElement(journal, - "JournalIssue"); - if (journalIssueElement != null) { - String volume = XMLUtils.getElementValue(journalIssueElement, - "Volume"); - if (volume != null) { - record.addValue("journalVolume", new StringValue(volume)); - } - - String issue = XMLUtils.getElementValue(journalIssueElement, - "Issue"); - if (issue != null) { - record.addValue("journalIssue", new StringValue(issue)); - } - - Element pubDateElement = XMLUtils.getSingleElement( - journalIssueElement, "PubDate"); - - String pubDate = null; - if (pubDateElement != null) { - pubDate = XMLUtils.getElementValue(pubDateElement, "Year"); - - String mounth = XMLUtils.getElementValue(pubDateElement, - "Month"); - String day = XMLUtils - .getElementValue(pubDateElement, "Day"); - if (StringUtils.isNotBlank(mounth) - && monthToNum.containsKey(mounth)) { - pubDate += "-" + monthToNum.get(mounth); - if (StringUtils.isNotBlank(day)) { - pubDate += "-" + (day.length() == 1 ? "0" + day : day); - } - } - } - if (pubDate == null) { - pubDate = XMLUtils.getElementValue(pubDateElement, "MedlineDate"); - } - if (pubDate != null) { - record.addValue("pubDate", new StringValue(pubDate)); - } - } - - String language = XMLUtils.getElementValue(article, "Language"); - if (language != null) { - record.addValue("language", new StringValue(language)); - } - - List type = new LinkedList(); - Element publicationTypeList = XMLUtils.getSingleElement(article, - "PublicationTypeList"); - if (publicationTypeList != null) { - List publicationTypes = XMLUtils.getElementList( - publicationTypeList, "PublicationType"); - for (Element publicationType : publicationTypes) { - type.add(publicationType.getTextContent().trim()); - } - } - if (type.size() > 0) { - List values = new LinkedList(); - for (String s : type) { - values.add(new StringValue(s)); - } - record.addField("publicationType", values); - } - - List primaryKeywords = new LinkedList(); - List secondaryKeywords = new LinkedList(); - Element keywordsList = XMLUtils.getSingleElement(medline, - "KeywordList"); - if (keywordsList != null) { - List keywords = XMLUtils.getElementList(keywordsList, - "Keyword"); - for (Element keyword : keywords) { - if ("Y".equals(keyword.getAttribute("MajorTopicYN"))) { - primaryKeywords.add(keyword.getTextContent().trim()); - } else { - secondaryKeywords.add(keyword.getTextContent().trim()); - } - } - } - if (primaryKeywords.size() > 0) { - List values = new LinkedList(); - for (String s : primaryKeywords) { - values.add(new StringValue(s)); - } - record.addField("primaryKeyword", values); - } - if (secondaryKeywords.size() > 0) { - List values = new LinkedList(); - for (String s : secondaryKeywords) { - values.add(new StringValue(s)); - } - record.addField("secondaryKeyword", values); - } - - List primaryMeshHeadings = new LinkedList(); - List secondaryMeshHeadings = new LinkedList(); - Element meshHeadingsList = XMLUtils.getSingleElement(medline, - "MeshHeadingList"); - if (meshHeadingsList != null) { - List meshHeadings = XMLUtils.getElementList( - meshHeadingsList, "MeshHeading"); - for (Element meshHeading : meshHeadings) { - if ("Y".equals(XMLUtils.getElementAttribute(meshHeading, - "DescriptorName", "MajorTopicYN"))) { - primaryMeshHeadings.add(XMLUtils.getElementValue( - meshHeading, "DescriptorName")); - } else { - secondaryMeshHeadings.add(XMLUtils.getElementValue( - meshHeading, "DescriptorName")); - } - } - } - if (primaryMeshHeadings.size() > 0) { - List values = new LinkedList(); - for (String s : primaryMeshHeadings) { - values.add(new StringValue(s)); - } - record.addField("primaryMeshHeading", values); - } - if (secondaryMeshHeadings.size() > 0) { - List values = new LinkedList(); - for (String s : secondaryMeshHeadings) { - values.add(new StringValue(s)); - } - record.addField("secondaryMeshHeading", values); - } - - Element paginationElement = XMLUtils.getSingleElement(article, - "Pagination"); - if (paginationElement != null) { - String startPage = XMLUtils.getElementValue(paginationElement, - "StartPage"); - String endPage = XMLUtils.getElementValue(paginationElement, - "EndPage"); - if (StringUtils.isBlank(startPage)) { - startPage = XMLUtils.getElementValue(paginationElement, - "MedlinePgn"); - } - - if (startPage != null) { - record.addValue("startPage", new StringValue(startPage)); - } - if (endPage != null) { - record.addValue("endPage", new StringValue(endPage)); - } - } - } - - return record; - } -} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index d2d6dd10c1..750e5eb309 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -36,7 +36,6 @@ - - - @@ -79,7 +77,6 @@ jeissn pisbn eisbn - arxivCategory keywords mesh language @@ -106,13 +103,9 @@ - - - - @@ -129,40 +122,11 @@ - - - - - - - - - - - arxivCategory - - - - - - - - - - - - - - publicationStatus - - - - @@ -357,75 +321,6 @@ value="http://ebooks.serrelib.gr/serrelib-oai/request" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -544,7 +439,6 @@ - @@ -553,7 +447,6 @@ - diff --git a/dspace/config/spring/api/step-processing-listener.xml b/dspace/config/spring/api/step-processing-listener.xml index eb016c5133..986b850875 100644 --- a/dspace/config/spring/api/step-processing-listener.xml +++ b/dspace/config/spring/api/step-processing-listener.xml @@ -13,9 +13,7 @@ - - From 18bc3e1fcba23d00c3d113e7eb085d12f59d40ad Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 2 Jul 2020 19:54:37 +0200 Subject: [PATCH 172/465] Refactor the ChoiceAuthority to remove the unnecessary dependency from the field and collection --- .../content/authority/ChoiceAuthority.java | 36 +++-- .../authority/ChoiceAuthorityServiceImpl.java | 12 +- .../content/authority/DCInputAuthority.java | 11 +- .../authority/DSpaceControlledVocabulary.java | 12 +- .../content/authority/SampleAuthority.java | 18 ++- .../content/authority/SolrAuthority.java | 43 +++++- .../content/authority/TestAuthority.java | 18 ++- .../dspace/core/LegacyPluginServiceImpl.java | 4 +- .../java/org/dspace/core/NameAwarePlugin.java | 42 ++++++ .../java/org/dspace/core/SelfNamedPlugin.java | 25 +--- .../DSpaceControlledVocabularyTest.java | 3 +- .../VocabularyEntryLinkRepository.java | 45 ++---- .../app/rest/VocabularyRestRepositoryIT.java | 135 +----------------- 13 files changed, 179 insertions(+), 225 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index ff196e2b79..1ea5f18ac2 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -10,7 +10,7 @@ package org.dspace.content.authority; import java.util.HashMap; import java.util.Map; -import org.dspace.content.Collection; +import org.dspace.core.NameAwarePlugin; /** * Plugin interface that supplies an authority control mechanism for @@ -20,7 +20,7 @@ import org.dspace.content.Collection; * @see ChoiceAuthorityServiceImpl * @see MetadataAuthorityServiceImpl */ -public interface ChoiceAuthority { +public interface ChoiceAuthority extends NameAwarePlugin { /** * Get all values from the authority that match the preferred value. * Note that the offering was entered by the user and may contain @@ -35,15 +35,13 @@ public interface ChoiceAuthority { * defaultSelected index in the Choices instance to the choice, if any, * that matches the value. * - * @param field being matched for * @param text user's value to match - * @param collection database ID of Collection for context (owner of Item) * @param start choice at which to start, 0 is first. * @param limit maximum number of choices to return, 0 for no limit. * @param locale explicit localization key if available, or null * @return a Choices object (never null). */ - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale); + public Choices getMatches(String text, int start, int limit, String locale); /** * Get the single "best" match (if any) of a value in the authority @@ -54,13 +52,11 @@ public interface ChoiceAuthority { * This call is typically used in non-interactive metadata ingest * where there is no interactive agent to choose from among options. * - * @param field being matched for * @param text user's value to match - * @param collection database ID of Collection for context (owner of Item) * @param locale explicit localization key if available, or null * @return a Choices object (never null) with 1 or 0 values. */ - public Choices getBestMatch(String field, String text, Collection collection, String locale); + public Choices getBestMatch(String field, String locale); /** * Get the canonical user-visible "label" (i.e. short descriptive text) @@ -131,6 +127,17 @@ public interface ChoiceAuthority { return isHierarchical() ? 0 : null; } + /** + * Build the preferred choice associated with the authKey. The default + * implementation delegate the creato to the {@link #getLabel(String, String)} + * {@link #getValue(String, String)} and {@link #getExtra(String, String)} + * methods but can be directly overriden for better efficiency or special + * scenario + * + * @param authKey authority key known to this authority. + * @param locale explicit localization key if available, or null + * @return the preferred choice for this authKey and locale + */ default public Choice getChoice(String authKey, String locale) { Choice result = new Choice(); result.authority = authKey; @@ -139,4 +146,17 @@ public interface ChoiceAuthority { result.extras.putAll(getExtra(authKey, locale)); return result; } + + /** + * Provide a recommendation to store the authority in the metadata value if + * available in the in the provided choice(s). Usually ChoiceAuthority should + * recommend that so the default is true and it only need to be implemented in + * the unusual scenario + * + * @return true if the authority provided in any choice of this + * authority should be stored in the metadata value + */ + default public boolean storeAuthority() { + return true; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 5e15d4d74a..3b1a6a7654 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -88,9 +88,9 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Autowired(required = true) protected PluginService pluginService; - private final String CHOICES_PLUGIN_PREFIX = "choices.plugin."; - private final String CHOICES_PRESENTATION_PREFIX = "choices.presentation."; - private final String CHOICES_CLOSED_PREFIX = "choices.closed."; + final static String CHOICES_PLUGIN_PREFIX = "choices.plugin."; + final static String CHOICES_PRESENTATION_PREFIX = "choices.presentation."; + final static String CHOICES_CLOSED_PREFIX = "choices.closed."; protected ChoiceAuthorityServiceImpl() { } @@ -152,7 +152,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "No choices plugin was configured for field \"" + fieldKey + "\", collection=" + collection.getID().toString() + "."); } - return ma.getMatches(fieldKey, query, collection, start, limit, locale); + return ma.getMatches(query, start, limit, locale); } @@ -168,7 +168,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService if (externalInput && ma instanceof SolrAuthority) { ((SolrAuthority) ma).addExternalResultsInNextMatches(); } - return ma.getMatches(fieldKey, query, collection, start, limit, locale); + return ma.getMatches(query, start, limit, locale); } @Override @@ -180,7 +180,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "No choices plugin was configured for field \"" + fieldKey + "\", collection=" + collection.getID().toString() + "."); } - return ma.getBestMatch(fieldKey, query, collection, locale); + return ma.getBestMatch(query, locale); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 5b3df48002..d3c51f9dc7 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -17,7 +17,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; -import org.dspace.content.Collection; import org.dspace.core.SelfNamedPlugin; /** @@ -55,6 +54,12 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority super(); } + @Override + public boolean storeAuthority() { + // For backward compatibility value pairs don't store authority in + // the metadatavalue + return false; + } public static String[] getPluginNames() { if (pluginNames == null) { initPluginNames(); @@ -104,7 +109,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority @Override - public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { + public Choices getMatches(String query, int start, int limit, String locale) { init(); int dflt = -1; @@ -126,7 +131,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { + public Choices getBestMatch(String text, String locale) { init(); for (int i = 0; i < values.length; ++i) { if (text.equalsIgnoreCase(values[i])) { diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index ce2237dd93..0b3c82e218 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -21,7 +21,6 @@ import javax.xml.xpath.XPathFactory; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; import org.dspace.core.SelfNamedPlugin; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -79,6 +78,13 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera super(); } + @Override + public boolean storeAuthority() { + // For backward compatibility controlled vocabularies don't store the node id in + // the metadatavalue + return false; + } + public static String[] getPluginNames() { if (pluginNames == null) { initPluginNames(); @@ -160,7 +166,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) { + public Choices getMatches(String text, int start, int limit, String locale) { init(); log.debug("Getting matches for '" + text + "'"); String xpathExpression = ""; @@ -184,7 +190,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { + public Choices getBestMatch(String text, String locale) { init(); log.debug("Getting best matches for '" + text + "'"); String xpathExpression = ""; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java index 0aaf8e9003..e6cc9b9d44 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java @@ -7,13 +7,13 @@ */ package org.dspace.content.authority; -import org.dspace.content.Collection; - /** * This is a *very* stupid test fixture for authority control, and also * serves as a trivial example of an authority plugin implementation. */ public class SampleAuthority implements ChoiceAuthority { + private String pluginInstanceName; + protected static String values[] = { "sun", "mon", @@ -35,7 +35,7 @@ public class SampleAuthority implements ChoiceAuthority { }; @Override - public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { + public Choices getMatches(String query, int start, int limit, String locale) { int dflt = -1; Choice v[] = new Choice[values.length]; for (int i = 0; i < values.length; ++i) { @@ -48,7 +48,7 @@ public class SampleAuthority implements ChoiceAuthority { } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { + public Choices getBestMatch(String text, String locale) { for (int i = 0; i < values.length; ++i) { if (text.equalsIgnoreCase(values[i])) { Choice v[] = new Choice[1]; @@ -63,4 +63,14 @@ public class SampleAuthority implements ChoiceAuthority { public String getLabel(String key, String locale) { return labels[Integer.parseInt(key)]; } + + @Override + public String getPluginInstanceName() { + return pluginInstanceName; + } + + @Override + public void setPluginInstanceName(String name) { + this.pluginInstanceName = name; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java index efc910a761..c93e6db786 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -24,8 +25,9 @@ import org.dspace.authority.AuthorityValue; import org.dspace.authority.SolrAuthorityInterface; import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; -import org.dspace.content.Collection; import org.dspace.core.ConfigurationManager; +import org.dspace.core.NameAwarePlugin; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; /** @@ -35,7 +37,14 @@ import org.dspace.services.factory.DSpaceServicesFactory; * @author Mark Diggory (markd at atmire dot com) */ public class SolrAuthority implements ChoiceAuthority { + /** the name assigned to the specific instance by the PluginService, @see {@link NameAwarePlugin} **/ + private String authorityName; + /** + * the metadata managed by the plugin instance, derived from its authority name + * in the form schema_element_qualifier + */ + private String field; protected SolrAuthorityInterface source = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("AuthoritySource", SolrAuthorityInterface.class); @@ -45,8 +54,9 @@ public class SolrAuthority implements ChoiceAuthority { protected boolean externalResults = false; protected final AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance() .getAuthorityValueService(); - - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale, + protected final ConfigurationService configurationService = DSpaceServicesFactory.getInstance() + .getConfigurationService(); + public Choices getMatches(String text, int start, int limit, String locale, boolean bestMatch) { if (limit == 0) { limit = 10; @@ -193,13 +203,13 @@ public class SolrAuthority implements ChoiceAuthority { } @Override - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) { - return getMatches(field, text, collection, start, limit, locale, true); + public Choices getMatches(String text, int start, int limit, String locale) { + return getMatches(text, start, limit, locale, true); } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { - Choices matches = getMatches(field, text, collection, 0, 1, locale, false); + public Choices getBestMatch(String text, String locale) { + Choices matches = getMatches(text, 0, 1, locale, false); if (matches.values.length != 0 && !matches.values[0].value.equalsIgnoreCase(text)) { matches = new Choices(false); } @@ -276,4 +286,23 @@ public class SolrAuthority implements ChoiceAuthority { public void addExternalResultsInNextMatches() { this.externalResults = true; } + + @Override + public void setPluginInstanceName(String name) { + authorityName = name; + for (Entry conf : configurationService.getProperties().entrySet()) { + if (StringUtils.startsWith((String) conf.getKey(), ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX) + && StringUtils.equals((String) conf.getValue(), authorityName)) { + field = ((String) conf.getKey()).substring(ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX.length()) + .replace(".", "_"); + // exit the look immediately as we have found it + break; + } + } + } + + @Override + public String getPluginInstanceName() { + return authorityName; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java index 1bb3fa5450..15c000e978 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; -import org.dspace.content.Collection; /** * This is a *very* stupid test fixture for authority control with AuthorityVariantsSupport. @@ -19,6 +18,7 @@ import org.dspace.content.Collection; * @author Andrea Bollini (CILEA) */ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport { + private String pluginInstanceName; @Override public List getVariants(String key, String locale) { @@ -33,8 +33,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } @Override - public Choices getMatches(String field, String text, Collection collection, - int start, int limit, String locale) { + public Choices getMatches(String text, int start, int limit, String locale) { Choices choices = new Choices(false); if (StringUtils.isNotBlank(text)) { @@ -52,8 +51,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } @Override - public Choices getBestMatch(String field, String text, Collection collection, - String locale) { + public Choices getBestMatch(String text, String locale) { Choices choices = new Choices(false); if (StringUtils.isNotBlank(text)) { @@ -76,4 +74,14 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } return "Unknown"; } + + @Override + public String getPluginInstanceName() { + return pluginInstanceName; + } + + @Override + public void setPluginInstanceName(String name) { + this.pluginInstanceName = name; + } } diff --git a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java index f8291dc977..ea8cdc1403 100644 --- a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java @@ -345,8 +345,8 @@ public class LegacyPluginServiceImpl implements PluginService { " for interface=" + iname + " pluginName=" + name); Object result = pluginClass.newInstance(); - if (result instanceof SelfNamedPlugin) { - ((SelfNamedPlugin) result).setPluginInstanceName(name); + if (result instanceof NameAwarePlugin) { + ((NameAwarePlugin) result).setPluginInstanceName(name); } return result; } diff --git a/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java b/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java new file mode 100644 index 0000000000..6c562ea04c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.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.core; + +/** + * This is the interface that should be implemented by all the named plugin that + * like to be aware of their name + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * @version $Revision$ + * @see org.dspace.core.service.PluginService + */ +public interface NameAwarePlugin { + + /** + * Get the instance's particular name. + * Returns the name by which the class was chosen when + * this instance was created. Only works for instances created + * by PluginService, or if someone remembers to call setPluginName. + *

+ * Useful when the implementation class wants to be configured differently + * when it is invoked under different names. + * + * @return name or null if not available. + */ + public String getPluginInstanceName(); + + /** + * Set the name under which this plugin was instantiated. + * Not to be invoked by application code, it is + * called automatically by PluginService.getNamedPlugin() + * when the plugin is instantiated. + * + * @param name -- name used to select this class. + */ + public void setPluginInstanceName(String name); +} diff --git a/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java b/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java index 2bdcf830e7..680fa15c80 100644 --- a/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java +++ b/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java @@ -28,7 +28,7 @@ package org.dspace.core; * @version $Revision$ * @see org.dspace.core.service.PluginService */ -public abstract class SelfNamedPlugin { +public abstract class SelfNamedPlugin implements NameAwarePlugin { // the specific alias used to find the class that created this instance. private String myName = null; @@ -52,30 +52,13 @@ public abstract class SelfNamedPlugin { return null; } - /** - * Get an instance's particular name. - * Returns the name by which the class was chosen when - * this instance was created. Only works for instances created - * by PluginService, or if someone remembers to call setPluginName. - *

- * Useful when the implementation class wants to be configured differently - * when it is invoked under different names. - * - * @return name or null if not available. - */ + @Override public String getPluginInstanceName() { return myName; } - /** - * Set the name under which this plugin was instantiated. - * Not to be invoked by application code, it is - * called automatically by PluginService.getNamedPlugin() - * when the plugin is instantiated. - * - * @param name -- name used to select this class. - */ - protected void setPluginInstanceName(String name) { + @Override + public void setPluginInstanceName(String name) { myName = name; } } diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 84306ac034..77cf105dd4 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -86,8 +86,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm"); assertNotNull(instance); - Choices result = instance.getMatches(field, text, collection, start, - limit, locale); + Choices result = instance.getMatches(text, start, limit, locale); assertEquals("the farm::north 40", result.values[0].value); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index e18fc35d84..1f24592022 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -7,10 +7,8 @@ */ package org.dspace.app.rest.repository; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; @@ -21,8 +19,8 @@ import org.dspace.app.rest.model.VocabularyEntryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; -import org.dspace.content.Collection; import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.service.CollectionService; @@ -31,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; 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; @@ -59,42 +58,26 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository String exact = request == null ? null : request.getParameter("exact"); String filter = request == null ? null : request.getParameter("filter"); String entryID = request == null ? null : request.getParameter("entryID"); - String metadata = request == null ? null : request.getParameter("metadata"); - String uuidCollectìon = request == null ? null : request.getParameter("collection"); - - if (StringUtils.isEmpty(metadata) || StringUtils.isEmpty(uuidCollectìon)) { - throw new IllegalArgumentException("the metadata and collection parameters are both required"); - } if (StringUtils.isNotBlank(filter) && StringUtils.isNotBlank(entryID)) { throw new IllegalArgumentException("required only one of the parameters: filter or entryID"); } - Collection collection = null; - if (StringUtils.isNotBlank(uuidCollectìon)) { - try { - collection = cs.find(context, UUID.fromString(uuidCollectìon)); - } catch (SQLException e) { - throw new UnprocessableEntityException(uuidCollectìon + " is not a valid collection"); - } - } - - // validate the parameters - String[] tokens = org.dspace.core.Utils.tokenize(metadata); - String vocName = cas.getChoiceAuthorityName(tokens[0], tokens[1], tokens[2], collection); - if (!StringUtils.equals(name, vocName)) { - throw new UnprocessableEntityException("The vocabulary " + name + " is not allowed for the metadata " - + metadata + " and collection " + uuidCollectìon); - } Pageable pageable = utils.getPageable(optionalPageable); List results = new ArrayList<>(); - String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); - + ChoiceAuthority ca = cas.getChoiceAuthorityByAuthorityName(name); + if (ca == null) { + throw new ResourceNotFoundException("the vocabulary named " + name + "doesn't exist"); + } + if (!ca.isScrollable() && StringUtils.isBlank(filter) && StringUtils.isBlank(entryID)) { + throw new UnprocessableEntityException( + "one of filter or entryID parameter is required for not scrollable vocabularies"); + } Choices choices = null; if (BooleanUtils.toBoolean(exact)) { - choices = cas.getBestMatch(fieldKey, filter, collection, context.getCurrentLocale().toString()); + choices = ca.getBestMatch(filter, context.getCurrentLocale().toString()); } else if (StringUtils.isNotBlank(entryID)) { - Choice choice = cas.getChoiceAuthorityByAuthorityName(vocName).getChoice(entryID, + Choice choice = ca.getChoice(entryID, context.getCurrentLocale().toString()); if (choice != null) { choices = new Choices(new Choice[] {choice}, 0, 1, Choices.CF_ACCEPTED, false); @@ -102,10 +85,10 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository choices = new Choices(false); } } else { - choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), + choices = ca.getMatches(filter, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); } - boolean storeAuthority = cas.storeAuthority(fieldKey, collection); + boolean storeAuthority = ca.storeAuthority(); for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 898e63cf78..2b1de5ae21 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -144,18 +144,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void correctSrscQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("collection", collection.getID().toString()) .param("filter", "Research") .param("size", "2")) .andExpect(status().isOk()) @@ -170,70 +161,19 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.size", Matchers.is(2))); } - @Test - public void controlledVocabularyEntriesWrongCollectionUUIDTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("collection", UUID.randomUUID().toString()) - .param("filter", "Research")) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void controlledVocabularyEntriesMissingCollectionUUID_Test() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("filter", "Research")) - .andExpect(status().isBadRequest()); - } - - @Test - public void controlledVocabularyEntriesWrongMetadataTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) - .param("filter", "Research")) - .andExpect(status().isUnprocessableEntity()); - } - @Test public void notScrollableVocabularyRequiredQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.not.existing") - .param("collection", collection.getID().toString())) + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries")) .andExpect(status().isUnprocessableEntity()); } @Test public void noResultsSrscQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") - .param("collection", collection.getID().toString()) .param("filter", "Research2") .param("size", "1000")) .andExpect(status().isOk()) @@ -242,17 +182,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void vocabularyEntriesCommonTypesWithPaginationTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token) - .perform(get("/api/submission/vocabularies/common_types/entries").param("metadata", "dc.type") - .param("collection", collection.getID().toString()).param("size", "2")) + .perform(get("/api/submission/vocabularies/common_types/entries").param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), @@ -265,8 +197,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes //second page getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) .param("size", "2") .param("page", "1")) .andExpect(status().isOk()) @@ -282,17 +212,8 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void vocabularyEntriesCommon_typesWithQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) .param("filter", "Book") .param("size", "2")) .andExpect(status().isOk()) @@ -307,17 +228,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void correctSolrQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/SolrAuthorAuthority/entries") - .param("metadata", "dc.contributor.author") - .param("collection", collection.getID().toString()) .param("filter", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) @@ -330,17 +243,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void noResultsSolrQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/SolrAuthorAuthority/entries") - .param("metadata", "dc.contributor.author") - .param("collection", collection.getID().toString()) .param("filter", "Smith") .param("size", "1000")) .andExpect(status().isOk()) @@ -350,7 +255,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void findByMetadataAndCollectionTest() throws Exception { context.turnOffAuthorisationSystem(); - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Test collection") .build(); @@ -408,17 +312,8 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void linkedEntitiesWithExactParamTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) .param("filter", "Animation") .param("exact", "true")) .andExpect(status().isOk()) @@ -428,36 +323,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } - @Test - public void linkedEntitiesWrongMetataForAuthorityTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) - .param("filter", "Animation")) - .andExpect(status().isUnprocessableEntity()); - } - @Test public void linkedEntitiesWithFilterAndEntryIdTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("collection", collection.getID().toString()) .param("filter", "Research") .param("entryID", "VR131402")) .andExpect(status().isBadRequest()); From 861170a179591ee0cc78e75c430caab1cd6219c3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 2 Jul 2020 19:55:53 +0200 Subject: [PATCH 173/465] added test and fix getLocale --- .../AbstractDSpaceRestRepository.java | 33 +++++++++++-------- .../app/rest/SubmissionFormsControllerIT.java | 31 +++++++++++++++++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java index 6460424f1c..f5ef703700 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.repository; import java.util.Enumeration; import java.util.Locale; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; @@ -40,7 +41,7 @@ public abstract class AbstractDSpaceRestRepository { Context context = null; Request currentRequest = requestService.getCurrentRequest(); context = ContextUtil.obtainContext(currentRequest.getServletRequest()); - Locale currentLocale = getLocal(context, currentRequest); + Locale currentLocale = getLocale(context, currentRequest); context.setCurrentLocale(currentLocale); return context; } @@ -49,26 +50,30 @@ public abstract class AbstractDSpaceRestRepository { return requestService; } - private Locale getLocal(Context context, Request request) { + private Locale getLocale(Context context, Request request) { Locale userLocale = null; Locale supportedLocale = null; - if (context.getCurrentUser() != null) { + + // Locales requested from client + String locale = request.getHttpServletRequest().getHeader("Accept-Language"); + if (StringUtils.isNotBlank(locale)) { + Enumeration locales = request.getHttpServletRequest().getLocales(); + if (locales != null) { + while (locales.hasMoreElements()) { + Locale current = locales.nextElement(); + if (I18nUtil.isSupportedLocale(current)) { + userLocale = current; + break; + } + } + } + } + if (userLocale == null && context.getCurrentUser() != null) { String userLanguage = context.getCurrentUser().getLanguage(); if (userLanguage != null) { userLocale = new Locale(userLanguage); } } - // Locales requested from client - Enumeration locales = request.getHttpServletRequest().getLocales(); - if (locales != null) { - while (locales.hasMoreElements()) { - Locale current = locales.nextElement(); - if (I18nUtil.isSupportedLocale(current)) { - userLocale = current; - break; - } - } - } if (userLocale == null) { return I18nUtil.getDefaultLocale(); } 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 53c41c62d4..4eee3579ee 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 @@ -411,6 +411,37 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe resetLocalesConfiguration(); } + @Test + public void supportLanguageUsingMultipleLocaleTest() throws Exception { + context.turnOffAuthorisationSystem(); + String[] supportedLanguage = {"it","uk","en"}; + configurationService.setProperty("default.locale","en"); + configurationService.setProperty("webui.supported.locales",supportedLanguage); + submissionFormRestRepository.reload(); + + context.restoreAuthSystemState(); + + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest") + .header("Accept-Language", "fr;q=1, it;q=0.9")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is("languagetest"))) + .andExpect(jsonPath("$.name", is("languagetest"))) + .andExpect(jsonPath("$.type", is("submissionform"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) + .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("name", "Autore", "\u00C8 richiesto almeno un autore", true, + "Aggiungi un autore", "dc.contributor.author")))) + .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher + .matchFormFieldDefinition("onebox", "Titolo", + "\u00C8 necessario inserire un titolo principale per questo item", false, + "Inserisci titolo principale di questo item", "dc.title")))); + + resetLocalesConfiguration(); + } + private void resetLocalesConfiguration() throws DCInputsReaderException { configurationService.setProperty("default.locale","en"); configurationService.setProperty("webui.supported.locales",null); From 675c975d2940fe48c15c71044b108668c5a86ea1 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 2 Jul 2020 21:11:56 +0200 Subject: [PATCH 174/465] Use a more explicit name for the storeAuthorityInMetadata method --- .../main/java/org/dspace/content/authority/ChoiceAuthority.java | 2 +- .../java/org/dspace/content/authority/DCInputAuthority.java | 2 +- .../dspace/content/authority/DSpaceControlledVocabulary.java | 2 +- .../app/rest/repository/VocabularyEntryLinkRepository.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index 1ea5f18ac2..8977970e1f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -156,7 +156,7 @@ public interface ChoiceAuthority extends NameAwarePlugin { * @return true if the authority provided in any choice of this * authority should be stored in the metadata value */ - default public boolean storeAuthority() { + default public boolean storeAuthorityInMetadata() { return true; } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index d3c51f9dc7..f46494706b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -55,7 +55,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } @Override - public boolean storeAuthority() { + public boolean storeAuthorityInMetadata() { // For backward compatibility value pairs don't store authority in // the metadatavalue return false; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 0b3c82e218..e933eee9bb 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -79,7 +79,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public boolean storeAuthority() { + public boolean storeAuthorityInMetadata() { // For backward compatibility controlled vocabularies don't store the node id in // the metadatavalue return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 1f24592022..7e4f989b0a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -88,7 +88,7 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository choices = ca.getMatches(filter, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); } - boolean storeAuthority = ca.storeAuthority(); + boolean storeAuthority = ca.storeAuthorityInMetadata(); for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } From dc2151209a18c5ad112c48301d5cd68267708b8e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 2 Jul 2020 14:21:56 -0500 Subject: [PATCH 175/465] [maven-release-plugin] prepare release dspace-7.0-beta3 --- dspace-api/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 4 ++-- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 4 ++-- dspace/pom.xml | 2 +- pom.xml | 26 +++++++++++++------------- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a0714c04ee..6571b5f6b1 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index d5a129c90a..137c990969 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 47fc5cd204..3a3bfe833c 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 1038617b49..45694cd2d4 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.0-beta3-SNAPSHOT + 7.0-beta3 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index d8cc6a7587..6f04013c31 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 1670c8454f..b1d92238e1 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 7bb709932a..3d56f3541e 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 934eae3682..cb92dcab91 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 0c05de84a4..e34a740642 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. @@ -158,7 +158,7 @@ org.dspace dspace-api - 7.0-beta3-SNAPSHOT + 7.0-beta3 test-jar test diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 84c685a981..c5951c13d3 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index a17ff70f80..ba0dd1b45d 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 06d466f522..78600f0f0c 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. @@ -194,7 +194,7 @@ just adding new jar in the classloader org.dspace dspace-server-webapp - 7.0-beta3-SNAPSHOT + 7.0-beta3 test-jar test diff --git a/dspace/pom.xml b/dspace/pom.xml index 6c693eacec..48e1f0ceb6 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.0-beta3-SNAPSHOT + 7.0-beta3 .. diff --git a/pom.xml b/pom.xml index aa8a8b15dd..a68760ba88 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.0-beta3-SNAPSHOT + 7.0-beta3 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -848,14 +848,14 @@ org.dspace dspace-rest - 7.0-beta3-SNAPSHOT + 7.0-beta3 jar classes org.dspace dspace-rest - 7.0-beta3-SNAPSHOT + 7.0-beta3 war @@ -1008,50 +1008,50 @@ org.dspace dspace-api - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace.modules additions - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace dspace-sword - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace dspace-swordv2 - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace dspace-oai - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace dspace-services - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace dspace-rdf - 7.0-beta3-SNAPSHOT + 7.0-beta3 org.dspace dspace-server-webapp - 7.0-beta3-SNAPSHOT + 7.0-beta3 jar classes org.dspace dspace-server-webapp - 7.0-beta3-SNAPSHOT + 7.0-beta3 war @@ -1812,7 +1812,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - HEAD + dspace-7.0-beta3 From cc7a6c30fd83d153fcd7d5b9af5f4a5454f0c342 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 2 Jul 2020 14:22:06 -0500 Subject: [PATCH 176/465] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 4 ++-- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 4 ++-- dspace/pom.xml | 2 +- pom.xml | 26 +++++++++++++------------- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6571b5f6b1..49ca062aa6 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 137c990969..4036348be0 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 3a3bfe833c..4287188c8e 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 45694cd2d4..744ee24203 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.0-beta3 + 7.0-beta4-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 6f04013c31..d1f54fd9d6 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index b1d92238e1..95ab75f52e 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 3d56f3541e..4b28f091db 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index cb92dcab91..e0642eaa0b 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index e34a740642..59ba279c3f 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. @@ -158,7 +158,7 @@ org.dspace dspace-api - 7.0-beta3 + 7.0-beta4-SNAPSHOT test-jar test diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index c5951c13d3..a875a17ca9 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index ba0dd1b45d..2f1addb2f9 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 78600f0f0c..4ecf2cf5b2 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. @@ -194,7 +194,7 @@ just adding new jar in the classloader org.dspace dspace-server-webapp - 7.0-beta3 + 7.0-beta4-SNAPSHOT test-jar test diff --git a/dspace/pom.xml b/dspace/pom.xml index 48e1f0ceb6..f468746b62 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.0-beta3 + 7.0-beta4-SNAPSHOT .. diff --git a/pom.xml b/pom.xml index a68760ba88..a3b4b30c98 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.0-beta3 + 7.0-beta4-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -848,14 +848,14 @@ org.dspace dspace-rest - 7.0-beta3 + 7.0-beta4-SNAPSHOT jar classes org.dspace dspace-rest - 7.0-beta3 + 7.0-beta4-SNAPSHOT war @@ -1008,50 +1008,50 @@ org.dspace dspace-api - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace.modules additions - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace dspace-sword - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace dspace-swordv2 - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace dspace-oai - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace dspace-services - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace dspace-rdf - 7.0-beta3 + 7.0-beta4-SNAPSHOT org.dspace dspace-server-webapp - 7.0-beta3 + 7.0-beta4-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.0-beta3 + 7.0-beta4-SNAPSHOT war @@ -1812,7 +1812,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.0-beta3 + HEAD From b524c36a53b5d6625fba2c21bc2e3adcd9d28a32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2020 19:22:27 +0000 Subject: [PATCH 177/465] Bump commons-beanutils from 1.9.3 to 1.9.4 Bumps commons-beanutils from 1.9.3 to 1.9.4. Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a3b4b30c98..b74e76da5c 100644 --- a/pom.xml +++ b/pom.xml @@ -1261,7 +1261,7 @@ commons-beanutils commons-beanutils - 1.9.3 + 1.9.4 commons-cli From fcdee503343aa7eac61f0f3689391047fbd3304e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 3 Jul 2020 10:55:52 -0500 Subject: [PATCH 178/465] Fixes to error handling Log all errors & provide generic messages in response --- .../DSpaceApiExceptionControllerAdvice.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) 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 d255b6fe27..52c691723f 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 @@ -14,6 +14,8 @@ import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.authorize.AuthorizeException; import org.springframework.beans.TypeMismatchException; @@ -41,6 +43,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep */ @ControllerAdvice public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { + private static final Logger log = LogManager.getLogger(DSpaceApiExceptionControllerAdvice.class); @Autowired private RestAuthenticationService restAuthenticationService; @@ -49,16 +52,16 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { if (restAuthenticationService.hasAuthenticationData(request)) { - sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_FORBIDDEN); + sendErrorResponse(request, response, ex, "Access is denied", HttpServletResponse.SC_FORBIDDEN); } else { - sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_UNAUTHORIZED); + sendErrorResponse(request, response, ex, "Authentication is required", HttpServletResponse.SC_UNAUTHORIZED); } } @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); + sendErrorResponse(request, response, ex, "Bad or invalid request", HttpServletResponse.SC_BAD_REQUEST); } @ExceptionHandler(SQLException.class) @@ -72,24 +75,23 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH protected void handleIOException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { sendErrorResponse(request, response, ex, - "An internal read or write operation failed (IO Exception)", + "An internal read or write operation failed", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } @ExceptionHandler(MethodNotAllowedException.class) protected void methodNotAllowedException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_METHOD_NOT_ALLOWED); + sendErrorResponse(request, response, ex, "Method is not allowed or supported", HttpServletResponse.SC_METHOD_NOT_ALLOWED); } @ExceptionHandler( {UnprocessableEntityException.class}) protected void handleUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - //422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity". //Using the value from HttpStatus. sendErrorResponse(request, response, null, - ex.getMessage(), + "Unprocessable or invalid entity", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -98,7 +100,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 sendErrorResponse(request, response, null, - ex.getMessage(), + "Required parameters are invalid", HttpStatus.BAD_REQUEST.value()); } @@ -107,7 +109,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 sendErrorResponse(request, response, null, - ex.getMessage(), + "Required parameters are missing", HttpStatus.BAD_REQUEST.value()); } @@ -137,7 +139,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH } else { returnCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; } - sendErrorResponse(request, response, ex, "An Exception has occured", returnCode); + sendErrorResponse(request, response, ex, "An Exception has occurred", returnCode); } @@ -147,6 +149,9 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH //Make sure Spring picks up this exception request.setAttribute(EXCEPTION_ATTRIBUTE, ex); + //Log the full error and status code + log.error(message + " (status={})", statusCode, ex); + //Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter response.sendError(statusCode, message); } From 41dbbfedd43ca4adb1e2760eff79f53d86f5b368 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 3 Jul 2020 15:16:45 -0500 Subject: [PATCH 179/465] Fix bugs and failed test --- .../DSpaceApiExceptionControllerAdvice.java | 13 +++++++------ .../rest/WorkflowDefinitionRestRepositoryIT.java | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) 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 52c691723f..6cf297ebf6 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 @@ -61,7 +61,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH @ExceptionHandler({IllegalArgumentException.class, MultipartException.class}) protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - sendErrorResponse(request, response, ex, "Bad or invalid request", HttpServletResponse.SC_BAD_REQUEST); + sendErrorResponse(request, response, ex, "Request is invalid or incorrect", HttpServletResponse.SC_BAD_REQUEST); } @ExceptionHandler(SQLException.class) @@ -82,7 +82,8 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH @ExceptionHandler(MethodNotAllowedException.class) protected void methodNotAllowedException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - sendErrorResponse(request, response, ex, "Method is not allowed or supported", HttpServletResponse.SC_METHOD_NOT_ALLOWED); + sendErrorResponse(request, response, ex, "Method is not allowed or supported", + HttpServletResponse.SC_METHOD_NOT_ALLOWED); } @ExceptionHandler( {UnprocessableEntityException.class}) @@ -100,7 +101,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 sendErrorResponse(request, response, null, - "Required parameters are invalid", + "A required parameter is invalid", HttpStatus.BAD_REQUEST.value()); } @@ -109,7 +110,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 sendErrorResponse(request, response, null, - "Required parameters are missing", + "A required parameter is missing", HttpStatus.BAD_REQUEST.value()); } @@ -139,7 +140,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH } else { returnCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; } - sendErrorResponse(request, response, ex, "An Exception has occurred", returnCode); + sendErrorResponse(request, response, ex, "An exception has occurred", returnCode); } @@ -150,7 +151,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH request.setAttribute(EXCEPTION_ATTRIBUTE, ex); //Log the full error and status code - log.error(message + " (status={})", statusCode, ex); + log.error("{} (status:{})", message, statusCode, ex); //Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter response.sendError(statusCode, message); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java index 4a877825b9..7d8b7f7065 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java @@ -240,7 +240,7 @@ public class WorkflowDefinitionRestRepositoryIT extends AbstractControllerIntegr getClient(token).perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/search/findByCollection?uuid=" + nonValidUUID)) //We expect a 400 Illegal Argument Exception (Bad Request) cannot convert UUID .andExpect(status().isBadRequest()) - .andExpect(status().reason(containsString("Failed to convert " + nonValidUUID))); + .andExpect(status().reason(containsString("A required parameter is invalid"))); } @Test From 3c349cb70c775be1d13bbe1109d53bff1aa6e050 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Mon, 6 Jul 2020 11:16:08 +0200 Subject: [PATCH 180/465] RIS, CSV and TSV live import implementation --- ...aratedImportMetadataSourceServiceImpl.java | 88 +++++++++++++ .../RisImportMetadataSourceServiceImpl.java | 117 ++++++++++++++++++ .../spring-dspace-addon-import-services.xml | 24 +++- .../config/spring/api/bibtex-integration.xml | 44 ++----- .../api/characterseparated-integration.xml | 80 ++++++++++++ .../spring/api/dublicore-metadata.mapper.xml | 59 +++++++++ dspace/config/spring/api/ris-integration.xml | 65 ++++++++++ 7 files changed, 441 insertions(+), 36 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java create mode 100644 dspace/config/spring/api/characterseparated-integration.xml create mode 100644 dspace/config/spring/api/dublicore-metadata.mapper.xml create mode 100644 dspace/config/spring/api/ris-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..75f2cb0776 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.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.importer.external.csv.service; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import au.com.bytecode.opencsv.CSVReader; +import org.dspace.importer.external.exception.FileSourceException; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.contributor.MetadataContributor; +import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; +import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; + + + +public class CharacterSeparatedImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { + + private String separator = ","; + + private String escapeCharacter = "\""; + + private String importSource = "CsvMetadataSource"; + + public void setSeparator(String separator) { + this.separator = separator; + } + + @Override + public String getImportSource() { + return importSource; + } + + public void setImportSource(String importSource) { + this.importSource = importSource; + } + + public void setEscapeCharacter(String escapeCharacter) { + this.escapeCharacter = escapeCharacter; + } + + @Override + protected List readData(InputStream inputStream) throws FileSourceException { + List plainMetadataList = new ArrayList<>(); + try (CSVReader csvReader = new CSVReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8), + separator.charAt(0), escapeCharacter.charAt(0));) { + List lines = csvReader.readAll(); + for (String [] items : lines) { + List keyValueList = new ArrayList<>(); + if (items != null) { + int size = items.length; + int count = 0; + while (count < size) { + PlainMetadataKeyValueItem keyValueItem = new PlainMetadataKeyValueItem(); + keyValueItem.setKey(String.valueOf(count)); + keyValueItem.setValue(items[count]); + keyValueList.add(keyValueItem); + count++; + } + PlainMetadataSourceDto dto = new PlainMetadataSourceDto(); + dto.setMetadata(keyValueList); + plainMetadataList.add(dto); + } + } + } catch (IOException e) { + throw new FileSourceException("Error reading file", e); + } + return plainMetadataList; + } + + @Override + public void setMetadataFieldMap(Map> metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..73183242e1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java @@ -0,0 +1,117 @@ +/** + * 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.importer.external.ris.service; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Resource; + +import org.dspace.importer.external.exception.FileSourceException; +import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; +import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; + +/** + * Implements a metadata importer for RIS files + * Implementations insprider by BTE DataLoader {@link https://github.com/EKT/Biblio-Transformation-Engine/blob/master/bte-io/src/main/java/gr/ekt/bteio/loaders/RISDataLoader.java} + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class RisImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { + + @Override + public String getImportSource() { + return "RISMetadataSource"; + } + + protected List readData(InputStream inputStream) throws FileSourceException { + return aggreageteData(inputStream); + } + + private List aggreageteData(InputStream inputStream) throws FileSourceException { + List metadata = new ArrayList<>(); + List notAggregatedItems = notAggregatedData(inputStream); + List aggregatedTmpList = null; + Iterator itr = notAggregatedItems.iterator(); + while (itr.hasNext()) { + PlainMetadataKeyValueItem item = itr.next(); + if ("TY".equals(item.getKey())) { + if (aggregatedTmpList != null) { + PlainMetadataSourceDto dto = new PlainMetadataSourceDto(); + dto.setMetadata(new ArrayList<>(aggregatedTmpList)); + metadata.add(dto); + } + aggregatedTmpList = new ArrayList<>(); + aggregatedTmpList.add(item); + } else { + if (aggregatedTmpList != null) { + aggregatedTmpList.add(item); + // save last iteration metadata + if (!itr.hasNext()) { + PlainMetadataSourceDto dto = new PlainMetadataSourceDto(); + dto.setMetadata(new ArrayList<>(aggregatedTmpList)); + metadata.add(dto); + } + } + } + } + return metadata; + } + + private List notAggregatedData(InputStream inputStream) throws FileSourceException { + LinkedList items = new LinkedList<>(); + BufferedReader reader; + try { + reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty() || line.equals("") || line.matches("^\\s*$")) { + continue; + } + //match valid RIS entry + Pattern risPattern = Pattern.compile("^([A-Z][A-Z0-9]) - (.*)$"); + Matcher risMatcher = risPattern.matcher(line); + if (risMatcher.matches()) { + PlainMetadataKeyValueItem keyValueItem = new PlainMetadataKeyValueItem(); + keyValueItem.setValue(risMatcher.group(2)); + keyValueItem.setKey(risMatcher.group(1)); + items.add(keyValueItem); + } else { + if (!items.isEmpty()) { + items.getLast().setValue(items.getLast().getValue().concat(line)); + } + } + } + } catch (Exception e) { + throw new FileSourceException("Cannot parse RIS file"); + } + return items; + } + + /** + * Retrieve the MetadataFieldMapping containing the mapping between RecordType + * (in this case PlainMetadataSourceDto.class) and Metadata + * + * @return The configured MetadataFieldMapping + */ + @Override + @SuppressWarnings("unchecked") + @Resource(name = "risMetadataFieldMap") + public void setMetadataFieldMap(@SuppressWarnings("rawtypes") Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index 3b4f7bad31..e356987787 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -23,8 +23,11 @@ lazy-init="false" autowire="byType" destroy-method="destroy"> - - + + + + + @@ -48,9 +51,24 @@ class="org.dspace.importer.external.pubmed.metadatamapping.PubmedFieldMapping"> + + + - + + + + + + + + + + diff --git a/dspace/config/spring/api/bibtex-integration.xml b/dspace/config/spring/api/bibtex-integration.xml index 9675ef82b3..eeabace1c7 100644 --- a/dspace/config/spring/api/bibtex-integration.xml +++ b/dspace/config/spring/api/bibtex-integration.xml @@ -17,58 +17,36 @@ only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over what metadatafield is generated. - - - - - + + + + + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dspace/config/spring/api/characterseparated-integration.xml b/dspace/config/spring/api/characterseparated-integration.xml new file mode 100644 index 0000000000..106d635671 --- /dev/null +++ b/dspace/config/spring/api/characterseparated-integration.xml @@ -0,0 +1,80 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/dublicore-metadata.mapper.xml b/dspace/config/spring/api/dublicore-metadata.mapper.xml new file mode 100644 index 0000000000..6461f129a5 --- /dev/null +++ b/dspace/config/spring/api/dublicore-metadata.mapper.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/ris-integration.xml b/dspace/config/spring/api/ris-integration.xml new file mode 100644 index 0000000000..39b8edcf8d --- /dev/null +++ b/dspace/config/spring/api/ris-integration.xml @@ -0,0 +1,65 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From af38c36bdb094d91f934565d79bab66e29679363 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Mon, 6 Jul 2020 11:33:52 +0200 Subject: [PATCH 181/465] add JavaDoc to LiveImportDataProvider public method --- .../provider/impl/LiveImportDataProvider.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java index 776984ddb7..7bc9766a19 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java @@ -47,18 +47,34 @@ public class LiveImportDataProvider implements ExternalDataProvider { return sourceIdentifier; } + /** + * This method set the SourceIdentifier for the ExternalDataProvider + * @param sourceIdentifier The UNIQUE sourceIdentifier to be set on any LiveImport data provider + */ public void setSourceIdentifier(String sourceIdentifier) { this.sourceIdentifier = sourceIdentifier; } + /** + * This method set the MetadataSource for the ExternalDataProvider + * @param metadataSource {@link org.dspace.importer.external.service.components.MetadataSource} implementation used to process the input data + */ public void setMetadataSource(MetadataSource metadataSource) { this.metadataSource = metadataSource; } + /** + * This method set dublin core identifier to use as metadata id + * @param recordIdMetadata dublin core identifier to use as metadata id + */ public void setRecordIdMetadata(String recordIdMetadata) { this.recordIdMetadata = recordIdMetadata; } + /** + * This method set the dublin core identifier to display the title + * @param displayMetadata metadata to use as title + */ public void setDisplayMetadata(String displayMetadata) { this.displayMetadata = displayMetadata; } From b7949a14d7b230d16ccd12139d3b28836ef7d24d Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Mon, 6 Jul 2020 16:35:48 +0200 Subject: [PATCH 182/465] Add endnote support and (partial) test --- ...ndnoteImportMetadataSourceServiceImpl.java | 99 +++++++++++ .../spring-dspace-addon-import-services.xml | 6 + .../rest/WorkspaceItemRestRepositoryIT.java | 165 +++++++++++++++++- .../app/rest/csv-missing-field-test.csv | 1 + .../org/dspace/app/rest/csv-test.csv | 1 + .../config/spring/api/endnote-integration.xml | 52 ++++++ 6 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-missing-field-test.csv create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-test.csv create mode 100644 dspace/config/spring/api/endnote-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..e0bc10f238 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java @@ -0,0 +1,99 @@ +package org.dspace.importer.external.endnote.service; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.dspace.importer.external.exception.FileSourceException; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.contributor.MetadataContributor; +import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; +import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; + +public class EndnoteImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { + + @Override + public String getImportSource() { + return "EndnoteMetadataSource"; + } + + @Override + protected List readData(InputStream fileInpuStream) throws FileSourceException { + List list = new ArrayList<>(); + try { + int lineForDebug = 3; + List tokenized = tokenize(fileInpuStream); + List tmpList = new ArrayList<>(); + for (PlainMetadataKeyValueItem item : tokenized) { + if (item.getKey() == null || item.getKey().isEmpty()) { + throw new FileSourceException("Null or empty key expected on line " + + lineForDebug + ". Keys cannot be null nor empty"); + } + if ("EF".equals(item.getKey())) { + break; + } + if ("ER".equals(item.getKey())) { + PlainMetadataSourceDto dto = new PlainMetadataSourceDto(); + dto.setMetadata(new ArrayList<>(tmpList)); + list.add(dto); + tmpList = new ArrayList<>(); + } else { + if (item.getValue() == null || item.getValue().isEmpty()) { + throw new FileSourceException("Null or empty value expected on line " + + lineForDebug + ". Value expected"); + } + tmpList.add(item); + } + lineForDebug++; + } + } catch (Exception e) { + throw new FileSourceException("Error reading file"); + } + return list; + } + + + private List tokenize(InputStream fileInpuStream) + throws IOException, FileSourceException { + BufferedReader reader = new BufferedReader(new InputStreamReader(fileInpuStream)); + String line; + line = reader.readLine(); + if (line == null || !line.startsWith("FN")) { + throw new FileSourceException("Invalid endNote file"); + } + line = reader.readLine(); + if (line == null || !line.startsWith("VR")) { + throw new FileSourceException("Invalid endNote file"); + } + Pattern pattern = Pattern.compile("(^[A-Z]{2}) ?(.*)$"); + List list = new ArrayList(); + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.equals("")) { + continue; + } + Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + PlainMetadataKeyValueItem item = new PlainMetadataKeyValueItem(); + item.setKey(matcher.group(1)); + item.setValue(matcher.group(2)); + list.add(item); + } + } + return list; + } + + @Override + public void setMetadataFieldMap(Map> metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index e356987787..dc988310f1 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -71,6 +71,12 @@ + + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index c312faa831..f2a4b1bb99 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -871,7 +871,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration * * @throws Exception */ - public void createSingleWorkspaceItemFromFileWithOneEntryTest() throws Exception { + public void createSingleWorkspaceItemFromBibtexFileWithOneEntryTest() throws Exception { context.turnOffAuthorisationSystem(); //** GIVEN ** @@ -938,7 +938,170 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration bibtex.close(); } + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a csv file + * + * @throws Exception + */ + public void createSingleWorkspaceItemFromCSVWithOneEntryTest() throws Exception { + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream csv = getClass().getResourceAsStream("csv-test.csv"); + final MockMultipartFile csvFile = new MockMultipartFile("file", "/local/path/csv-test.csv", + "text/csv", csv); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + // bulk create workspaceitems in the default collection (col1) + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(csvFile)) + // bulk create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.author'][0].value", + is("Nobody"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.date.issued'][0].value", + is("2006"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.source'][0].value", + is("My Journal"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.identifier.issn'][0].value", + is("Mock ISSN"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.type'][0].value", + is("Mock subtype"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/csv-test.csv"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("csv-test.csv"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); + ; + + // bulk create workspaceitems explicitly in the col2 + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(csvFile) + .param("owningCollection", col2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.title'][0].value", + is("My Article"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.author'][0].value", + is("Nobody"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.date.issued'][0].value", + is("2006"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.source'][0].value", + is("My Journal"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.identifier.issn'][0].value", + is("Mock ISSN"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.type'][0].value", + is("Mock subtype"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col2.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/csv-test.csv"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload" + + ".files[0].metadata['dc.title'][0].value", + is("csv-test.csv"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); + csv.close(); + } + + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a csv file + * with some missing data + * + * @throws Exception + */ + public void createSingleWorkspaceItemFromCSVWithOneEntryAndMissingDataTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream csv = getClass().getResourceAsStream("bibtex-test.bib"); + final MockMultipartFile csvFile = new MockMultipartFile("file", "/local/path/csv-missing-field-test.csv", + "text/csv", csv); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + // bulk create workspaceitems in the default collection (col1) + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(csvFile)) + // bulk create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.author'][0].value", + is("Nobody"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.date.issued'][0].value").doesNotExist()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.source'][0].value", + is("My Journal"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" + + "['dc.identifier.issn'][0].value", + is("Mock ISSN"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.type'][0].value" + ).doesNotExist()) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/csv-missing-field-test.csv"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("csv-missing-field-test.csv"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()); + ; + + csv.close(); + } @Test /** diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-missing-field-test.csv b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-missing-field-test.csv new file mode 100644 index 0000000000..58aecd1c8f --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-missing-field-test.csv @@ -0,0 +1 @@ +My article,Nobody,,My Journal,"This is my abstract, i user comma to check escape works fine",Mock ISSN \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-test.csv b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-test.csv new file mode 100644 index 0000000000..d8bf63871c --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/csv-test.csv @@ -0,0 +1 @@ +My article,Nobody,2006,My Journal,"This is my abstract, i user comma to check escape works fine",Mock ISSN,Mock subtype \ No newline at end of file diff --git a/dspace/config/spring/api/endnote-integration.xml b/dspace/config/spring/api/endnote-integration.xml new file mode 100644 index 0000000000..15ff3ca6f7 --- /dev/null +++ b/dspace/config/spring/api/endnote-integration.xml @@ -0,0 +1,52 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e52e88004565525c4e501bf943dd5b0fb542911a Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 7 Jul 2020 14:39:40 +0200 Subject: [PATCH 183/465] [Task 71690] implemented the IPAuthenticationFilter --- .../rest/security/IPAuthenticationFilter.java | 67 +++++++++++++++++++ .../security/WebSecurityConfiguration.java | 7 +- dspace/config/modules/authentication.cfg | 2 +- 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java new file mode 100644 index 0000000000..e0f4d189c4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java @@ -0,0 +1,67 @@ +/** + * 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.security; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authenticate.service.AuthenticationService; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +/** + * This is a Filter class that'll fetch special groups from the {@link AuthenticationService} and set these in the + * current DSpace Context. This will allow us to set a specific Group to a specific IP so that any request from that + * IP is always treated as being a part of the configured group. + * The configuration for the authentication through ip can be fined in authentication-ip.cfg + * This can be enabled by uncommenting the IPAuhentication plugin in authentication.cfg + */ +public class IPAuthenticationFilter extends BasicAuthenticationFilter { + + private static final Logger log = Logger.getLogger(IPAuthenticationFilter.class); + + private AuthenticationService authenticationService; + + /** + * Constructor for the class + * @param authenticationManager The relevant AuthenticationManager + * @param authenticationService The autowired AuthenticationService + */ + public IPAuthenticationFilter(AuthenticationManager authenticationManager, + AuthenticationService authenticationService) { + super(authenticationManager); + this.authenticationService = authenticationService; + } + + @Override + protected void doFilterInternal(HttpServletRequest req, + HttpServletResponse res, + FilterChain chain) throws IOException, ServletException { + + Context context = ContextUtil.obtainContext(req); + try { + List groups = authenticationService.getSpecialGroups(context, req); + for (Group group : groups) { + context.setSpecialGroup(group.getID()); + } + } catch (SQLException e) { + log.error("Something went wrong trying to fetch groups in IPAuthenticationFilter", e); + } + chain.doFilter(req, res); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 32c0cdda00..c5b5dff39d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.security; +import org.dspace.authenticate.service.AuthenticationService; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; @@ -53,6 +54,9 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private CustomLogoutHandler customLogoutHandler; + @Autowired + private AuthenticationService authenticationService; + @Override public void configure(WebSecurity webSecurity) throws Exception { webSecurity @@ -103,7 +107,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { //Everyone can call GET on the status endpoint .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() .and() - + .addFilterBefore(new IPAuthenticationFilter(authenticationManager(), authenticationService), + StatelessAuthenticationFilter.class) //Add a filter before our login endpoints to do the authentication based on the data in the HTTP request .addFilterBefore(new StatelessLoginFilter("/api/authn/login", authenticationManager(), restAuthenticationService), diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index f22e2eaf19..fab1f577d2 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -34,7 +34,7 @@ # defining a new order in local.cfg. # IP-based authentication/authorization. See authentication-ip.cfg for default configuration. -#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.IPAuthentication +plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.IPAuthentication # LDAP authentication/authorization. See authentication-ldap.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.LDAPAuthentication From f34f44d7e00f5258ef875b3e2ddd2ac88dde5920 Mon Sep 17 00:00:00 2001 From: Ian Little Date: Tue, 7 Jul 2020 09:52:38 -0400 Subject: [PATCH 184/465] Bump Handle to version 9.3.0-SNAPSHOT, now coming form CNRI maven repository. Requires bumping Jetty version also. --- dspace-api/pom.xml | 63 +++++++++++++++++++- dspace-server-webapp/pom.xml | 8 +++ dspace/config/log4j-handle-plugin.properties | 4 +- dspace/modules/server/pom.xml | 8 +++ pom.xml | 17 ++++-- 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 49ca062aa6..7a8662bcff 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -291,9 +291,20 @@ - org.dspace + net.handle handle + + net.cnri + cnri-servlet-container + + + + org.ow2.asm + asm-commons + + + org.eclipse.jetty @@ -471,11 +482,59 @@ solr-cell ${solr.client.version} - + org.ow2.asm asm-commons + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcprov-jdk15on + + + org.eclipse.jetty + jetty-xml + + + org.eclipse.jetty + jetty-http + + + org.eclipse.jetty + jetty-servlet + + + org.eclipse.jetty + jetty-webapp + + + org.eclipse.jetty + jetty-util + + + org.eclipse.jetty + jetty-deploy + + + org.eclipse.jetty + jetty-continuation + + + org.eclipse.jetty + jetty-servlets + + + org.eclipse.jetty + jetty-io + + + org.eclipse.jetty + jetty-security + diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index d1f54fd9d6..ccb78dde49 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -460,6 +460,14 @@ solr-cell test + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcprov-jdk15on + org.eclipse.jetty jetty-continuation diff --git a/dspace/config/log4j-handle-plugin.properties b/dspace/config/log4j-handle-plugin.properties index 72381a698c..44d39fb1bd 100644 --- a/dspace/config/log4j-handle-plugin.properties +++ b/dspace/config/log4j-handle-plugin.properties @@ -20,12 +20,12 @@ log.dir=${dspace.dir}/log log4j.rootCategory=INFO, A1 # A1 is set to be a DailyRollingFileAppender. -log4j.appender.A1=org.apache.logging.log4j.DailyRollingFileAppender +log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender log4j.appender.A1.File=${log.dir}/handle-plugin.log log4j.appender.A1.DatePattern='.'yyyy-MM-dd # A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.logging.log4j.PatternLayout +log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%d %-5p %c @ %m%n diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 4ecf2cf5b2..174d6eb019 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -266,6 +266,14 @@ just adding new jar in the classloader solr-cell test + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcprov-jdk15on + org.eclipse.jetty jetty-continuation diff --git a/pom.xml b/pom.xml index a3b4b30c98..5f50f577bb 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ 2.3.1 2.3.1 - 9.4.8.v20171121 + 9.4.15.v20190215 2.11.2 2.0.15 3.17 @@ -1236,9 +1236,14 @@ - org.dspace + net.handle handle - 9.1.0.v20190416 + 9.3.0-SNAPSHOT + + + net.cnri + cnri-servlet-container + 3.0.0-SNAPSHOT @@ -1590,7 +1595,7 @@ com.google.code.gson gson - 2.6.1 + 2.8.6 compile @@ -1842,6 +1847,10 @@ true + + handle.net + https://handle.net/maven + From dc958c9d670082987597932712f5b4203b56c658 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 8 Jul 2020 00:06:49 +0200 Subject: [PATCH 185/465] Add support for multiple authors in *sv, complete implementation for RIS and Endnote, complete IT --- ...aratedImportMetadataSourceServiceImpl.java | 40 ++- .../EnhancedSimpleMetadataContributor.java | 94 ++++++ .../SimpleMetadataContributor.java | 7 + ...PubmedImportMetadataSourceServiceImpl.java | 12 + .../external/service/ImportService.java | 9 +- .../AbstractPlainMetadataSource.java | 11 + .../service/components/FileSource.java | 21 ++ .../spring-dspace-addon-import-services.xml | 36 ++- .../WorkspaceItemRestRepository.java | 5 +- .../rest/WorkspaceItemRestRepositoryIT.java | 278 +++++++++++++++++- .../app/rest/csv-missing-field-test.csv | 3 +- .../org/dspace/app/rest/csv-test.csv | 3 +- .../org/dspace/app/rest/endnote-test.enw | 10 + .../org/dspace/app/rest/ris-test.ris | 19 ++ .../app/rest/tsv-missing-field-test.tsv | 2 + .../org/dspace/app/rest/tsv-test.tsv | 2 + .../api/characterseparated-integration.xml | 6 +- 17 files changed, 524 insertions(+), 34 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/endnote-test.enw create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/ris-test.ris create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/tsv-missing-field-test.tsv create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/tsv-test.tsv diff --git a/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java index 75f2cb0776..3c1a838d7d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java @@ -27,14 +27,24 @@ import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDt public class CharacterSeparatedImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { - private String separator = ","; + private char separator = ','; - private String escapeCharacter = "\""; + private char escapeCharacter = '"'; + + private Integer skipLines = 1; private String importSource = "CsvMetadataSource"; - public void setSeparator(String separator) { - this.separator = separator; + public void setSkipLines(Integer skipLines) { + this.skipLines = skipLines; + } + + public Integer getSkipLines() { + return skipLines; + } + + public void setSeparator(int separator) { + this.separator = (char)separator; } @Override @@ -46,32 +56,36 @@ public class CharacterSeparatedImportMetadataSourceServiceImpl extends AbstractP this.importSource = importSource; } - public void setEscapeCharacter(String escapeCharacter) { - this.escapeCharacter = escapeCharacter; + public void setEscapeCharacter(int escapeCharacter) { + this.escapeCharacter = (char)escapeCharacter; } @Override protected List readData(InputStream inputStream) throws FileSourceException { List plainMetadataList = new ArrayList<>(); try (CSVReader csvReader = new CSVReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8), - separator.charAt(0), escapeCharacter.charAt(0));) { + separator, escapeCharacter);) { List lines = csvReader.readAll(); - for (String [] items : lines) { + int listSize = lines == null ? 0 : lines.size(); + int count = skipLines; + while (count < listSize) { + String [] items = lines.get(count); List keyValueList = new ArrayList<>(); if (items != null) { int size = items.length; - int count = 0; - while (count < size) { + int index = 0; + while (index < size) { PlainMetadataKeyValueItem keyValueItem = new PlainMetadataKeyValueItem(); - keyValueItem.setKey(String.valueOf(count)); - keyValueItem.setValue(items[count]); + keyValueItem.setKey(String.valueOf(index)); + keyValueItem.setValue(items[index]); keyValueList.add(keyValueItem); - count++; + index++; } PlainMetadataSourceDto dto = new PlainMetadataSourceDto(); dto.setMetadata(keyValueList); plainMetadataList.add(dto); } + count++; } } catch (IOException e) { throw new FileSourceException("Error reading file", e); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java new file mode 100644 index 0000000000..0d2d009f7b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java @@ -0,0 +1,94 @@ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import au.com.bytecode.opencsv.CSVReader; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; +import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; + + +/** + * This class implements functionalities to handle common situation regarding plain metadata. + * In some scenario, like csv or tsv, the format don't allow lists. + * We can use this MetadataContribut to parse a given plain metadata and split it into + * related list, based on the delimiter. No escape character is present. + * Default values are comma (,) for delimiter, and double quote (") for escape character + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ +public class EnhancedSimpleMetadataContributor extends SimpleMetadataContributor { + + private char delimiter = ','; + + private char escape = '"'; + + private boolean useEnhancer; + + public void setDelimiter(int delimiter) { + this.delimiter = (char)delimiter; + } + + public char getDelimiter() { + return delimiter; + } + + public void setEscape(int escape) { + this.escape = (char)escape; + } + + public char getEscape() { + return escape; + } + + public void setUseEnhancer(boolean useEnhancer) { + this.useEnhancer = useEnhancer; + } + + public boolean isUseEnhancer() { + return useEnhancer; + } + + @Override + public Collection contributeMetadata(PlainMetadataSourceDto t) { + Collection values = null; + if (!useEnhancer) { + values = super.contributeMetadata(t); + } else { + values = new LinkedList<>(); + for (PlainMetadataKeyValueItem metadatum : t.getMetadata()) { + if (getKey().equals(metadatum.getKey())) { + String[] splitted = splitToRecord(metadatum.getValue()); + for (String value : splitted) { + MetadatumDTO dcValue = new MetadatumDTO(); + dcValue.setValue(value); + dcValue.setElement(getField().getElement()); + dcValue.setQualifier(getField().getQualifier()); + dcValue.setSchema(getField().getSchema()); + values.add(dcValue); + } + } + } + } + return values; + } + + private String[] splitToRecord(String value) { + List rows; + try (CSVReader csvReader = new CSVReader(new StringReader(value), + delimiter, escape);) { + rows = csvReader.readAll(); + } catch (IOException e) { + //fallback, use the inpu as value + return new String[] { value }; + } + //must be one row + return rows.get(0); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java index 21dd1bfcee..e61b3d2187 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java @@ -91,4 +91,11 @@ public class SimpleMetadataContributor implements MetadataContributor supportedExtensions; + + public void setSupportedExtensions(List supportedExtensions) { + this.supportedExtensions = supportedExtensions; + } + + @Override + public List getSupportedExtensions() { + return supportedExtensions; + } + /** * Find the number of records matching a query; * @@ -430,4 +441,5 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat return records; } + } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java index 5a838e8027..35d8e59eb9 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/ImportService.java @@ -305,14 +305,17 @@ public class ImportService implements Destroyable { * @return a single record contains the metadatum * @throws FileMultipleOccurencesException if more than one entry is found */ - public ImportRecord getRecord(File file) throws FileMultipleOccurencesException, FileSourceException { + public ImportRecord getRecord(File file, String originalName) + throws FileMultipleOccurencesException, FileSourceException { ImportRecord importRecords = null; for (MetadataSource metadataSource : importSources.values()) { try (InputStream fileInputStream = new FileInputStream(file)) { if (metadataSource instanceof FileSource) { FileSource fileSource = (FileSource)metadataSource; - importRecords = fileSource.getRecord(fileInputStream); - break; + if (fileSource.isValidSourceForFile(originalName)) { + importRecords = fileSource.getRecord(fileInputStream); + break; + } } } catch (FileSourceException e) { log.debug(metadataSource.getImportSource() + " isn't a valid parser for file"); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java index 45b96f10b9..39f80cdd72 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java @@ -33,6 +33,17 @@ public abstract class AbstractPlainMetadataSource extends AbstractMetadataFieldMapping implements FileSource { + private List supportedExtensions; + + public void setSupportedExtensions(List supportedExtensions) { + this.supportedExtensions = supportedExtensions; + } + + @Override + public List getSupportedExtensions() { + return supportedExtensions; + } + protected abstract List readData(InputStream fileInpuStream) throws FileSourceException; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java index ff05b8cdfb..16a3b0629d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java @@ -22,6 +22,8 @@ import org.dspace.importer.external.exception.FileSourceException; */ public interface FileSource extends MetadataSource { + public List getSupportedExtensions(); + /** * Return a list of ImportRecord constructed from input file. * @@ -43,4 +45,23 @@ public interface FileSource extends MetadataSource { public ImportRecord getRecord(InputStream inputStream) throws FileSourceException, FileMultipleOccurencesException; + + /** + * This method is used to decide if the FileSource manage the file format + * + * @param originalName the file file original name + * @return true if the FileSource can parse the file, false otherwise + */ + public default boolean isValidSourceForFile(String originalName) { + List extensions = getSupportedExtensions(); + if (extensions == null || extensions.isEmpty()) { + return false; + } + if (originalName != null && originalName.contains(".")) { + String extension = originalName.substring(originalName.lastIndexOf('.') + 1, + originalName.length()); + return getSupportedExtensions().contains(extension); + } + return false; + } } diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index dc988310f1..64627f68fc 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -44,6 +44,11 @@ class="org.dspace.importer.external.pubmed.service.PubmedImportMetadataSourceServiceImpl" scope="singleton"> + + + xml + + @@ -53,27 +58,56 @@ + + + ris + + + + + bib + bibtex + + + + + + csv + + - + + + + + tsv + + + + + enl + enw + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index 3befc00a42..7ee157ab52 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -380,11 +380,14 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository - + + + + From 3f8e68055728405b11ca2dddf52810975b33f139 Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 8 Jul 2020 01:09:40 +0200 Subject: [PATCH 186/465] Add comments --- ...aratedImportMetadataSourceServiceImpl.java | 34 ++++++++++++++++++- ...ndnoteImportMetadataSourceServiceImpl.java | 12 +++++++ .../EnhancedSimpleMetadataContributor.java | 28 +++++++++++++++ .../SimpleMetadataContributor.java | 16 ++++++--- ...PubmedImportMetadataSourceServiceImpl.java | 6 ++++ .../AbstractPlainMetadataSource.java | 5 +++ .../service/components/FileSource.java | 3 ++ ...pper.xml => dublicore-metadata-mapper.xml} | 0 8 files changed, 99 insertions(+), 5 deletions(-) rename dspace/config/spring/api/{dublicore-metadata.mapper.xml => dublicore-metadata-mapper.xml} (100%) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java index 3c1a838d7d..92002123e8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/csv/service/CharacterSeparatedImportMetadataSourceServiceImpl.java @@ -20,11 +20,18 @@ import org.dspace.importer.external.exception.FileSourceException; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; import org.dspace.importer.external.metadatamapping.contributor.MetadataContributor; import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; +import org.dspace.importer.external.service.components.MetadataSource; import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; - +/** + * This class is an implementation of {@link MetadataSource} which extends {@link AbstractPlainMetadataSource} + * in order to parse "character separated" files like csv, tsv, etc using the Live Import framework. + * + * @author Pasquale Cavallo + * + */ public class CharacterSeparatedImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { private char separator = ','; @@ -35,14 +42,30 @@ public class CharacterSeparatedImportMetadataSourceServiceImpl extends AbstractP private String importSource = "CsvMetadataSource"; + /** + * Set the number of lines to skip at the start of the file. This method is suitable, + * for example, to skip file headers. + * + * @param skipLines number of the line at the start of the file to skip. + */ public void setSkipLines(Integer skipLines) { this.skipLines = skipLines; } + /** + * + * @return the number of the lines to skip + */ public Integer getSkipLines() { return skipLines; } + /** + * Method to inject the separator + * This must be the ASCII integer + * related to the char. + * In example, 9 for tab, 44 for comma + */ public void setSeparator(int separator) { this.separator = (char)separator; } @@ -52,10 +75,19 @@ public class CharacterSeparatedImportMetadataSourceServiceImpl extends AbstractP return importSource; } + /** + * Method to set the name of the source + */ public void setImportSource(String importSource) { this.importSource = importSource; } + /** + * Method to inject the escape character. This must be the ASCII integer + * related to the char. + * In example, 9 for tab, 44 for comma + * + */ public void setEscapeCharacter(int escapeCharacter) { this.escapeCharacter = (char)escapeCharacter; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java index e0bc10f238..995002e7ba 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/endnote/service/EndnoteImportMetadataSourceServiceImpl.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.importer.external.endnote.service; import java.io.BufferedReader; @@ -17,6 +24,11 @@ import org.dspace.importer.external.service.components.AbstractPlainMetadataSour import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDto; +/** + * Implements a metadata importer for Endnote files + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ public class EndnoteImportMetadataSourceServiceImpl extends AbstractPlainMetadataSource { @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java index 0d2d009f7b..19db700ff7 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java @@ -30,26 +30,54 @@ public class EnhancedSimpleMetadataContributor extends SimpleMetadataContributor private boolean useEnhancer; + /** + * This method could be used to set the delimiter used during parse + * If no delimiter is set, comma will be used + */ public void setDelimiter(int delimiter) { this.delimiter = (char)delimiter; } + /** + * This method could be used to get the delimiter used in this class + */ public char getDelimiter() { return delimiter; } + /** + * Method to inject the escape character. + * This must be the ASCII integer + * related to the char. + * In example, 9 for tab, 44 for comma + * If no escape is set, double quote will be used + */ public void setEscape(int escape) { this.escape = (char)escape; } + /** + * Method to get the escape character. + * + */ public char getEscape() { return escape; } + /** + * Method to set up the enhancer. If set to false, enhancing will be not used + * In this case, the metadata value will + * As default, it is valued as false + * + */ public void setUseEnhancer(boolean useEnhancer) { this.useEnhancer = useEnhancer; } + /** + * + * @return true if the enhancer is set up, false otherwise. + */ public boolean isUseEnhancer() { return useEnhancer; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java index e61b3d2187..1b9007f23c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java @@ -77,24 +77,32 @@ public class SimpleMetadataContributor implements MetadataContributor implements QuerySource, FileSource { @@ -54,6 +55,11 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat private List supportedExtensions; + /** + * Set the file extensions supported by this metadata service + * + * @param supportedExtensionsthe file extensions (xml,txt,...) supported by this service + */ public void setSupportedExtensions(List supportedExtensions) { this.supportedExtensions = supportedExtensions; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java index 39f80cdd72..6c18ce20bc 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java @@ -35,6 +35,11 @@ public abstract class AbstractPlainMetadataSource private List supportedExtensions; + /** + * Set the file extensions supported by the implementation of this abstract + * + * @param supportedExtensionsthe file extensions (xml,txt,...) supported by the specific implementation + */ public void setSupportedExtensions(List supportedExtensions) { this.supportedExtensions = supportedExtensions; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java index 16a3b0629d..0d46da385b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java @@ -22,6 +22,9 @@ import org.dspace.importer.external.exception.FileSourceException; */ public interface FileSource extends MetadataSource { + /** + * Get the file extensions (xml, csv, txt, ...) supported by the FileSource + */ public List getSupportedExtensions(); /** diff --git a/dspace/config/spring/api/dublicore-metadata.mapper.xml b/dspace/config/spring/api/dublicore-metadata-mapper.xml similarity index 100% rename from dspace/config/spring/api/dublicore-metadata.mapper.xml rename to dspace/config/spring/api/dublicore-metadata-mapper.xml From 493ab0f496724c6000d17736a564f4be8824e9bb Mon Sep 17 00:00:00 2001 From: Pasquale Cavallo Date: Wed, 8 Jul 2020 01:49:08 +0200 Subject: [PATCH 187/465] Add license header --- .../contributor/EnhancedSimpleMetadataContributor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java index 19db700ff7..93f59b8efd 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.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.importer.external.metadatamapping.contributor; import java.io.IOException; From 4e129cf841e7c103b77e488858f46d3a9744f1c3 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 8 Jul 2020 10:23:08 +0200 Subject: [PATCH 188/465] [Task 71753] implemented feedback and added tests for the ip authentication functionality --- .../test/data/dspaceFolder/config/local.cfg | 3 + ...onymousAdditionalAuthorizationFilter.java} | 12 +- .../security/WebSecurityConfiguration.java | 2 +- ...nymousAdditionalAuthorizationFilterIT.java | 144 ++++++++++++++++++ .../AbstractControllerIntegrationTest.java | 17 +++ dspace/config/modules/authentication.cfg | 2 +- 6 files changed, 173 insertions(+), 7 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/security/{IPAuthenticationFilter.java => AnonymousAdditionalAuthorizationFilter.java} (78%) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 51ce1a0165..2455c68cfd 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -120,3 +120,6 @@ rest.properties.exposed = configuration.not.existing configuration.not.exposed = secret_value configuration.exposed.single.value = public_value configuration.exposed.array.value = public_value_1, public_value_2 + +authentication-ip.Staff = 5.5.5.5 +authentication-ip.Student = 6.6.6.6 diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java similarity index 78% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java index e0f4d189c4..ef040daf12 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/IPAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java @@ -25,14 +25,16 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi /** * This is a Filter class that'll fetch special groups from the {@link AuthenticationService} and set these in the - * current DSpace Context. This will allow us to set a specific Group to a specific IP so that any request from that + * current DSpace Context. It'll do extra processing on anonymous requests to see which authorizations they + * can implicitly have and adds those + * This will allow us to for example set a specific Group to a specific IP so that any request from that * IP is always treated as being a part of the configured group. * The configuration for the authentication through ip can be fined in authentication-ip.cfg * This can be enabled by uncommenting the IPAuhentication plugin in authentication.cfg */ -public class IPAuthenticationFilter extends BasicAuthenticationFilter { +public class AnonymousAdditionalAuthorizationFilter extends BasicAuthenticationFilter { - private static final Logger log = Logger.getLogger(IPAuthenticationFilter.class); + private static final Logger log = Logger.getLogger(AnonymousAdditionalAuthorizationFilter.class); private AuthenticationService authenticationService; @@ -41,8 +43,8 @@ public class IPAuthenticationFilter extends BasicAuthenticationFilter { * @param authenticationManager The relevant AuthenticationManager * @param authenticationService The autowired AuthenticationService */ - public IPAuthenticationFilter(AuthenticationManager authenticationManager, - AuthenticationService authenticationService) { + public AnonymousAdditionalAuthorizationFilter(AuthenticationManager authenticationManager, + AuthenticationService authenticationService) { super(authenticationManager); this.authenticationService = authenticationService; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index c5b5dff39d..4471262ef6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -107,7 +107,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { //Everyone can call GET on the status endpoint .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() .and() - .addFilterBefore(new IPAuthenticationFilter(authenticationManager(), authenticationService), + .addFilterBefore(new AnonymousAdditionalAuthorizationFilter(authenticationManager(), authenticationService), StatelessAuthenticationFilter.class) //Add a filter before our login endpoints to do the authentication based on the data in the HTTP request .addFilterBefore(new StatelessLoginFilter("/api/authn/login", authenticationManager(), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java new file mode 100644 index 0000000000..779bbabee8 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java @@ -0,0 +1,144 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.GroupBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class AnonymousAdditionalAuthorizationFilterIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private GroupService groupService; + + public static final String[] IP = {"org.dspace.authenticate.IPAuthentication"}; + public static final String[] IP_AND_PASS = + {"org.dspace.authenticate.IPAuthentication", + "org.dspace.authenticate.PasswordAuthentication"}; + public static final String[] PASS = {"org.dspace.authenticate.PasswordAuthentication"}; + + + Item publicItem1; + Group staff; + + @Before + public void setup() { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + staff = GroupBuilder.createGroup(context).withName("Staff").build(); + + //2. Three public items that are readable by Anonymous with different subjects + publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .withReaderGroup(staff) + .build(); + + } + + @Test + public void verifyIPAuthentication() throws Exception { + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", IP); + + getClient().perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "5.5.5.5")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-FORWARDED-FOR", "6.6.6.6")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void verifyIPAndPasswordAuthentication() throws Exception { + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", IP_AND_PASS); + + groupService.addMember(context, staff, eperson); + + getClient().perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "5.5.5.5")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "6.6.6.6")) + .andExpect(status().isUnauthorized()); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isOk()); + + getClient(getAuthTokenWithXForwardedForHeader(eperson.getEmail(), password, "6.6.6.6")) + .perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "6.6.6.6")) + .andExpect(status().isOk()); + } + + @Test + public void verifyPasswordAuthentication() throws Exception { + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", PASS); + + groupService.addMember(context, staff, eperson); + + getClient().perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "5.5.5.5")) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "6.6.6.6")) + .andExpect(status().isUnauthorized()); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isOk()); + + getClient(getAuthTokenWithXForwardedForHeader(eperson.getEmail(), password, "6.6.6.6")) + .perform(get("/api/core/items/" + publicItem1.getID()) + .header("X-Forwarded-For", "6.6.6.6")) + .andExpect(status().isOk()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index de9003b2fe..c470e8a045 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -134,12 +134,29 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi .andReturn().getResponse(); } + public MockHttpServletResponse getAuthResponseWithXForwardedForHeader(String user, String password, + String xForwardedFor) throws Exception { + return getClient().perform(post("/api/authn/login") + .param("user", user) + .param("password", password) + .header("X-Forwarded-For", xForwardedFor)) + .andReturn().getResponse(); + } + + public String getAuthToken(String user, String password) throws Exception { return StringUtils.substringAfter( getAuthResponse(user, password).getHeader(AUTHORIZATION_HEADER), AUTHORIZATION_TYPE); } + public String getAuthTokenWithXForwardedForHeader(String user, String password, String xForwardedFor) + throws Exception { + return StringUtils.substringAfter( + getAuthResponseWithXForwardedForHeader(user, password, xForwardedFor).getHeader(AUTHORIZATION_HEADER), + AUTHORIZATION_TYPE); + } + public String getPatchContent(List ops) { ObjectMapper objectMapper = new ObjectMapper(); try { diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index fab1f577d2..f22e2eaf19 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -34,7 +34,7 @@ # defining a new order in local.cfg. # IP-based authentication/authorization. See authentication-ip.cfg for default configuration. -plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.IPAuthentication +#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.IPAuthentication # LDAP authentication/authorization. See authentication-ldap.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.LDAPAuthentication From 9184189bb09d132dd9d5763943041691553996e2 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 8 Jul 2020 10:32:53 +0200 Subject: [PATCH 189/465] [Task 71690] added javadoc for the tests and config --- .../test/data/dspaceFolder/config/local.cfg | 1 + ...nymousAdditionalAuthorizationFilterIT.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 2455c68cfd..093e1742bb 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -121,5 +121,6 @@ configuration.not.exposed = secret_value configuration.exposed.single.value = public_value configuration.exposed.array.value = public_value_1, public_value_2 +# Test config for the authentication ip functionality authentication-ip.Staff = 5.5.5.5 authentication-ip.Student = 6.6.6.6 diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java index 779bbabee8..f520df05f4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java @@ -25,6 +25,9 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +/** + * Testing class for the {@link org.dspace.app.rest.security.AnonymousAdditionalAuthorizationFilter} filter + */ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractControllerIntegrationTest { @Autowired @@ -74,13 +77,16 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController public void verifyIPAuthentication() throws Exception { configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", IP); + // Make sure that the item is not accessible for anonymous getClient().perform(get("/api/core/items/" + publicItem1.getID())) .andExpect(status().isUnauthorized()); + // Test that we can access the item using the IP that's configured for the Staff group getClient().perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "5.5.5.5")) .andExpect(status().isOk()); + // Test that we can't access the item using the IP that's configured for the Students group getClient().perform(get("/api/core/items/" + publicItem1.getID()) .header("X-FORWARDED-FOR", "6.6.6.6")) .andExpect(status().isUnauthorized()); @@ -92,22 +98,28 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController groupService.addMember(context, staff, eperson); + // Make sure that the item is not accessible for anonymous getClient().perform(get("/api/core/items/" + publicItem1.getID())) .andExpect(status().isUnauthorized()); + // Test that we can access the item using the IP that's configured for the Staff group getClient().perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "5.5.5.5")) .andExpect(status().isOk()); + // Test that we can't access the item using the IP that's configured for the Students group getClient().perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) .andExpect(status().isUnauthorized()); String token = getAuthToken(eperson.getEmail(), password); + // Test that the user in the Staff group can access the Item with the normal password authentication getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) .andExpect(status().isOk()); + // Test that the user in the Staff group can access the Item with the normal password authentication even + // when it's IP is configured to be part of the students group getClient(getAuthTokenWithXForwardedForHeader(eperson.getEmail(), password, "6.6.6.6")) .perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) @@ -120,22 +132,30 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController groupService.addMember(context, staff, eperson); + // Make sure that the item is not accessible for anonymous getClient().perform(get("/api/core/items/" + publicItem1.getID())) .andExpect(status().isUnauthorized()); + // Test that the Item can't be accessed with the IP for the Staff group if the config is turned off and only + // allows password authentication getClient().perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "5.5.5.5")) .andExpect(status().isUnauthorized()); + // Test that the Item can't be accessed with the IP for the Students group if the config is turned off and only + // allows password authentication getClient().perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) .andExpect(status().isUnauthorized()); String token = getAuthToken(eperson.getEmail(), password); + // Test that the Item is accessible for a user in the Staff group by password login getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) .andExpect(status().isOk()); + // Test that the Item is accessible for a user in the Staff group by password Login when the request + // is coming from the IP that's configured to be for the Student group getClient(getAuthTokenWithXForwardedForHeader(eperson.getEmail(), password, "6.6.6.6")) .perform(get("/api/core/items/" + publicItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) From dfe33d273e2c1189b4eae54041e8c45914e07c17 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 7 Jul 2020 17:43:36 +0200 Subject: [PATCH 190/465] 71701: item link on BundleRest + embedded parent tests for bundle and bitstream --- .../org/dspace/app/rest/model/BundleRest.java | 5 ++ .../repository/BundleItemLinkRepository.java | 59 +++++++++++++++++++ .../app/rest/BitstreamRestRepositoryIT.java | 53 +++++++++++++++++ .../app/rest/BundleRestRepositoryIT.java | 17 ++++++ .../app/rest/matcher/BundleMatcher.java | 4 +- 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java index 71f05c8333..dd4a80d488 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java @@ -16,6 +16,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ @LinksRest(links = { + @LinkRest( + name = BundleRest.ITEM, + method = "getItem" + ), @LinkRest( name = BundleRest.BITSTREAMS, method = "getBitstreams" @@ -30,6 +34,7 @@ public class BundleRest extends DSpaceObjectRest { public static final String PLURAL_NAME = "bundles"; public static final String CATEGORY = RestAddressableModel.CORE; + public static final String ITEM = "item"; public static final String BITSTREAMS = "bitstreams"; public static final String PRIMARY_BITSTREAM = "primaryBitstream"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java new file mode 100644 index 0000000000..9309829085 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.BundleService; +import org.dspace.core.Context; +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.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of an individual bundle. + */ +@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.ITEM) +public class BundleItemLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + BundleService bundleService; + + @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") + public ItemRest getItem(@Nullable HttpServletRequest request, + UUID bundleId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Bundle bundle = bundleService.find(context, bundleId); + if (bundle == null) { + throw new ResourceNotFoundException("No such bundle: " + bundleId); + } + Item item = bundle.getItems().get(0); + if (item == null) { + return null; + } + return converter.toRest(item, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 34bb56cbec..e609f02d7e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -30,11 +30,13 @@ import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.BitstreamMatcher; +import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -1143,5 +1145,56 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest } + @Test + public void getEmbeddedBundleForBitstream() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + } + + Bundle bundle = bitstream.getBundles().get(0); + + //Get the bitstream with embedded bundle + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "?embed=bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.bundle", + BundleMatcher.matchProperties( + bundle.getName(), + bundle.getID(), + bundle.getHandle(), + bundle.getType() + ) + )); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 07d6645c00..bdf3447124 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -37,6 +37,7 @@ import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.MetadataRest; @@ -636,4 +637,20 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()); } + @Test + public void getEmbeddedItemForBundle() throws Exception { + context.turnOffAuthorisationSystem(); + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "?embed=item")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.item", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java index 4fb5606293..812daabf58 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java @@ -48,7 +48,8 @@ public class BundleMatcher { public static Matcher matchFullEmbeds() { return matchEmbeds( "bitstreams[]", - "primaryBitstream" + "primaryBitstream", + "item" ); } @@ -57,6 +58,7 @@ public class BundleMatcher { */ public static Matcher matchLinks(UUID uuid) { return HalMatcher.matchLinks(REST_SERVER_URL + "core/bundles/" + uuid, + "item", "bitstreams", "primaryBitstream", "self" From 3243f19127f61f793598fe8e6dab19c522b0f5ec Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 8 Jul 2020 11:42:36 +0200 Subject: [PATCH 191/465] 71701: Additional parent link tests for Bitstream and Bundle + JavaDocs --- .../repository/BundleItemLinkRepository.java | 3 + .../app/rest/BitstreamRestRepositoryIT.java | 78 +++++++++++++++++++ .../app/rest/BundleRestRepositoryIT.java | 29 +++++++ 3 files changed, 110 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java index 9309829085..3d3e5226f3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -35,6 +35,9 @@ public class BundleItemLinkRepository extends AbstractDSpaceRestRepository @Autowired BundleService bundleService; + /** + * Get the first item the provided bundle resides in + */ @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") public ItemRest getItem(@Nullable HttpServletRequest request, UUID bundleId, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index e609f02d7e..9cbb5363b6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -24,6 +24,7 @@ import java.util.UUID; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.builder.BitstreamBuilder; +import org.dspace.app.rest.builder.BundleBuilder; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; @@ -1197,4 +1198,81 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest )); } + @Test + public void linksToFirstBundleWhenMultipleBundles() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + } + + Bundle secondBundle = BundleBuilder.createBundle(context, publicItem1) + .withName("second bundle") + .withBitstream(bitstream).build(); + + Bundle bundle = bitstream.getBundles().get(0); + + //Get bundle should contain the first bundle in the list + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", + BundleMatcher.matchProperties( + bundle.getName(), + bundle.getID(), + bundle.getHandle(), + bundle.getType() + ) + )); + } + + @Test + public void linksToEmptyWhenNoBundle() throws Exception { + // We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // ** GIVEN ** + // 1. A community with a logo + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").withLogo("logo_community") + .build(); + + // 2. A collection with a logo + Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection") + .withLogo("logo_collection").build(); + + Bitstream bitstream = parentCommunity.getLogo(); + + //Get bundle should contain an empty response + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isNoContent()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index bdf3447124..7e3dcead5a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -51,6 +51,7 @@ import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; @@ -64,6 +65,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired ResourcePolicyService resourcePolicyService; + @Autowired + ItemService itemService; + private Collection collection; private Item item; private Bundle bundle1; @@ -653,4 +657,29 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$._embedded.item", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); } + @Test + public void linksToFirstItemWhenMultipleItems() throws Exception { + context.turnOffAuthorisationSystem(); + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 2") + .withIssueDate("2020-07-08") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("SecondEntry") + .build(); + + itemService.addBundle(context, item2, bundle1); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/item")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + } + } From 3e33a866c6af5cd885770aba53aae92fdc323f6c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 8 Jul 2020 12:47:35 +0200 Subject: [PATCH 192/465] 71701: Checkstyle fixes --- .../java/org/dspace/app/rest/BundleRestRepositoryIT.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 7e3dcead5a..20590a1992 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -654,7 +654,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "?embed=item")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.item", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + .andExpect(jsonPath("$._embedded.item", + ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17") + )); } @Test @@ -679,7 +681,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/item")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + .andExpect(jsonPath("$", + ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17") + )); } } From 62cfd2d5bd98bcc688395a3c7ba89e77df003fb6 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 8 Jul 2020 13:40:47 +0200 Subject: [PATCH 193/465] [Task 71761] applied feedback to the AnonymousAdditionalAuthorizationFilter and IT --- ...nonymousAdditionalAuthorizationFilter.java | 2 +- ...nymousAdditionalAuthorizationFilterIT.java | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java index ef040daf12..3087a5850b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java @@ -29,7 +29,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi * can implicitly have and adds those * This will allow us to for example set a specific Group to a specific IP so that any request from that * IP is always treated as being a part of the configured group. - * The configuration for the authentication through ip can be fined in authentication-ip.cfg + * The configuration for the authentication through ip can be found in authentication-ip.cfg * This can be enabled by uncommenting the IPAuhentication plugin in authentication.cfg */ public class AnonymousAdditionalAuthorizationFilter extends BasicAuthenticationFilter { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java index f520df05f4..3202bd37c2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java @@ -43,7 +43,7 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController public static final String[] PASS = {"org.dspace.authenticate.PasswordAuthentication"}; - Item publicItem1; + Item staffAccessItem1; Group staff; @Before @@ -63,7 +63,7 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController staff = GroupBuilder.createGroup(context).withName("Staff").build(); //2. Three public items that are readable by Anonymous with different subjects - publicItem1 = ItemBuilder.createItem(context, col1) + staffAccessItem1 = ItemBuilder.createItem(context, col1) .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") @@ -78,16 +78,16 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", IP); // Make sure that the item is not accessible for anonymous - getClient().perform(get("/api/core/items/" + publicItem1.getID())) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID())) .andExpect(status().isUnauthorized()); // Test that we can access the item using the IP that's configured for the Staff group - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "5.5.5.5")) .andExpect(status().isOk()); // Test that we can't access the item using the IP that's configured for the Students group - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-FORWARDED-FOR", "6.6.6.6")) .andExpect(status().isUnauthorized()); } @@ -99,29 +99,29 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController groupService.addMember(context, staff, eperson); // Make sure that the item is not accessible for anonymous - getClient().perform(get("/api/core/items/" + publicItem1.getID())) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID())) .andExpect(status().isUnauthorized()); // Test that we can access the item using the IP that's configured for the Staff group - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "5.5.5.5")) .andExpect(status().isOk()); // Test that we can't access the item using the IP that's configured for the Students group - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) .andExpect(status().isUnauthorized()); String token = getAuthToken(eperson.getEmail(), password); // Test that the user in the Staff group can access the Item with the normal password authentication - getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) + getClient(token).perform(get("/api/core/items/" + staffAccessItem1.getID())) .andExpect(status().isOk()); // Test that the user in the Staff group can access the Item with the normal password authentication even // when it's IP is configured to be part of the students group getClient(getAuthTokenWithXForwardedForHeader(eperson.getEmail(), password, "6.6.6.6")) - .perform(get("/api/core/items/" + publicItem1.getID()) + .perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) .andExpect(status().isOk()); } @@ -133,31 +133,31 @@ public class AnonymousAdditionalAuthorizationFilterIT extends AbstractController groupService.addMember(context, staff, eperson); // Make sure that the item is not accessible for anonymous - getClient().perform(get("/api/core/items/" + publicItem1.getID())) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID())) .andExpect(status().isUnauthorized()); // Test that the Item can't be accessed with the IP for the Staff group if the config is turned off and only // allows password authentication - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "5.5.5.5")) .andExpect(status().isUnauthorized()); // Test that the Item can't be accessed with the IP for the Students group if the config is turned off and only // allows password authentication - getClient().perform(get("/api/core/items/" + publicItem1.getID()) + getClient().perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) .andExpect(status().isUnauthorized()); String token = getAuthToken(eperson.getEmail(), password); // Test that the Item is accessible for a user in the Staff group by password login - getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) + getClient(token).perform(get("/api/core/items/" + staffAccessItem1.getID())) .andExpect(status().isOk()); // Test that the Item is accessible for a user in the Staff group by password Login when the request // is coming from the IP that's configured to be for the Student group getClient(getAuthTokenWithXForwardedForHeader(eperson.getEmail(), password, "6.6.6.6")) - .perform(get("/api/core/items/" + publicItem1.getID()) + .perform(get("/api/core/items/" + staffAccessItem1.getID()) .header("X-Forwarded-For", "6.6.6.6")) .andExpect(status().isOk()); } From 56be1bbe745dd0a6202e31e27173f9e1110cc46e Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 8 Jul 2020 14:19:54 +0200 Subject: [PATCH 194/465] 71734: Sitemap REST controller --- .../app/rest/SitemapRestController.java | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java new file mode 100644 index 0000000000..310e2e5a39 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java @@ -0,0 +1,148 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.sql.SQLException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import javassist.NotFoundException; +import org.apache.catalina.connector.ClientAbortException; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.MultipartFileSender; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +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 is a specialized controller to provide access to the sitemap files, generated by + * {@link org.dspace.app.sitemap.GenerateSitemaps} + * + * The mapping for requested endpoint try to resolve a valid sitemap file name, for example + *

+ * {@code
+ * https:///api/discover/sitemaps/26453b4d-e513-44e8-8d5b-395f62972eff/sitemap0.html
+ * }
+ * 
+ * + * @author Maria Verdonck (Atmire) on 08/07/2020 + */ +@RestController +@RequestMapping("/api/" + RestModel.DISCOVER + "/sitemaps") +public class SitemapRestController { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SitemapRestController.class); + + @Autowired + ConfigurationService configurationService; + + // Most file systems are configured to use block sizes of 4096 or 8192 and our buffer should be a multiple of that. + private static final int BUFFER_SIZE = 4096 * 10; + + /** + * Tries to retrieve a matching sitemap file in configured location + * + * @param name the name of the requested sitemap file + * @param response the HTTP response + * @param request the HTTP request + * @throws NotFoundException if no matching sitemap file can be found + * @throws SQLException if db error while completing DSpace context + * @throws IOException if IO error surrounding sitemap file + */ + @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}, value = "/{name}") + public void retrieve(@PathVariable String name, HttpServletResponse response, + HttpServletRequest request) throws NotFoundException, IOException, SQLException { + // Find sitemap with given name in dspace/sitemaps + File foundSitemapFile = null; + File sitemapOutputDir = new File(configurationService.getProperty("sitemap.dir")); + if (sitemapOutputDir.exists() && sitemapOutputDir.isDirectory()) { + // List of all files and directories inside sitemapOutputDir + File sitemapFilesList[] = sitemapOutputDir.listFiles(); + for (File sitemapFile : sitemapFilesList) { + if (name.equalsIgnoreCase(sitemapFile.getName())) { + if (sitemapFile.isFile()) { + foundSitemapFile = sitemapFile; + } else { + throw new NotFoundException( + "Directory with name " + name + " in " + sitemapOutputDir.getAbsolutePath() + + " found, but no file"); + } + } + } + } else { + throw new NotFoundException("Sitemap directory in " + sitemapOutputDir.getAbsolutePath() + " does not " + + "exist, either sitemaps have not been generated, or are located elsewhere."); + } + if (foundSitemapFile == null) { + throw new NotFoundException( + "Could not find sitemap file with name " + name + " in " + sitemapOutputDir.getAbsolutePath()); + } else { + // return found sitemap file + this.returnSitemapFile(foundSitemapFile, response, request); + } + } + + /** + * Sends back the matching sitemap file as a MultipartFile, with the headers set with details of the file + * (content, size, name, last modified) + * + * @param foundSitemapFile the found sitemap file, with matching name as in request path + * @param response the HTTP response + * @param request the HTTP request + * @throws SQLException if db error while completing DSpace context + * @throws IOException if IO error surrounding sitemap file + */ + private void returnSitemapFile(File foundSitemapFile, HttpServletResponse response, HttpServletRequest request) + throws SQLException, IOException { + // Pipe the bits + try (InputStream is = new FileInputStream(foundSitemapFile)) { + MultipartFileSender sender = MultipartFileSender + .fromInputStream(is) + .withBufferSize(BUFFER_SIZE) + .withFileName(foundSitemapFile.getName()) + .withLength(foundSitemapFile.length()) + .withMimetype(Files.probeContentType(foundSitemapFile.toPath())) + .with(request) + .with(response); + + sender.withLastModified(foundSitemapFile.lastModified()); + + // Determine if we need to send the file as a download or if the browser can open it inline + long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold"); + if (dispositionThreshold >= 0 && foundSitemapFile.length() > dispositionThreshold) { + sender.withDisposition(MultipartFileSender.CONTENT_DISPOSITION_ATTACHMENT); + } + + Context context = ContextUtil.obtainContext(request); + + // We have all the data we need, close the connection to the database so that it doesn't stay open during + // download/streaming + context.complete(); + + // Send the data + if (sender.isValid()) { + sender.serveResource(); + } + + } catch (ClientAbortException e) { + log.debug("Client aborted the request before the download was completed. " + + "Client is probably switching to a Range request.", e); + } + } +} From 894738a6826a93719b419741a810eb819fdf7e6f Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 8 Jul 2020 15:44:58 +0200 Subject: [PATCH 195/465] 71736: Changes to sitemap contents --- .../dspace/app/sitemap/GenerateSitemaps.java | 23 ++++++++----------- .../app/sitemap/SitemapsOrgGenerator.java | 6 ++--- .../app/rest/SitemapRestController.java | 6 ++--- dspace/config/dspace.cfg | 2 +- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java index bb35cd3ff9..99517773a5 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java @@ -61,6 +61,8 @@ public class GenerateSitemaps { private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + public static final String SITEMAPS_ENDPOINT = "/api/discover/sitemaps"; + /** * Default constructor */ @@ -152,12 +154,9 @@ public class GenerateSitemaps { */ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) throws SQLException, IOException { - String sitemapStem = configurationService.getProperty("dspace.ui.url") - + "/sitemap"; - String htmlMapStem = configurationService.getProperty("dspace.ui.url") - + "/htmlmap"; - String handleURLStem = configurationService.getProperty("dspace.ui.url") - + "/handle/"; + String sitemapStem = configurationService.getProperty("dspace.server.url") + + SITEMAPS_ENDPOINT + "/sitemap"; + String uiURLStem = configurationService.getProperty("dspace.ui.url"); File outputDir = new File(configurationService.getProperty("sitemap.dir")); if (!outputDir.exists() && !outputDir.mkdir()) { @@ -168,13 +167,11 @@ public class GenerateSitemaps { AbstractGenerator sitemapsOrg = null; if (makeHTMLMap) { - html = new HTMLSitemapGenerator(outputDir, htmlMapStem + "?map=", - null); + html = new HTMLSitemapGenerator(outputDir, sitemapStem, ".html"); } if (makeSitemapOrg) { - sitemapsOrg = new SitemapsOrgGenerator(outputDir, sitemapStem - + "?map=", null); + sitemapsOrg = new SitemapsOrgGenerator(outputDir, sitemapStem, ".xml"); } Context c = new Context(Context.Mode.READ_ONLY); @@ -182,7 +179,7 @@ public class GenerateSitemaps { List comms = communityService.findAll(c); for (Community comm : comms) { - String url = handleURLStem + comm.getHandle(); + String url = uiURLStem + "/communities/" + comm.getID(); if (makeHTMLMap) { html.addURL(url, null); @@ -197,7 +194,7 @@ public class GenerateSitemaps { List colls = collectionService.findAll(c); for (Collection coll : colls) { - String url = handleURLStem + coll.getHandle(); + String url = uiURLStem + "/collections/" + coll.getID(); if (makeHTMLMap) { html.addURL(url, null); @@ -214,7 +211,7 @@ public class GenerateSitemaps { while (allItems.hasNext()) { Item i = allItems.next(); - String url = handleURLStem + i.getHandle(); + String url = uiURLStem + "/items/" + i.getID(); Date lastMod = i.getLastModified(); if (makeHTMLMap) { diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java b/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java index 9a0d5a6ba4..3ec4ca8239 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java @@ -59,7 +59,7 @@ public class SitemapsOrgGenerator extends AbstractGenerator { @Override public String getFilename(int number) { - return "sitemap" + number + ".xml.gz"; + return "sitemap" + number + ".xml"; } @Override @@ -100,12 +100,12 @@ public class SitemapsOrgGenerator extends AbstractGenerator { @Override public boolean useCompression() { - return true; + return false; } @Override public String getIndexFilename() { - return "sitemap_index.xml.gz"; + return "sitemap_index.xml"; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java index 310e2e5a39..dba5d2e46e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java @@ -19,9 +19,9 @@ import javax.servlet.http.HttpServletResponse; import javassist.NotFoundException; import org.apache.catalina.connector.ClientAbortException; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.MultipartFileSender; +import org.dspace.app.sitemap.GenerateSitemaps; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -44,7 +44,7 @@ import org.springframework.web.bind.annotation.RestController; * @author Maria Verdonck (Atmire) on 08/07/2020 */ @RestController -@RequestMapping("/api/" + RestModel.DISCOVER + "/sitemaps") +@RequestMapping(GenerateSitemaps.SITEMAPS_ENDPOINT) public class SitemapRestController { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SitemapRestController.class); @@ -81,7 +81,7 @@ public class SitemapRestController { } else { throw new NotFoundException( "Directory with name " + name + " in " + sitemapOutputDir.getAbsolutePath() + - " found, but no file"); + " found, but no file."); } } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5090f5ea34..4311887f96 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -28,7 +28,7 @@ dspace.server.url = http://localhost:8080/server # URL of DSpace frontend (Angular UI). Include port number etc # This is used by the backend to provide links in emails, RSS feeds, Sitemaps, etc. -dspace.ui.url = http://localhost:3000 +dspace.ui.url = http://localhost:4000 # Name of the site dspace.name = DSpace at My University From 0480b6521948b952cf2d85bcc3a901fd5a5bda82 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 8 Jul 2020 17:05:36 +0200 Subject: [PATCH 196/465] 71737: Sitemap REST IT --- .../app/rest/SitemapRestController.java | 23 +-- .../app/rest/utils/MultipartFileSender.java | 10 +- .../rest/RootRestResourceControllerIT.java | 9 +- .../app/rest/ShibbolethRestControllerIT.java | 7 +- .../app/rest/SitemapRestControllerIT.java | 142 ++++++++++++++++++ 5 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java index dba5d2e46e..41a0c96ffa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java @@ -16,15 +16,15 @@ import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javassist.NotFoundException; import org.apache.catalina.connector.ClientAbortException; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.MultipartFileSender; -import org.dspace.app.sitemap.GenerateSitemaps; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -44,7 +44,7 @@ import org.springframework.web.bind.annotation.RestController; * @author Maria Verdonck (Atmire) on 08/07/2020 */ @RestController -@RequestMapping(GenerateSitemaps.SITEMAPS_ENDPOINT) +@RequestMapping("/api/" + RestModel.DISCOVER + "/sitemaps") public class SitemapRestController { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SitemapRestController.class); @@ -61,13 +61,12 @@ public class SitemapRestController { * @param name the name of the requested sitemap file * @param response the HTTP response * @param request the HTTP request - * @throws NotFoundException if no matching sitemap file can be found - * @throws SQLException if db error while completing DSpace context - * @throws IOException if IO error surrounding sitemap file + * @throws SQLException if db error while completing DSpace context + * @throws IOException if IO error surrounding sitemap file */ @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}, value = "/{name}") public void retrieve(@PathVariable String name, HttpServletResponse response, - HttpServletRequest request) throws NotFoundException, IOException, SQLException { + HttpServletRequest request) throws IOException, SQLException { // Find sitemap with given name in dspace/sitemaps File foundSitemapFile = null; File sitemapOutputDir = new File(configurationService.getProperty("sitemap.dir")); @@ -79,18 +78,20 @@ public class SitemapRestController { if (sitemapFile.isFile()) { foundSitemapFile = sitemapFile; } else { - throw new NotFoundException( + throw new ResourceNotFoundException( "Directory with name " + name + " in " + sitemapOutputDir.getAbsolutePath() + " found, but no file."); } } } } else { - throw new NotFoundException("Sitemap directory in " + sitemapOutputDir.getAbsolutePath() + " does not " + - "exist, either sitemaps have not been generated, or are located elsewhere."); + throw new ResourceNotFoundException( + "Sitemap directory in " + sitemapOutputDir.getAbsolutePath() + " does not " + + "exist, either sitemaps have not been generated (./dspace generate-sitemaps)," + + " or are located elsewhere (config used: sitemap.dir)."); } if (foundSitemapFile == null) { - throw new NotFoundException( + throw new ResourceNotFoundException( "Could not find sitemap file with name " + name + " in " + sitemapOutputDir.getAbsolutePath()); } else { // return found sitemap file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/MultipartFileSender.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/MultipartFileSender.java index 4ae836bccf..284d0b87ab 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/MultipartFileSender.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/MultipartFileSender.java @@ -156,9 +156,13 @@ public class MultipartFileSender { // Initialize response. response.reset(); response.setBufferSize(bufferSize); - response.setHeader(CONTENT_TYPE, contentType); + if (contentType != null) { + response.setHeader(CONTENT_TYPE, contentType); + } response.setHeader(ACCEPT_RANGES, BYTES); - response.setHeader(ETAG, checksum); + if (checksum != null) { + response.setHeader(ETAG, checksum); + } response.setDateHeader(LAST_MODIFIED, lastModified); response.setDateHeader(EXPIRES, System.currentTimeMillis() + DEFAULT_EXPIRE_TIME); @@ -481,4 +485,4 @@ public class MultipartFileSender { return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java index 89ed50ec24..6ad1d23b18 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java @@ -14,8 +14,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; /** * Integration test for the {@link RootRestResourceController} @@ -25,6 +27,9 @@ import org.junit.Test; */ public class RootRestResourceControllerIT extends AbstractControllerIntegrationTest { + @Autowired + ConfigurationService configurationService; + @Test public void serverPropertiesTest() throws Exception { //When we call the root endpoint @@ -33,7 +38,7 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.dspaceURL", Matchers.is("http://localhost:3000"))) + .andExpect(jsonPath("$.dspaceURL", Matchers.is(configurationService.getProperty("dspace.ui.url")))) .andExpect(jsonPath("$.dspaceName", Matchers.is("DSpace at My University"))) .andExpect(jsonPath("$.dspaceRest", Matchers.is(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$.type", Matchers.is("root"))); @@ -71,4 +76,4 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT ; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java index a690e539ab..eff30a14c0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java @@ -12,7 +12,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.services.ConfigurationService; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; /** * Integration test that cover ShibbolethRestController @@ -21,13 +23,16 @@ import org.junit.Test; */ public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTest { + @Autowired + ConfigurationService configurationService; + @Test public void testRedirectToDefaultDspaceUrl() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/authn/shibboleth")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost:3000")); + .andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url"))); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java new file mode 100644 index 0000000000..7ef453d22d --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -0,0 +1,142 @@ +/** + * 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.builder.CollectionBuilder.createCollection; +import static org.dspace.app.rest.builder.CommunityBuilder.createCommunity; +import static org.dspace.app.rest.builder.ItemBuilder.createItem; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MvcResult; + +/** + * Integration test to test the /api/discover/sitemaps/{name} endpoint, see {@link SitemapRestController} + * + * @author Maria Verdonck (Atmire) on 08/07/2020 + */ +public class SitemapRestControllerIT extends AbstractControllerIntegrationTest { + + @Autowired + ConfigurationService configurationService; + + private final static String SITEMAPS_ENDPOINT = "/api/" + RestModel.DISCOVER + "/sitemaps"; + + private Item item1; + private Item item2; + + @Before + @Override + public void setUp() throws Exception { + + super.setUp(); + + context.turnOffAuthorisationSystem(); + + Community community = createCommunity(context).build(); + Collection collection = createCollection(context, community).build(); + this.item1 = createItem(context, collection) + .withTitle("Test 1") + .withIssueDate("2010-10-17") + .build(); + this.item2 = createItem(context, collection) + .withTitle("Test 2") + .withIssueDate("2015-8-3") + .build(); + + runDSpaceScript("generate-sitemaps"); + + context.restoreAuthSystemState(); + } + + @Test + public void testSitemap_notValidSiteMapFile() throws Exception { + //** WHEN ** + //We attempt to retrieve a non valid sitemap file + getClient().perform(get(SITEMAPS_ENDPOINT + "/notValidSiteMapFile")) + //** THEN ** + .andExpect(status().isNotFound()); + } + + @Test + public void testSitemap_sitemapIndexHtml() throws Exception { + //** WHEN ** + //We retrieve sitemap_index.html + MvcResult result = getClient().perform(get(SITEMAPS_ENDPOINT + "/sitemap_index.html")) + //** THEN ** + .andExpect(status().isOk()) + //We expect the content type to match + .andExpect(content().contentType("text/html")) + .andReturn(); + + String response = result.getResponse().getContentAsString(); + // contains a link to /api/discover/sitemaps/sitemap0.html + assertTrue(response.contains(SITEMAPS_ENDPOINT + "/sitemap0.html")); + } + + @Test + public void testSitemap_sitemap0Html() throws Exception { + //** WHEN ** + //We retrieve sitemap0.html + MvcResult result = getClient().perform(get(SITEMAPS_ENDPOINT + "/sitemap0.html")) + //** THEN ** + .andExpect(status().isOk()) + //We expect the content type to match + .andExpect(content().contentType("text/html")) + .andReturn(); + + String response = result.getResponse().getContentAsString(); + // contains a link to items: [dspace.ui.url]/items/ + assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item1.getID())); + assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item2.getID())); + } + + @Test + public void testSitemap_sitemapIndexXml() throws Exception { + //** WHEN ** + //We retrieve sitemap_index.xml + MvcResult result = getClient().perform(get(SITEMAPS_ENDPOINT + "/sitemap_index.xml")) + //** THEN ** + .andExpect(status().isOk()) + //We expect the content type to match + .andExpect(content().contentType("application/xml")) + .andReturn(); + + String response = result.getResponse().getContentAsString(); + // contains a link to /api/discover/sitemaps/sitemap0.html + assertTrue(response.contains(SITEMAPS_ENDPOINT + "/sitemap0.xml")); + } + + @Test + public void testSitemap_sitemap0Xml() throws Exception { + //** WHEN ** + //We retrieve sitemap0.html + MvcResult result = getClient().perform(get(SITEMAPS_ENDPOINT + "/sitemap0.xml")) + //** THEN ** + .andExpect(status().isOk()) + //We expect the content type to match + .andExpect(content().contentType("application/xml")) + .andReturn(); + + String response = result.getResponse().getContentAsString(); + // contains a link to items: [dspace.ui.url]/items/ + assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item1.getID())); + assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item2.getID())); + } +} From d0a7104e9f5747cf235d254b6a0efec3b13e38b1 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 9 Jul 2020 10:52:05 +0200 Subject: [PATCH 197/465] 71738: Automatic cron job for sitemaps --- .../dspace/app/sitemap/GenerateSitemaps.java | 13 ++++++++++-- dspace/config/spring/api/task-scheduler.xml | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 dspace/config/spring/api/task-scheduler.xml diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java index 99517773a5..2a4698a833 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java @@ -142,6 +142,16 @@ public class GenerateSitemaps { System.exit(0); } + /** + * Runs generate-sitemaps without any params for the scheduler (task-scheduler.xml). + * + * @throws SQLException if a database error occurs. + * @throws IOException if IO error occurs. + */ + public static void generateSitemapsScheduled() throws IOException, SQLException { + generateSitemaps(true, true); + } + /** * Generate sitemap.org protocol and/or basic HTML sitemaps. * @@ -152,8 +162,7 @@ public class GenerateSitemaps { * @throws IOException if IO error * if IO error occurs. */ - public static void generateSitemaps(boolean makeHTMLMap, - boolean makeSitemapOrg) throws SQLException, IOException { + public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) throws SQLException, IOException { String sitemapStem = configurationService.getProperty("dspace.server.url") + SITEMAPS_ENDPOINT + "/sitemap"; String uiURLStem = configurationService.getProperty("dspace.ui.url"); diff --git a/dspace/config/spring/api/task-scheduler.xml b/dspace/config/spring/api/task-scheduler.xml new file mode 100644 index 0000000000..a77b7aafaa --- /dev/null +++ b/dspace/config/spring/api/task-scheduler.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + From 61bed89a0c97e0e71c85ef16ff400c6c30310eae Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 14 Jul 2020 09:44:14 -0500 Subject: [PATCH 198/465] Rename master to main. Rename whitelist to allow list. --- .github/pull_request_template.md | 2 +- Dockerfile | 2 +- Dockerfile.cli | 2 +- Dockerfile.test | 2 +- README.md | 36 +++++++++---------- .../service/CitationDocumentService.java | 2 +- .../SimpleXpathMetadatumContributor.java | 2 +- .../webapp/WEB-INF/applicationContext.xml | 2 +- .../java/org/dspace/app/rest/Application.java | 4 +-- .../java/org/dspace/app/rest/utils/Utils.java | 2 +- .../crosswalks/google-metadata.properties | 14 ++++---- dspace/src/main/docker-compose/cli.ingest.yml | 2 +- dspace/src/main/docker/README.md | 6 ++-- pom.xml | 2 +- 14 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3605531adb..6542242af7 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,5 +23,5 @@ _This checklist provides a reminder of what we are going to look for when review - [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). - [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. - [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). -- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. +- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] If my PR modifies the REST API, I've linked to the REST Contract page (or open PR) related to this change. diff --git a/Dockerfile b/Dockerfile index 006f32f28e..2dc3ee9bda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # This image will be published as dspace/dspace -# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details +# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # # This version is JDK11 compatible # - tomcat:8-jdk11 diff --git a/Dockerfile.cli b/Dockerfile.cli index 116b251f2d..d4204ebdd0 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -1,5 +1,5 @@ # This image will be published as dspace/dspace-cli -# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details +# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # # This version is JDK11 compatible # - openjdk:11 diff --git a/Dockerfile.test b/Dockerfile.test index 090f714e28..82ffdef177 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,5 +1,5 @@ # This image will be published as dspace/dspace -# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details +# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # # This version is JDK11 compatible # - tomcat:8-jdk11 diff --git a/README.md b/README.md index 49f3814b49..1b28c958d4 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,24 @@ # DSpace -[![Build Status](https://travis-ci.org/DSpace/DSpace.png?branch=master)](https://travis-ci.org/DSpace/DSpace) +[![Build Status](https://travis-ci.com/DSpace/DSpace.png?branch=main)](https://travis-ci.com/DSpace/DSpace) -[DSpace Documentation](https://wiki.duraspace.org/display/DSDOC/) | +[DSpace Documentation](https://wiki.lyrasis.org/display/DSDOC/) | [DSpace Releases](https://github.com/DSpace/DSpace/releases) | -[DSpace Wiki](https://wiki.duraspace.org/display/DSPACE/Home) | -[Support](https://wiki.duraspace.org/display/DSPACE/Support) +[DSpace Wiki](https://wiki.lyrasis.org/display/DSPACE/Home) | +[Support](https://wiki.lyrasis.org/display/DSPACE/Support) DSpace open source software is a turnkey repository application used by more than 2,000 organizations and institutions worldwide to provide durable access to digital resources. For more information, visit http://www.dspace.org/ *** -:warning: **Work on DSpace 7 has begun on our `master` branch.** This means that there is temporarily NO user interface on this `master` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) page. Additionally, the codebases can be found in the following places: - * DSpace 7 REST API work is occurring on the [`master` branch](https://github.com/DSpace/DSpace/tree/master/dspace-server-webapp) of this repository. - * The REST Contract is being documented at https://github.com/DSpace/Rest7Contract +:warning: **Work on DSpace 7 has begun on our `main` branch.** This means that there is NO user interface on this `main` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) page. Additionally, the codebases can be found in the following places: + * DSpace 7 REST API work is occurring on the [`main` branch](https://github.com/DSpace/DSpace/tree/main/dspace-server-webapp) of this repository. + * The REST Contract is at https://github.com/DSpace/Rest7Contract * DSpace 7 Angular UI work is occurring at https://github.com/DSpace/dspace-angular -**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) wiki page for more info. +**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) wiki page for more info. **If you are looking for the ongoing maintenance work for DSpace 6 (or prior releases)**, you can find that work on the corresponding maintenance branch (e.g. [`dspace-6_x`](https://github.com/DSpace/DSpace/tree/dspace-6_x)) in this repository. *** @@ -31,10 +31,10 @@ Past releases are all available via GitHub at https://github.com/DSpace/DSpace/r ## Documentation / Installation -Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.duraspace.org/display/DSDOC/). +Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/). The latest DSpace Installation instructions are available at: -https://wiki.duraspace.org/display/DSDOC6x/Installing+DSpace +https://wiki.lyrasis.org/display/DSDOC6x/Installing+DSpace Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL or Oracle) and a servlet container (usually Tomcat) in order to function. @@ -49,11 +49,11 @@ DSpace is a community built and supported project. We do not have a centralized but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc. We welcome contributions of any type. Here's a few basic guides that provide suggestions for contributing to DSpace: -* [How to Contribute to DSpace](https://wiki.duraspace.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc) -* [Code Contribution Guidelines](https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc. -* [DSpace Community Advisory Team (DCAT)](https://wiki.duraspace.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam). +* [How to Contribute to DSpace](https://wiki.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc) +* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc. +* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam). -We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.duraspace.org/display/DSPACE/Development+with+Git) guide for more info. +We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info. In addition, a listing of all known contributors to DSpace software can be found online at: https://wiki.duraspace.org/display/DSPACE/DSpaceContributors @@ -64,12 +64,12 @@ DSpace provides public mailing lists where you can post questions or raise topic We welcome everyone to participate in these lists: * [dspace-community@googlegroups.com](https://groups.google.com/d/forum/dspace-community) : General discussion about DSpace platform, announcements, sharing of best practices -* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.duraspace.org/display/DSPACE/Troubleshoot+an+error). +* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.lyrasis.org/display/DSPACE/Troubleshoot+an+error). * [dspace-devel@googlegroups.com](https://groups.google.com/d/forum/dspace-devel) : Developers / Development mailing list Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stackoverflow.com/questions/tagged/dspace) -Additional support options are listed at https://wiki.duraspace.org/display/DSPACE/Support +Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support DSpace also has an active service provider network. If you'd rather hire a service provider to install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our @@ -84,7 +84,7 @@ The DSpace Issue Tracker can be found at: https://jira.duraspace.org/projects/DS ### Running Tests By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are -run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all Pull Requests and code commits. +run automatically by [Travis CI](https://travis-ci.com/DSpace/DSpace/) for all Pull Requests and code commits. * How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`): ``` @@ -130,4 +130,4 @@ run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all P ## License DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause). -The full license is available at http://www.dspace.org/license/ +The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/ diff --git a/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java b/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java index d6c7935a86..4a59de3f5f 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java @@ -38,7 +38,7 @@ public interface CitationDocumentService { * Citation enabled globally (all citable bitstreams will get "watermarked") modules/disseminate-citation: * enable_globally * OR - * The container is this object is whitelist enabled. + * The container is this object is "allow list" enabled. * - community: modules/disseminate-citation: enabled_communities * - collection: modules/disseminate-citation: enabled_collections * AND diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java index ba5afceb5f..ef2571acc6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java @@ -79,7 +79,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributorMetadataFieldConfig + * MetadataFieldConfig */ public SimpleXpathMetadatumContributor(String query, Map prefixToNamespaceMapping, MetadataFieldConfig field) { diff --git a/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml b/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml index 62b660b86b..ec892fbaa4 100644 --- a/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml +++ b/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml @@ -28,7 +28,7 @@ + 9.4.15.v20190215 2.11.2 2.0.15 @@ -1240,6 +1240,7 @@ handle 9.3.0-SNAPSHOT + net.cnri cnri-servlet-container @@ -1847,6 +1848,7 @@ true + handle.net https://handle.net/maven From 239bc5271d248923cf65df277ccfc9a4a4244860 Mon Sep 17 00:00:00 2001 From: KevinVdV Date: Wed, 22 Jul 2020 11:31:51 +0200 Subject: [PATCH 219/465] [Issue: 2800] Put dynamic workflowgroup links on Collections in an array --- .../CollectionResourceWorkflowGroupHalLinkFactory.java | 4 ++-- .../org/dspace/app/rest/model/hateoas/HALResource.java | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/CollectionResourceWorkflowGroupHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/CollectionResourceWorkflowGroupHalLinkFactory.java index 7d0256cfc4..c049a74c0d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/CollectionResourceWorkflowGroupHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/CollectionResourceWorkflowGroupHalLinkFactory.java @@ -47,9 +47,9 @@ public class CollectionResourceWorkflowGroupHalLinkFactory Map roles = WorkflowUtils.getCollectionRoles(collection); UUID resourceUuid = UUID.fromString(halResource.getContent().getUuid()); for (Map.Entry entry : roles.entrySet()) { - list.add(buildLink("workflowGroups/" + entry.getKey(), getMethodOn() + list.add(buildLink("workflowGroups", getMethodOn() .getWorkflowGroupForRole(resourceUuid, null, null, - entry.getKey()))); + entry.getKey())).withName(entry.getKey())); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java index 31e2c672e3..0077ae1bc5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java @@ -44,12 +44,4 @@ public abstract class HALResource extends EntityModel { public void setPageHeader(EmbeddedPageHeader page) { this.pageHeader = page; } - - @Override - public EntityModel add(Link link) { - if (!hasLink(link.getRel())) { - return super.add(link); - } - return this; - } } From dd9c0b9999f75f1f2045da8e382bd7fca39e93cd Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 21 Jul 2020 16:22:07 -0400 Subject: [PATCH 220/465] #2893 Move builders from dspace-server-webapp to dspace-api. --- .../AbstractDSpaceIntegrationTest.java | 7 +- .../AbstractIntegrationTestWithDatabase.java | 4 +- .../test/java/org/dspace}/ExitException.java | 2 +- .../org/dspace}/NoExitSecurityManager.java | 2 +- .../MockAuthoritySolrServiceImpl.java | 4 + ...rationTest.java => AuthorizeConfigIT.java} | 2 +- .../org/dspace}/builder/AbstractBuilder.java | 11 +- .../dspace}/builder/AbstractCRUDBuilder.java | 2 +- .../builder/AbstractDSpaceObjectBuilder.java | 2 +- .../org/dspace}/builder/BitstreamBuilder.java | 2 +- .../builder/BitstreamFormatBuilder.java | 3 +- .../org/dspace}/builder/BundleBuilder.java | 2 +- .../dspace}/builder/ClaimedTaskBuilder.java | 2 +- .../dspace}/builder/CollectionBuilder.java | 2 +- .../org/dspace}/builder/CommunityBuilder.java | 2 +- .../org/dspace}/builder/EPersonBuilder.java | 6 +- .../dspace}/builder/EntityTypeBuilder.java | 2 +- .../org/dspace}/builder/GroupBuilder.java | 6 +- .../java/org/dspace}/builder/ItemBuilder.java | 2 +- .../dspace}/builder/MetadataFieldBuilder.java | 2 +- .../builder/MetadataSchemaBuilder.java | 2 +- .../org/dspace}/builder/PoolTaskBuilder.java | 2 +- .../org/dspace}/builder/ProcessBuilder.java | 2 +- .../dspace}/builder/RelationshipBuilder.java | 2 +- .../builder/RelationshipTypeBuilder.java | 2 +- .../builder/ResourcePolicyBuilder.java | 2 +- .../java/org/dspace}/builder/SiteBuilder.java | 2 +- .../dspace}/builder/WorkflowItemBuilder.java | 2 +- .../dspace}/builder/WorkspaceItemBuilder.java | 2 +- .../util/AbstractBuilderCleanupUtil.java | 43 ++++---- .../dspace/discovery/MockSolrSearchCore.java | 4 + .../java/org/dspace/solr/MockSolrServer.java | 2 +- .../statistics/MockSolrLoggerServiceImpl.java | 40 +++++-- dspace-server-webapp/pom.xml | 7 ++ .../java/org/dspace/app/oai/OAIpmhIT.java | 8 +- .../opensearch/OpenSearchControllerIT.java | 8 +- .../test/java/org/dspace/app/rdf/RdfIT.java | 2 +- .../rest/AuthenticationRestControllerIT.java | 35 +++--- .../rest/AuthorizationFeatureServiceIT.java | 4 +- .../rest/AuthorizationRestRepositoryIT.java | 6 +- .../app/rest/BitstreamControllerIT.java | 14 +-- .../rest/BitstreamFormatRestRepositoryIT.java | 4 +- .../app/rest/BitstreamRestControllerIT.java | 24 ++--- .../app/rest/BitstreamRestRepositoryIT.java | 10 +- .../app/rest/BrowsesResourceControllerIT.java | 8 +- .../app/rest/BundleRestRepositoryIT.java | 15 +-- .../BundleUploadBitstreamControllerIT.java | 8 +- .../rest/CCLicenseAddPatchOperationIT.java | 10 +- .../rest/CCLicenseRemovePatchOperationIT.java | 12 +-- .../rest/CollectionGroupRestControllerIT.java | 4 +- ...CollectionHarvestSettingsControllerIT.java | 6 +- .../app/rest/CollectionLogoControllerIT.java | 4 +- .../app/rest/CollectionRestRepositoryIT.java | 12 +-- .../CommunityAdminGroupRestControllerIT.java | 14 +-- .../rest/CommunityCollectionItemParentIT.java | 6 +- .../app/rest/CommunityLogoControllerIT.java | 2 +- .../app/rest/CommunityRestRepositoryIT.java | 12 +-- .../app/rest/DiscoveryRestControllerIT.java | 22 ++-- .../app/rest/EPersonRestRepositoryIT.java | 10 +- .../app/rest/GroupRestRepositoryIT.java | 12 +-- .../app/rest/IdentifierRestControllerIT.java | 4 +- ...wningCollectionUpdateRestControllerIT.java | 10 +- .../dspace/app/rest/ItemRestRepositoryIT.java | 16 +-- .../rest/ItemTemplateRestControllerIT.java | 10 +- .../dspace/app/rest/LanguageSupportIT.java | 2 +- .../org/dspace/app/rest/LoginAsEPersonIT.java | 12 +-- .../MappedCollectionRestRepositoryIT.java | 10 +- .../rest/MetadataSchemaRestRepositoryIT.java | 22 ++-- .../rest/MetadatafieldRestRepositoryIT.java | 30 +++--- .../app/rest/ProcessRestRepositoryIT.java | 7 +- .../RelationshipDeleteRestRepositoryIT.java | 12 +-- .../rest/RelationshipRestRepositoryIT.java | 10 +- .../RelationshipTypeRestControllerIT.java | 8 +- .../rest/ResourcePolicyRestRepositoryIT.java | 12 +-- .../app/rest/RestResourceControllerIT.java | 2 +- .../app/rest/ScriptRestRepositoryIT.java | 8 +- .../app/rest/SearchEventRestRepositoryIT.java | 6 +- .../dspace/app/rest/SiteRestRepositoryIT.java | 2 +- .../app/rest/SubResourcePermissionsIT.java | 10 +- .../SubmissionDefinitionsControllerIT.java | 4 +- .../app/rest/SubmissionFormsControllerIT.java | 10 +- .../app/rest/TaskRestRepositoriesIT.java | 14 ++- .../app/rest/UUIDLookupRestControllerIT.java | 12 +-- .../org/dspace/app/rest/UriListParsingIT.java | 6 +- .../rest/VersionHistoryRestRepositoryIT.java | 6 +- .../app/rest/VersionRestRepositoryIT.java | 6 +- .../app/rest/ViewEventRestRepositoryIT.java | 10 +- .../WorkflowDefinitionRestRepositoryIT.java | 7 +- .../rest/WorkflowItemRestRepositoryIT.java | 16 +-- .../rest/WorkspaceItemRestRepositoryIT.java | 14 +-- .../authorization/AdministratorFeatureIT.java | 6 +- .../authorization/CCLicenseFeatureRestIT.java | 12 +-- .../authorization/EnrollAdministratorIT.java | 2 +- .../LoginOnBehalfOfFeatureRestIT.java | 2 +- .../authorization/ReinstateFeatureRestIT.java | 14 +-- .../authorization/WithdrawFeatureRestIT.java | 14 +-- .../csv/CSVMetadataImportReferenceIT.java | 6 +- .../org/dspace/app/rest/csv/CsvImportIT.java | 6 +- .../AbstractControllerIntegrationTest.java | 1 + .../test/AbstractEntityIntegrationTest.java | 5 +- .../AbstractWebClientIntegrationTest.java | 2 + .../statistics/MockSolrLoggerServiceImpl.java | 101 ------------------ dspace/modules/server/pom.xml | 6 ++ pom.xml | 6 ++ 104 files changed, 445 insertions(+), 489 deletions(-) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest/test => dspace-api/src/test/java/org/dspace}/AbstractDSpaceIntegrationTest.java (97%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest/test => dspace-api/src/test/java/org/dspace}/AbstractIntegrationTestWithDatabase.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest/test => dspace-api/src/test/java/org/dspace}/ExitException.java (93%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest/test => dspace-api/src/test/java/org/dspace}/NoExitSecurityManager.java (95%) rename dspace-api/src/test/java/org/dspace/authorize/{AuthorizeConfigIntegrationTest.java => AuthorizeConfigIT.java} (97%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/AbstractBuilder.java (96%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/AbstractCRUDBuilder.java (96%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/AbstractDSpaceObjectBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/BitstreamBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/BitstreamFormatBuilder.java (98%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/BundleBuilder.java (98%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/ClaimedTaskBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/CollectionBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/CommunityBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/EPersonBuilder.java (97%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/EntityTypeBuilder.java (98%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/GroupBuilder.java (97%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/ItemBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/MetadataFieldBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/MetadataSchemaBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/PoolTaskBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/ProcessBuilder.java (98%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/RelationshipBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/RelationshipTypeBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/ResourcePolicyBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/SiteBuilder.java (97%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/WorkflowItemBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/WorkspaceItemBuilder.java (99%) rename {dspace-server-webapp/src/test/java/org/dspace/app/rest => dspace-api/src/test/java/org/dspace}/builder/util/AbstractBuilderCleanupUtil.java (72%) rename {dspace-server-webapp => dspace-api}/src/test/java/org/dspace/solr/MockSolrServer.java (98%) delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractDSpaceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java similarity index 97% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractDSpaceIntegrationTest.java rename to dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java index e3bb0a0500..1abc4e017d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractDSpaceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.test; +package org.dspace; import static org.junit.Assert.fail; @@ -17,7 +17,7 @@ import java.util.TimeZone; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.builder.AbstractBuilder; +import org.dspace.builder.AbstractBuilder; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.junit.AfterClass; @@ -90,8 +90,9 @@ public class AbstractDSpaceIntegrationTest { } /** - * This method will be run after all tests finish as per @AfterClass. It + * This method will be run after all tests finish as per @AfterClass. It * will clean resources initialized by the @BeforeClass methods. + * @throws java.sql.SQLException */ @AfterClass public static void destroyTestEnvironment() throws SQLException { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java rename to dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index 0b4cb7791b..7b3e4ecee3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.test; +package org.dspace; import static org.junit.Assert.fail; @@ -14,11 +14,11 @@ import java.sql.SQLException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.launcher.ScriptLauncher; -import org.dspace.app.rest.builder.AbstractBuilder; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.authority.AuthoritySearchService; import org.dspace.authority.MockAuthoritySolrServiceImpl; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.AbstractBuilder; import org.dspace.content.Community; import org.dspace.core.Context; import org.dspace.core.I18nUtil; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/ExitException.java b/dspace-api/src/test/java/org/dspace/ExitException.java similarity index 93% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/test/ExitException.java rename to dspace-api/src/test/java/org/dspace/ExitException.java index a377d42238..3e7ce2fdc2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/ExitException.java +++ b/dspace-api/src/test/java/org/dspace/ExitException.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.test; +package org.dspace; public class ExitException extends SecurityException { private final int status; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/NoExitSecurityManager.java b/dspace-api/src/test/java/org/dspace/NoExitSecurityManager.java similarity index 95% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/test/NoExitSecurityManager.java rename to dspace-api/src/test/java/org/dspace/NoExitSecurityManager.java index 79d75dcaf1..7d98f688ef 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/NoExitSecurityManager.java +++ b/dspace-api/src/test/java/org/dspace/NoExitSecurityManager.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.test; +package org.dspace; import java.security.Permission; diff --git a/dspace-api/src/test/java/org/dspace/authority/MockAuthoritySolrServiceImpl.java b/dspace-api/src/test/java/org/dspace/authority/MockAuthoritySolrServiceImpl.java index e1e018ef33..6c0ad5ace8 100644 --- a/dspace-api/src/test/java/org/dspace/authority/MockAuthoritySolrServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/authority/MockAuthoritySolrServiceImpl.java @@ -21,4 +21,8 @@ public class MockAuthoritySolrServiceImpl extends AuthoritySolrServiceImpl imple //We don't use SOLR in the tests of this module solr = null; } + + public void reset() { + // This method intentionally left blank. + } } diff --git a/dspace-api/src/test/java/org/dspace/authorize/AuthorizeConfigIntegrationTest.java b/dspace-api/src/test/java/org/dspace/authorize/AuthorizeConfigIT.java similarity index 97% rename from dspace-api/src/test/java/org/dspace/authorize/AuthorizeConfigIntegrationTest.java rename to dspace-api/src/test/java/org/dspace/authorize/AuthorizeConfigIT.java index d338bc6e2c..3218c14d7e 100644 --- a/dspace-api/src/test/java/org/dspace/authorize/AuthorizeConfigIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/authorize/AuthorizeConfigIT.java @@ -20,7 +20,7 @@ import org.junit.Test; * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class AuthorizeConfigIntegrationTest extends AbstractIntegrationTest { +public class AuthorizeConfigIT extends AbstractIntegrationTest { @Test public void testReloadConfiguration() { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java similarity index 96% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index faa8c473af..76fd02916f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -5,18 +5,18 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.sql.SQLException; import java.util.List; import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.builder.util.AbstractBuilderCleanupUtil; 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.builder.util.AbstractBuilderCleanupUtil; import org.dspace.content.Bitstream; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; @@ -55,8 +55,8 @@ import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; /** * Abstract builder class that holds references to all available services * - * @param This param represents the Model object for the Builder - * @param This param represents the Service object for the builder + * @param This parameter represents the Model object for the Builder + * @param This parameter represents the Service object for the builder * @author Jonas Van Goolen - (jonas@atmire.com) */ public abstract class AbstractBuilder { @@ -96,7 +96,8 @@ public abstract class AbstractBuilder { * This static class will make sure that the objects built with the builders are disposed of in a foreign-key * constraint safe manner by predefining an order */ - private static AbstractBuilderCleanupUtil abstractBuilderCleanupUtil = new AbstractBuilderCleanupUtil(); + private static final AbstractBuilderCleanupUtil abstractBuilderCleanupUtil + = new AbstractBuilderCleanupUtil(); /** * log4j category */ diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractCRUDBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java similarity index 96% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractCRUDBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java index 884bcc9e3c..72e08b062a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractCRUDBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index 02b7e221e8..848bac54f4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.sql.SQLException; import java.util.Date; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index 42a375a58e..2b19489475 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java similarity index 98% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java index 3cd7084577..1051712326 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; @@ -71,7 +71,6 @@ public class BitstreamFormatBuilder extends AbstractCRUDBuilder log.error(e); } catch (AuthorizeException e) { log.error(e); - ; } return bitstreamFormat; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BundleBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java similarity index 98% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BundleBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java index 76d4a90104..6b15ca432f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BundleBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java index 72acd3f27d..338739285f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java index d472316c74..da46281290 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java index f7b13e117f..5500697da4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java similarity index 97% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java index 26fc2b51c4..2f2c7522a5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; @@ -28,7 +28,7 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { @Override public void cleanup() throws Exception { - try (Context c = new Context()) { + try (Context c = new Context()) { c.turnOffAuthorisationSystem(); // Ensure object and any related objects are reloaded before checking to see what needs cleanup ePerson = c.reloadEntity(ePerson); @@ -36,7 +36,7 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { delete(c, ePerson); c.complete(); } - } + } } protected DSpaceObjectService getService() { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EntityTypeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java similarity index 98% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EntityTypeBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java index 8a2efaffa6..5c17e9ef53 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/EntityTypeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java b/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java similarity index 97% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java index 5319a67718..cbee3e57a1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; @@ -34,7 +34,7 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { @Override public void cleanup() throws Exception { - try (Context c = new Context()) { + try (Context c = new Context()) { c.turnOffAuthorisationSystem(); // Ensure object and any related objects are reloaded before checking to see what needs cleanup group = c.reloadEntity(group); @@ -42,7 +42,7 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { delete(c, group); c.complete(); } - } + } } public static GroupBuilder createGroup(final Context context) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index ddc2a1963c..e4f3a0e29d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java b/dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java index 76d411cf14..d209e0ec7d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/MetadataSchemaBuilder.java b/dspace-api/src/test/java/org/dspace/builder/MetadataSchemaBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/MetadataSchemaBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/MetadataSchemaBuilder.java index 9b20154c8a..86186df9ab 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/MetadataSchemaBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/MetadataSchemaBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java b/dspace-api/src/test/java/org/dspace/builder/PoolTaskBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/PoolTaskBuilder.java index bfd346934c..c0de46e1e5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/PoolTaskBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ProcessBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java similarity index 98% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ProcessBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java index 5749a44b79..886c1ddc06 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ProcessBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java index c054521569..f53340c345 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java index 96ceb4792b..aa56eed802 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.sql.SQLException; import java.util.List; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ResourcePolicyBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ResourcePolicyBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java index 05dc1fe15d..e6d677afb9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ResourcePolicyBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SiteBuilder.java similarity index 97% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/SiteBuilder.java index 935a0fc56f..9dc483b4c9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/SiteBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import org.dspace.content.Site; import org.dspace.content.service.DSpaceObjectService; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkflowItemBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/WorkflowItemBuilder.java index e3bd82e3cc..551eb51910 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkflowItemBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java index 0c328a79e9..0a0c4ad6bc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/WorkspaceItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.io.IOException; import java.io.InputStream; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java similarity index 72% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java rename to dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 9570bcabac..d3dadbbc59 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -5,32 +5,32 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder.util; +package org.dspace.builder.util; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import org.dspace.app.rest.builder.AbstractBuilder; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.BitstreamFormatBuilder; -import org.dspace.app.rest.builder.ClaimedTaskBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.EntityTypeBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.MetadataFieldBuilder; -import org.dspace.app.rest.builder.MetadataSchemaBuilder; -import org.dspace.app.rest.builder.PoolTaskBuilder; -import org.dspace.app.rest.builder.ProcessBuilder; -import org.dspace.app.rest.builder.RelationshipBuilder; -import org.dspace.app.rest.builder.RelationshipTypeBuilder; -import org.dspace.app.rest.builder.SiteBuilder; -import org.dspace.app.rest.builder.WorkflowItemBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; +import org.dspace.builder.AbstractBuilder; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BitstreamFormatBuilder; +import org.dspace.builder.ClaimedTaskBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.MetadataFieldBuilder; +import org.dspace.builder.MetadataSchemaBuilder; +import org.dspace.builder.PoolTaskBuilder; +import org.dspace.builder.ProcessBuilder; +import org.dspace.builder.RelationshipBuilder; +import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.SiteBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; /** * This class will ensure that all the builders that are registered will be cleaned up in the order as defined @@ -39,7 +39,8 @@ import org.dspace.app.rest.builder.WorkspaceItemBuilder; */ public class AbstractBuilderCleanupUtil { - private LinkedHashMap> map = new LinkedHashMap<>(); + private final LinkedHashMap> map + = new LinkedHashMap<>(); /** * Constructor that will initialize the Map with a predefined order for deletion diff --git a/dspace-api/src/test/java/org/dspace/discovery/MockSolrSearchCore.java b/dspace-api/src/test/java/org/dspace/discovery/MockSolrSearchCore.java index 1934ba9f0f..3fd917ad31 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/MockSolrSearchCore.java +++ b/dspace-api/src/test/java/org/dspace/discovery/MockSolrSearchCore.java @@ -22,4 +22,8 @@ public class MockSolrSearchCore extends SolrSearchCore implements InitializingBe solr = null; } + public void reset() { + // This method intentionally left blank. + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/solr/MockSolrServer.java b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java similarity index 98% rename from dspace-server-webapp/src/test/java/org/dspace/solr/MockSolrServer.java rename to dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java index 237f35e63f..d7893f779d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/solr/MockSolrServer.java +++ b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java @@ -19,7 +19,7 @@ import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.core.CoreContainer; -import org.dspace.app.rest.test.AbstractDSpaceIntegrationTest; +import org.dspace.AbstractDSpaceIntegrationTest; /** * Factory of connections to an in-process embedded Solr service. diff --git a/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java b/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java index cca05a12cc..4c42fab932 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java @@ -27,27 +27,39 @@ import com.maxmind.geoip2.record.MaxMind; import com.maxmind.geoip2.record.Postal; import com.maxmind.geoip2.record.RepresentedCountry; import com.maxmind.geoip2.record.Traits; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.solr.MockSolrServer; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; /** * Mock service that uses an embedded SOLR server for the statistics core. + * *

- * NOTE: this class is overridden by one of the same name - * defined in dspace-server-webapp and declared as a bean there. + * NOTE: this class overrides one of the same name + * defined in dspace-api and declared as a bean there. * See {@code test/data/dspaceFolder/config/spring/api/solr-services.xml}. Some kind of classpath * magic makes this work. */ +@Service public class MockSolrLoggerServiceImpl extends SolrLoggerServiceImpl - implements InitializingBean { + implements InitializingBean, DisposableBean { + + private static final Logger log = LogManager.getLogger(); + + private MockSolrServer mockSolrServer; public MockSolrLoggerServiceImpl() { } @Override public void afterPropertiesSet() throws Exception { - //We don't use SOLR in the tests of this module - solr = null; + // Initialize our service with a Mock Solr statistics core + mockSolrServer = new MockSolrServer("statistics"); + solr = mockSolrServer.getSolrServer(); // Mock GeoIP's DatabaseReader DatabaseReader reader = mock(DatabaseReader.class); @@ -62,10 +74,10 @@ public class MockSolrLoggerServiceImpl * @return faked CityResponse */ private CityResponse mockCityResponse() { - List cityNames = new ArrayList(Collections.singleton("New York")); + List cityNames = new ArrayList<>(Collections.singleton("New York")); City city = new City(cityNames, 1, 1, new HashMap()); - List countryNames = new ArrayList(Collections.singleton("United States")); + List countryNames = new ArrayList<>(Collections.singleton("United States")); Country country = new Country(countryNames, 1, 1, "US", new HashMap()); Location location = new Location(1, 1, 40.760498D, -73.9933D, 501, 1, "EST"); @@ -73,7 +85,17 @@ public class MockSolrLoggerServiceImpl Postal postal = new Postal("10036", 1); return new CityResponse(city, new Continent(), country, location, new MaxMind(), postal, - country, new RepresentedCountry(), new ArrayList<>(0), - new Traits()); + country, new RepresentedCountry(), new ArrayList<>(0), + new Traits()); + } + + /** Remove all records. */ + public void reset() { + mockSolrServer.reset(); + } + + @Override + public void destroy() throws Exception { + mockSolrServer.destroy(); } } diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index ccb78dde49..d415d12539 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -307,6 +307,13 @@ dspace-api + + org.dspace + dspace-api + test-jar + test + + org.dspace dspace-services diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIpmhIT.java index 3b277a937f..052c363771 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIpmhIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIpmhIT.java @@ -29,9 +29,9 @@ import com.lyncode.xoai.dataprovider.services.impl.BaseDateProvider; import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; import com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration; import org.apache.commons.lang3.time.DateUtils; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Community; import org.dspace.services.ConfigurationService; import org.dspace.xoai.services.api.EarliestDateResolver; @@ -76,7 +76,7 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { private EarliestDateResolver earliestDateResolver; // XOAI's BaseDateProvider (used for date-based testing below) - private static BaseDateProvider baseDateProvider = new BaseDateProvider(); + private static final BaseDateProvider baseDateProvider = new BaseDateProvider(); // Spy on the current XOAIManagerResolver bean, to allow us to change behavior of XOAIManager in tests // See also: createMockXOAIManager() method @@ -278,6 +278,6 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { * @throws ConfigurationException */ private XOAIManager createMockXOAIManager(Configuration xoaiConfig) throws ConfigurationException { - return new XOAIManager(filterResolver, resourceResolver, xoaiConfig); + return new XOAIManager(filterResolver, resourceResolver, xoaiConfig); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java index 58cd3d0a16..9f7e4e6610 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java @@ -12,10 +12,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; -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.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -210,7 +210,7 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest { .andExpect(xpath("OpenSearchDescription/LongName").string("DSpace at My University")) .andExpect(xpath("OpenSearchDescription/Description") .string("DSpace at My University DSpace repository") - ) + ) ; /* Expected response for the service document is: diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java index 681b2ced81..85ab3dcadd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java @@ -14,8 +14,8 @@ import static org.mockito.Mockito.doReturn; import java.net.URI; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Community; import org.dspace.content.service.SiteService; import org.dspace.rdf.RDFUtil; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 513ca5ebfd..24a27b9fe8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -30,17 +30,17 @@ import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.BundleBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.AuthenticationStatusMatcher; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; @@ -70,12 +70,14 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public static final String[] PASS_ONLY = {"org.dspace.authenticate.PasswordAuthentication"}; public static final String[] SHIB_ONLY = {"org.dspace.authenticate.ShibAuthentication"}; - public static final String[] SHIB_AND_PASS = - {"org.dspace.authenticate.ShibAuthentication", - "org.dspace.authenticate.PasswordAuthentication"}; - public static final String[] SHIB_AND_IP = - {"org.dspace.authenticate.IPAuthentication", - "org.dspace.authenticate.ShibAuthentication"}; + public static final String[] SHIB_AND_PASS = { + "org.dspace.authenticate.ShibAuthentication", + "org.dspace.authenticate.PasswordAuthentication" + }; + public static final String[] SHIB_AND_IP = { + "org.dspace.authenticate.IPAuthentication", + "org.dspace.authenticate.ShibAuthentication" + }; @Before public void setup() throws Exception { @@ -406,7 +408,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio @Test public void testLoginGetRequest() throws Exception { - getClient().perform(get("/api/authn/login") + getClient().perform(get("/api/authn/login") .param("user", eperson.getEmail()) .param("password", password)) .andExpect(status().isMethodNotAllowed()); @@ -721,8 +723,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio //Check if WWW-Authenticate header contains only password getClient().perform(get("/api/authn/status").header("Referer", "http://my.uni.edu")) - .andExpect(status().isOk()) - .andExpect(header().string("WWW-Authenticate", + .andExpect(status().isOk()) + .andExpect(header().string("WWW-Authenticate", "password realm=\"DSpace REST API\"")); //Check if a shibboleth authentication fails @@ -730,7 +732,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .requestAttr("SHIB-MAIL", eperson.getEmail()) .requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff")) .andExpect(status().isUnauthorized()); - } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java index d53bdc92c8..eba774345d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Set; import org.apache.commons.lang3.ArrayUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.rest.authorization.AlwaysFalseFeature; import org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature; import org.dspace.app.rest.authorization.AlwaysTrueFeature; @@ -26,7 +27,6 @@ import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; -import org.dspace.app.rest.test.AbstractIntegrationTestWithDatabase; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.dspace.content.Site; @@ -77,7 +77,7 @@ public class AuthorizationFeatureServiceIT extends AbstractIntegrationTestWithDa assertThat("We have at least our 7 mock features for testing", authzFeatureServiceFindAll.size(), greaterThanOrEqualTo(7)); - Set featureNames = new HashSet(); + Set featureNames = new HashSet<>(); for (AuthorizationFeature f : authzFeatureServiceFindAll) { featureNames.add(f.getName()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index 05631790e3..e86da979c2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -28,9 +28,6 @@ import org.dspace.app.rest.authorization.TrueForAdminsFeature; import org.dspace.app.rest.authorization.TrueForLoggedUsersFeature; import org.dspace.app.rest.authorization.TrueForTestUsersFeature; import org.dspace.app.rest.authorization.TrueForUsersInGroupTestFeature; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.converter.SiteConverter; @@ -43,6 +40,9 @@ import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.content.Community; import org.dspace.content.Site; import org.dspace.content.factory.ContentServiceFactory; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java index 608232ef5d..ca3c05ec30 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java @@ -22,15 +22,15 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.BundleBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java index 744e673912..48ad410d00 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java @@ -25,14 +25,14 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.BitstreamFormatBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.converter.BitstreamFormatConverter; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.model.BitstreamFormatRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamFormatBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.BitstreamFormat; import org.dspace.content.service.BitstreamFormatService; import org.dspace.core.I18nUtil; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 96176d0a77..675d725a84 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -11,13 +11,13 @@ import static java.util.UUID.randomUUID; import static org.apache.commons.codec.CharEncoding.UTF_8; import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.io.IOUtils.toInputStream; -import static org.dspace.app.rest.builder.BitstreamBuilder.createBitstream; -import static org.dspace.app.rest.builder.BitstreamFormatBuilder.createBitstreamFormat; -import static org.dspace.app.rest.builder.CollectionBuilder.createCollection; -import static org.dspace.app.rest.builder.CommunityBuilder.createCommunity; -import static org.dspace.app.rest.builder.ItemBuilder.createItem; -import static org.dspace.app.rest.builder.ResourcePolicyBuilder.createResourcePolicy; import static org.dspace.app.rest.matcher.BitstreamFormatMatcher.matchBitstreamFormat; +import static org.dspace.builder.BitstreamBuilder.createBitstream; +import static org.dspace.builder.BitstreamFormatBuilder.createBitstreamFormat; +import static org.dspace.builder.CollectionBuilder.createCollection; +import static org.dspace.builder.CommunityBuilder.createCommunity; +import static org.dspace.builder.ItemBuilder.createItem; +import static org.dspace.builder.ResourcePolicyBuilder.createResourcePolicy; import static org.dspace.content.BitstreamFormat.KNOWN; import static org.dspace.content.BitstreamFormat.SUPPORTED; import static org.dspace.core.Constants.READ; @@ -53,14 +53,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import org.apache.solr.client.solrj.SolrServerException; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; import org.dspace.content.Collection; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 34bb56cbec..31b233a56b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -23,17 +23,17 @@ import java.util.UUID; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; 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 88ca72b08a..c4abf5240b 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 @@ -19,14 +19,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.BrowseEntryResourceMatcher; import org.dspace.app.rest.matcher.BrowseIndexMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 07d6645c00..b520c11faa 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -27,13 +27,6 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.BundleBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; @@ -46,6 +39,13 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; @@ -71,6 +71,7 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { private Bitstream bitstream2; @Before + @Override public void setUp() throws Exception { super.setUp(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java index efb07b3f1d..eefcb81656 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java @@ -18,10 +18,6 @@ import java.util.Map; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.BundleBuilder; -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.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.BitstreamRest; @@ -29,6 +25,10 @@ import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java index 4f9c753047..fcb814d82d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java @@ -20,12 +20,12 @@ import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.MediaType; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.WorkspaceItem; @@ -61,7 +61,7 @@ public class CCLicenseAddPatchOperationIT extends AbstractControllerIntegrationT String adminToken = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); AddOperation addOperation = new AddOperation("/sections/cclicense/uri", "http://creativecommons.org/licenses/by-nc-sa/4.0/"); @@ -102,7 +102,7 @@ public class CCLicenseAddPatchOperationIT extends AbstractControllerIntegrationT String adminToken = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); AddOperation addOperation = new AddOperation("/sections/cclicense/uri", "invalid-license-uri"); ops.add(addOperation); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java index 3b05621f08..c003cf2809 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java @@ -19,13 +19,13 @@ import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.MediaType; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.WorkspaceItem; @@ -62,7 +62,7 @@ public class CCLicenseRemovePatchOperationIT extends AbstractControllerIntegrati String epersonToken = getAuthToken(eperson.getEmail(), password); // First add a license and verify it is added - List ops = new ArrayList(); + List ops = new ArrayList<>(); AddOperation addOperation = new AddOperation("/sections/cclicense/uri", "http://creativecommons.org/licenses/by-nc-sa/4.0/"); @@ -84,7 +84,7 @@ public class CCLicenseRemovePatchOperationIT extends AbstractControllerIntegrati // Remove the license again and verify it is removed - List removeOps = new ArrayList(); + List removeOps = new ArrayList<>(); RemoveOperation removeOperation = new RemoveOperation("/sections/cclicense/uri"); removeOps.add(removeOperation); @@ -120,7 +120,7 @@ public class CCLicenseRemovePatchOperationIT extends AbstractControllerIntegrati String epersonToken = getAuthToken(eperson.getEmail(), password); - List removeOps = new ArrayList(); + List removeOps = new ArrayList<>(); RemoveOperation removeOperation = new RemoveOperation("/sections/cclicense/uri"); removeOps.add(removeOperation); 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 index 7464e9c38c..767ea5f565 100644 --- 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 @@ -20,14 +20,14 @@ 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.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionHarvestSettingsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionHarvestSettingsControllerIT.java index dd5f9b6b83..e7479786e7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionHarvestSettingsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionHarvestSettingsControllerIT.java @@ -21,15 +21,15 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.matcher.HarvesterMetadataMatcher; import org.dspace.app.rest.matcher.MetadataConfigsMatcher; import org.dspace.app.rest.model.HarvestTypeEnum; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.core.Constants; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java index e82f845697..f093156000 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java @@ -14,9 +14,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Map; 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.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.junit.Before; import org.junit.Test; 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 62dc114c6e..d1522d4c8c 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 @@ -27,11 +27,6 @@ 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.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.CommunityMatcher; @@ -46,6 +41,11 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.core.Constants; @@ -435,7 +435,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes CollectionMatcher.matchCollectionEntrySpecificEmbedProjection(col2.getName(), col2.getID(), col2.getHandle()) ))) - ) + ) ; getClient().perform(get("/api/core/collections/" + col1.getID() + "/logo")) 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 fb00219a4d..37548553b1 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 @@ -21,10 +21,6 @@ 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.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.model.GroupRest; @@ -32,6 +28,10 @@ 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.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.service.CollectionService; @@ -157,7 +157,7 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg .andExpect(status().isCreated()) .andDo(result -> idRef .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) - ); + ); // no needs to explicitly cleanup the group created as the community comes // from a CommunityBuilder that will cleanup also related groups Group adminGroup = groupService.find(context, idRef.get()); @@ -188,7 +188,7 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg .andExpect(status().isCreated()) .andDo(result -> idRef .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) - ); + ); // no needs to explicitly cleanup the group created as the community comes // from a CommunityBuilder that will cleanup also related groups Group adminGroup = groupService.find(context, idRef.get()); @@ -249,7 +249,7 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg .andExpect(status().isCreated()) .andDo(result -> idRef .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) - ); + ); // no needs to explicitly cleanup the group created as the community comes // from a CommunityBuilder that will cleanup also related groups Group adminGroup = groupService.find(context, idRef.get()); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityCollectionItemParentIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityCollectionItemParentIT.java index d85cf34d6a..a29d9494c9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityCollectionItemParentIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityCollectionItemParentIT.java @@ -16,14 +16,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.sql.SQLException; import java.util.UUID; -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.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.CommunityMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java index 22174c4c0c..1d34a99dd9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java @@ -14,8 +14,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CommunityBuilder; import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; 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 56ab3c1972..862db60489 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 @@ -30,9 +30,6 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.CommunityMatcher; @@ -47,6 +44,9 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.service.CommunityService; @@ -115,8 +115,8 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest String authToken = getAuthToken(admin.getEmail(), password); // Capture the UUID of the created Community (see andDo() below) - AtomicReference idRef = new AtomicReference(); - AtomicReference idRefNoEmbeds = new AtomicReference(); + AtomicReference idRef = new AtomicReference<>(); + AtomicReference idRefNoEmbeds = new AtomicReference<>(); try { getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) @@ -234,7 +234,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest new MetadataValueRest("Title Text"))); // Capture the UUID of the created Community (see andDo() below) - AtomicReference idRef = new AtomicReference(); + AtomicReference idRef = new AtomicReference<>(); try { getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 5b8022120d..80fe6fd96d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -25,15 +25,6 @@ import java.util.UUID; import com.jayway.jsonpath.matchers.JsonPathMatchers; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.ClaimedTaskBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.WorkflowItemBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.AppliedFilterMatcher; import org.dspace.app.rest.matcher.FacetEntryMatcher; import org.dspace.app.rest.matcher.FacetValueMatcher; @@ -45,6 +36,15 @@ import org.dspace.app.rest.matcher.SortOptionMatcher; import org.dspace.app.rest.matcher.WorkflowItemMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.ClaimedTaskBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -95,11 +95,11 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false))) - ); + ); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index d2b599e477..ef0d684903 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -39,11 +39,6 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.jackson.IgnoreJacksonWriteOnlyAccess; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; @@ -57,6 +52,11 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; 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 868b5d271e..7789bc5d7b 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 @@ -31,10 +31,6 @@ import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.HalMatcher; @@ -47,6 +43,10 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.factory.ContentServiceFactory; @@ -97,7 +97,7 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { // hold the id of the created workflow item AtomicReference idRef = new AtomicReference<>(); - AtomicReference idRefNoEmbeds = new AtomicReference(); + AtomicReference idRefNoEmbeds = new AtomicReference<>(); try { ObjectMapper mapper = new ObjectMapper(); GroupRest groupRest = new GroupRest(); @@ -121,7 +121,7 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$", GroupMatcher.matchFullEmbeds())) .andDo(result -> idRef .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) - ); + ); getClient(authToken).perform(get("/api/eperson/groups")) //The status has to be 200 OK diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java index 2956e90513..bd67289330 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java @@ -11,8 +11,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CommunityBuilder; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -27,7 +27,7 @@ public class IdentifierRestControllerIT extends AbstractControllerIntegrationTes @Before public void setup() throws Exception { super.setUp(); - } + } @Test public void testValidIdentifier() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java index 98014cc3a0..73c2c8a3fe 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java @@ -15,14 +15,14 @@ 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 org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Constants; 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 0c96d24288..37b26d5c14 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 @@ -33,14 +33,6 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.HalMatcher; @@ -52,6 +44,14 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java index 58cca9c414..55e82831f3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java @@ -22,8 +22,6 @@ import java.util.List; import java.util.Map; 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.MetadataMatcher; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; @@ -33,6 +31,8 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.dspace.core.Constants; import org.hamcrest.Matchers; @@ -249,7 +249,7 @@ public class ItemTemplateRestControllerIT extends AbstractControllerIntegrationT String itemId = installTestTemplate(); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/inArchive", true); ops.add(replaceOperation); String illegalPatchBody = getPatchContent(ops); @@ -266,7 +266,7 @@ public class ItemTemplateRestControllerIT extends AbstractControllerIntegrationT String itemId = installTestTemplate(); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", true); ops.add(replaceOperation); String illegalPatchBody = getPatchContent(ops); @@ -283,7 +283,7 @@ public class ItemTemplateRestControllerIT extends AbstractControllerIntegrationT String itemId = installTestTemplate(); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", true); ops.add(replaceOperation); String illegalPatchBody = getPatchContent(ops); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java index cc8af92e2b..379744ed22 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LanguageSupportIT.java @@ -12,8 +12,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Locale; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.authority.ChoiceAuthorityServiceImpl; import org.dspace.core.LegacyPluginServiceImpl; import org.dspace.eperson.EPerson; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java index 22ec3ebb1d..95412b514d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java @@ -23,15 +23,15 @@ import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.PoolTaskBuilder; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.WorkflowItemMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.PoolTaskBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java index b3b6513cdd..26d5e87b1d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MappedCollectionRestRepositoryIT.java @@ -15,12 +15,12 @@ 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 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.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -406,13 +406,13 @@ public class MappedCollectionRestRepositoryIT extends AbstractControllerIntegrat .andExpect(jsonPath("$._embedded.mappedItems", Matchers.not(Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) ))) - .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0)));; + .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(0))); getClient().perform(get("/api/core/collections/" + col3.getID() + "/mappedItems")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.mappedItems", Matchers.contains( ItemMatcher.matchItemProperties(publicItem1)) )) - .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(1)));; + .andExpect(jsonPath("$._embedded.mappedItems", Matchers.hasSize(1))); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java index 940a077f4c..4a094d7dc9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java @@ -21,13 +21,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.MetadataSchemaBuilder; import org.dspace.app.rest.converter.MetadataSchemaConverter; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.matcher.MetadataschemaMatcher; import org.dspace.app.rest.model.MetadataSchemaRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.MetadataSchemaBuilder; import org.dspace.content.MetadataSchema; import org.hamcrest.Matchers; import org.junit.Test; @@ -99,17 +99,17 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio try { - getClient(authToken) - .perform(post("/api/core/metadataschemas") - .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + getClient(authToken) + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - getClient().perform(get("/api/core/metadataschemas/" + idRef.get())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", MetadataschemaMatcher.matchEntry(TEST_NAME, TEST_NAMESPACE))); + getClient().perform(get("/api/core/metadataschemas/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataschemaMatcher.matchEntry(TEST_NAME, TEST_NAMESPACE))); } finally { MetadataSchemaBuilder.deleteMetadataSchema(idRef.get()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index 067a496de7..5b2bfa6f6d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -23,11 +23,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.MetadataFieldBuilder; -import org.dspace.app.rest.builder.MetadataSchemaBuilder; import org.dspace.app.rest.matcher.MetadataFieldMatcher; import org.dspace.app.rest.model.MetadataFieldRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.MetadataFieldBuilder; +import org.dspace.builder.MetadataSchemaBuilder; import org.dspace.content.MetadataField; import org.dspace.content.MetadataFieldServiceImpl; import org.dspace.content.MetadataSchema; @@ -179,21 +179,21 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration String authToken = getAuthToken(admin.getEmail(), password); AtomicReference idRef = new AtomicReference<>(); try { - assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); + assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); - getClient(authToken) - .perform(post("/api/core/metadatafields") - .param("schemaId", metadataSchema.getID() + "") - .param("projection", "full") - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - getClient(authToken).perform(get("/api/core/metadatafields/" + idRef.get())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", MetadataFieldMatcher.matchMetadataFieldByKeys( - metadataSchema.getName(), "testElementForCreate", "testQualifierForCreate"))); + getClient(authToken).perform(get("/api/core/metadatafields/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataFieldMatcher.matchMetadataFieldByKeys( + metadataSchema.getName(), "testElementForCreate", "testQualifierForCreate"))); } finally { MetadataFieldBuilder.deleteMetadataField(idRef.get()); } 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 fdca31d07b..f2e8322dc9 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 @@ -22,11 +22,11 @@ import java.util.Random; import org.apache.commons.codec.CharEncoding; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.ProcessBuilder; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.ProcessFileTypesMatcher; import org.dspace.app.rest.matcher.ProcessMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.ProcessBuilder; import org.dspace.content.Bitstream; import org.dspace.content.ProcessStatus; import org.dspace.scripts.DSpaceCommandLineParameter; @@ -72,7 +72,7 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$", Matchers.is( ProcessMatcher.matchProcess(process.getName(), String.valueOf(process.getEPerson().getID()), process.getID(), parameters, ProcessStatus.SCHEDULED))) - ); + ); } @Test @@ -86,7 +86,7 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$", Matchers.is( ProcessMatcher.matchProcess(process.getName(), String.valueOf(process.getEPerson().getID()), process.getID(), new LinkedList<>(), ProcessStatus.SCHEDULED))) - ); + ); } @Test @@ -323,6 +323,7 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { } @After + @Override public void destroy() throws Exception { super.destroy(); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipDeleteRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipDeleteRestRepositoryIT.java index 87d9c16e12..9cc9ce51c9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipDeleteRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipDeleteRestRepositoryIT.java @@ -16,11 +16,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. 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.RelationshipBuilder; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -618,7 +618,7 @@ public class RelationshipDeleteRestRepositoryIT extends AbstractEntityIntegratio assertThat(publicationAuthorList.size(), equalTo(0)); List publicationRelationships = itemService.getMetadata(publicationItem, - "relation", "isAuthorOfPublication", Item.ANY, Item.ANY); + "relation", "isAuthorOfPublication", Item.ANY, Item.ANY); assertThat(publicationRelationships.size(), equalTo(0)); projectItem = itemService.find(context, projectItem.getID()); @@ -628,7 +628,7 @@ public class RelationshipDeleteRestRepositoryIT extends AbstractEntityIntegratio assertThat(projectAuthorList.get(0).getValue(), equalTo("Smith, Donald")); assertNull(projectAuthorList.get(0).getAuthority()); List projectRelationships = itemService.getMetadata(projectItem, - "relation", "isPersonOfProject", Item.ANY, Item.ANY); + "relation", "isPersonOfProject", Item.ANY, Item.ANY); assertThat(projectRelationships.size(), equalTo(0)); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index a9badd03f6..060c504f3d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -33,16 +33,16 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonObject; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.RelationshipBuilder; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.RelationshipMatcher; import org.dspace.app.rest.model.RelationshipRest; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java index fe4bef8b8d..9f631c0650 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java @@ -13,15 +13,15 @@ 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 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.RelationshipBuilder; import org.dspace.app.rest.matcher.EntityTypeMatcher; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.RelationshipMatcher; import org.dspace.app.rest.matcher.RelationshipTypeMatcher; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 1b207fbeee..d8a6234d36 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -29,12 +29,6 @@ import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.ResourcePolicyMatcher; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.patch.AddOperation; @@ -45,6 +39,12 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RestResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RestResourceControllerIT.java index 164459b228..1ca5179942 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RestResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RestResourceControllerIT.java @@ -12,8 +12,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.UUID; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CommunityBuilder; import org.junit.Test; /** 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 c1ba31305e..4c4f369684 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 @@ -27,10 +27,6 @@ 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.builder.ProcessBuilder; import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.ProcessMatcher; @@ -39,6 +35,10 @@ 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.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ProcessBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java index 497f7b9073..bd40cfdc9d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java @@ -16,13 +16,13 @@ import java.util.List; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.model.PageRest; import org.dspace.app.rest.model.SearchEventRest; import org.dspace.app.rest.model.SearchResultsRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java index f208d0827f..092ea32b3f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java @@ -14,10 +14,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.UUID; -import org.dspace.app.rest.builder.SiteBuilder; import org.dspace.app.rest.matcher.SiteMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.builder.SiteBuilder; import org.dspace.content.Site; import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java index d4399a8047..c5f2ed9a13 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java @@ -15,15 +15,15 @@ import java.io.InputStream; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.BundleBuilder; -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.matcher.BundleMatcher; import org.dspace.app.rest.matcher.CommunityMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index e52b70b7bd..e642336b1c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -18,10 +18,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.matcher.SubmissionDefinitionsMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.hamcrest.Matchers; import org.junit.Test; 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 4eee3579ee..09c97b3397 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 @@ -18,11 +18,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Locale; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.matcher.SubmissionFormFieldMatcher; import org.dspace.app.rest.repository.SubmissionFormRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.builder.EPersonBuilder; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; @@ -258,7 +258,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", "dc.language.iso")))); - resetLocalesConfiguration(); + resetLocalesConfiguration(); } @Test @@ -331,7 +331,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", "dc.language.iso")))); - resetLocalesConfiguration(); + resetLocalesConfiguration(); } @Test @@ -377,7 +377,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe + " Se il contenuto non ha davvero una lingua" + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); - resetLocalesConfiguration(); + resetLocalesConfiguration(); } @Test @@ -408,7 +408,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe "\u00C8 necessario inserire un titolo principale per questo item", false, "Inserisci titolo principale di questo item", "dc.title")))); - resetLocalesConfiguration(); + resetLocalesConfiguration(); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java index 8a8c567293..a7813601ec 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java @@ -25,12 +25,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import org.dspace.app.rest.builder.ClaimedTaskBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.PoolTaskBuilder; -import org.dspace.app.rest.builder.WorkflowItemBuilder; import org.dspace.app.rest.matcher.ClaimedTaskMatcher; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.PoolTaskMatcher; @@ -41,6 +35,12 @@ import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.ClaimedTaskBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.PoolTaskBuilder; +import org.dspace.builder.WorkflowItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -339,7 +339,6 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(3))); - ; String authReviewer2 = getAuthToken(reviewer2.getEmail(), password); getClient(authReviewer2).perform(get("/api/workflow/pooltasks/search/findByUser") @@ -360,7 +359,6 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/pooltasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(2))); - ; String authAdmin = getAuthToken(admin.getEmail(), password); getClient(authAdmin).perform(get("/api/workflow/pooltasks/search/findByUser") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java index d8cad3117a..8a6debce3e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java @@ -16,13 +16,13 @@ import java.util.UUID; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.SiteBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SiteBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UriListParsingIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UriListParsingIT.java index 108859381e..91a1572e5a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UriListParsingIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UriListParsingIT.java @@ -13,12 +13,12 @@ import static org.junit.Assert.assertTrue; 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.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 584b099e9e..60fdb11c97 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -17,13 +17,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.sql.SQLException; import java.util.Date; -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.matcher.VersionHistoryMatcher; import org.dspace.app.rest.matcher.VersionMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java index 1a64454dc6..a6087c58c2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java @@ -11,12 +11,12 @@ 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 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.matcher.ItemMatcher; import org.dspace.app.rest.matcher.VersionMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ViewEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ViewEventRestRepositoryIT.java index a84b2138a1..5683bd30a8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ViewEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ViewEventRestRepositoryIT.java @@ -17,13 +17,13 @@ import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.SiteBuilder; import org.dspace.app.rest.model.ViewEventRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SiteBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java index 4a877825b9..74c0aa8a21 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java @@ -19,12 +19,12 @@ import java.util.List; import java.util.UUID; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.matcher.WorkflowDefinitionMatcher; import org.dspace.app.rest.model.WorkflowDefinitionRest; import org.dspace.app.rest.repository.WorkflowDefinitionRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; @@ -41,7 +41,8 @@ import org.junit.Test; */ public class WorkflowDefinitionRestRepositoryIT extends AbstractControllerIntegrationTest { - private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + private final XmlWorkflowFactory xmlWorkflowFactory + = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); private static final String WORKFLOW_DEFINITIONS_ENDPOINT = "/api/" + WorkflowDefinitionRest.CATEGORY + "/" + WorkflowDefinitionRest.NAME_PLURAL; 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 13837f6461..e8c7922dc0 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 @@ -28,14 +28,6 @@ import javax.ws.rs.core.MediaType; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.ClaimedTaskBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.WorkflowItemBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.WorkflowItemMatcher; @@ -46,6 +38,14 @@ 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.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.ClaimedTaskBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index c4a64c6de1..ba2695ca0b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -35,13 +35,6 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; -import org.dspace.app.rest.builder.BitstreamBuilder; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; @@ -51,6 +44,13 @@ 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.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java index e1e89cda02..c8e35cb10b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java @@ -12,9 +12,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.AdministratorOfFeature; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.converter.SiteConverter; @@ -25,6 +22,9 @@ import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Site; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java index 97225d7df0..707bf305f2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java @@ -12,10 +12,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.CCLicenseFeature; -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.ResourcePolicyBuilder; import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; @@ -23,6 +19,10 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -131,8 +131,8 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { .param("uri", itemUri) .param("eperson", eperson.getID().toString()) .param("feature", ccLicenseFeature.getName())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(authAdminCCLicense)))); // now verify that the property core.authorization.community-admin.item-admin.cc-license = false is respected diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java index 2be3ed9466..c6ea031f7d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java @@ -15,12 +15,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.AdministratorOfFeature; -import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.Site; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.SiteService; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/LoginOnBehalfOfFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/LoginOnBehalfOfFeatureRestIT.java index 207592c68a..8ab5a72ef1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/LoginOnBehalfOfFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/LoginOnBehalfOfFeatureRestIT.java @@ -12,7 +12,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.LoginOnBehalfOfFeature; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; @@ -21,6 +20,7 @@ import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.dspace.services.ConfigurationService; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java index 96cd2243fc..f09813b516 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java @@ -12,17 +12,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.ReinstateFeature; -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.WorkflowItemBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -131,8 +131,8 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { .param("uri", itemUri) .param("eperson", eperson.getID().toString()) .param("feature", reinstateFeature.getName())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(authAdminWithdraw)))); // now verify that the property core.authorization.community-admin.item.reinstatiate = false is respected diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java index dac47a3a88..0a89b44ef1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java @@ -12,17 +12,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.WithdrawFeature; -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.WorkflowItemBuilder; -import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -131,8 +131,8 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { .param("uri", itemUri) .param("eperson", eperson.getID().toString()) .param("feature", withdrawFeature.getName())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(authAdminWithdraw)))); // now verify that the property core.authorization.community-admin.item.withdraw = false is respected diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java index 53f3966c7c..a8c4b42173 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java @@ -21,10 +21,10 @@ import java.util.UUID; import org.dspace.app.bulkedit.MetadataImportException; import org.dspace.app.bulkedit.MetadataImportInvalidHeadingException; -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.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.MetadataField; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java index 5955edbdda..38b745e320 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java @@ -24,11 +24,11 @@ import java.util.ArrayList; import java.util.Iterator; 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.matcher.RelationshipMatcher; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index c470e8a045..98a7101a9b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -19,6 +19,7 @@ import javax.servlet.Filter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.rest.Application; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java index dec2461779..0f771df0b9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java @@ -7,8 +7,8 @@ */ package org.dspace.app.rest.test; -import org.dspace.app.rest.builder.EntityTypeBuilder; -import org.dspace.app.rest.builder.RelationshipTypeBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; import org.junit.Before; @@ -37,6 +37,7 @@ public class AbstractEntityIntegrationTest extends AbstractControllerIntegration * in relationship-types.xml */ @Before + @Override public void setUp() throws Exception { super.setUp(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index eef602f47c..9083887581 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.test; import org.apache.commons.lang3.StringUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.rest.Application; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; @@ -102,6 +103,7 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit * @param path path to perform GET against * @param username Username (may be null to perform an unauthenticated POST) * @param password Password + * @param requestEntity unknown -- not used. * @return ResponseEntity with a String body */ public ResponseEntity postResponseAsString(String path, String username, String password, diff --git a/dspace-server-webapp/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java deleted file mode 100644 index 245bb8f86f..0000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java +++ /dev/null @@ -1,101 +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; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -import com.maxmind.geoip2.DatabaseReader; -import com.maxmind.geoip2.model.CityResponse; -import com.maxmind.geoip2.record.City; -import com.maxmind.geoip2.record.Continent; -import com.maxmind.geoip2.record.Country; -import com.maxmind.geoip2.record.Location; -import com.maxmind.geoip2.record.MaxMind; -import com.maxmind.geoip2.record.Postal; -import com.maxmind.geoip2.record.RepresentedCountry; -import com.maxmind.geoip2.record.Traits; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.solr.MockSolrServer; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.stereotype.Service; - -/** - * Mock service that uses an embedded SOLR server for the statistics core. - * - *

- * NOTE: this class overrides one of the same name - * defined in dspace-api and declared as a bean there. - * See {@code test/data/dspaceFolder/config/spring/api/solr-services.xml}. Some kind of classpath - * magic makes this work. - */ -@Service -public class MockSolrLoggerServiceImpl - extends SolrLoggerServiceImpl - implements InitializingBean, DisposableBean { - - private static final Logger log = LogManager.getLogger(); - - private MockSolrServer mockSolrServer; - - public MockSolrLoggerServiceImpl() { - } - - @Override - public void afterPropertiesSet() throws Exception { - // Initialize our service with a Mock Solr statistics core - mockSolrServer = new MockSolrServer("statistics"); - solr = mockSolrServer.getSolrServer(); - - // Mock GeoIP's DatabaseReader - DatabaseReader reader = mock(DatabaseReader.class); - // Ensure that any tests requesting a city() get a mock/fake CityResponse - when(reader.city(any(InetAddress.class))).thenReturn(mockCityResponse()); - // Save this mock DatabaseReader to be used by SolrLoggerService - locationService = reader; - } - - /** - * A mock/fake GeoIP CityResponse, which will be used for *all* test statistical requests - * @return faked CityResponse - */ - private CityResponse mockCityResponse() { - List cityNames = new ArrayList(Collections.singleton("New York")); - City city = new City(cityNames, 1, 1, new HashMap()); - - List countryNames = new ArrayList(Collections.singleton("United States")); - Country country = new Country(countryNames, 1, 1, "US", new HashMap()); - - Location location = new Location(1, 1, 40.760498D, -73.9933D, 501, 1, "EST"); - - Postal postal = new Postal("10036", 1); - - return new CityResponse(city, new Continent(), country, location, new MaxMind(), postal, - country, new RepresentedCountry(), new ArrayList<>(0), - new Traits()); - } - - /** Remove all records. */ - public void reset() { - mockSolrServer.reset(); - } - - @Override - public void destroy() throws Exception { - mockSolrServer.destroy(); - } -} diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 174d6eb019..001dd0bd1f 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -235,6 +235,12 @@ just adding new jar in the classloader + + org.dspace + dspace-api + test-jar + test + org.springframework.boot spring-boot-starter-test diff --git a/pom.xml b/pom.xml index ed439a121d..158886cb2f 100644 --- a/pom.xml +++ b/pom.xml @@ -1010,6 +1010,12 @@ dspace-api 7.0-beta4-SNAPSHOT + + org.dspace + dspace-api + test-jar + 7.0-beta4-SNAPSHOT + org.dspace.modules additions From 9d6c8891ec8bff691f3576a2cbdef827e9b23baf Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Jul 2020 09:58:23 -0400 Subject: [PATCH 221/465] #2893 Rebase on current main, patch recently-added class, tidy documentation. --- .../org/dspace/statistics/MockSolrLoggerServiceImpl.java | 6 ------ .../rest/AnonymousAdditionalAuthorizationFilterIT.java | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java b/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java index 4c42fab932..791941c3b7 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java @@ -36,12 +36,6 @@ import org.springframework.stereotype.Service; /** * Mock service that uses an embedded SOLR server for the statistics core. - * - *

- * NOTE: this class overrides one of the same name - * defined in dspace-api and declared as a bean there. - * See {@code test/data/dspaceFolder/config/spring/api/solr-services.xml}. Some kind of classpath - * magic makes this work. */ @Service public class MockSolrLoggerServiceImpl diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java index 3202bd37c2..106018ff9b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AnonymousAdditionalAuthorizationFilterIT.java @@ -10,11 +10,11 @@ package org.dspace.app.rest; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; From e36e477be49c436bc6ef986ec8eaff26036208a3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 22 Jul 2020 16:35:18 +0200 Subject: [PATCH 222/465] Fixes due to merge --- .../export/RetryFailedOpenUrlTracker.java | 36 +---- ...iledOpenUrlTrackerScriptConfiguration.java | 124 ++++++------------ .../config/spring/api/scripts.xml | 2 +- .../export/ITRetryFailedOpenUrlTracker.java | 19 ++- .../app/rest/ScriptRestRepositoryIT.java | 4 +- dspace/config/spring/api/scripts.xml | 2 +- 6 files changed, 66 insertions(+), 121 deletions(-) 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 index 0795b3cdf0..b30da8bd8a 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 @@ -7,19 +7,19 @@ */ 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.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; import org.dspace.statistics.export.service.OpenUrlService; +import org.dspace.utils.DSpace; /** * 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; @@ -57,6 +57,11 @@ public class RetryFailedOpenUrlTracker extends DSpaceRunnable { } } + public RetryFailedOpenUrlTrackerScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("retry-tracker", + RetryFailedOpenUrlTrackerScriptConfiguration.class); + } + /** * Setups the parameters * @@ -70,7 +75,6 @@ public class RetryFailedOpenUrlTracker extends DSpaceRunnable { throw new ParseException("At least one of the parameters (-a, -r, -h) is required!"); } - if (commandLine.hasOption('h')) { help = true; } @@ -82,30 +86,4 @@ public class RetryFailedOpenUrlTracker extends DSpaceRunnable { } } - private RetryFailedOpenUrlTracker() { - Options options = constructOptions(); - this.options = options; - } - - /** - * Constructs the script options - * - * @return the constructed 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("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); - - return options; - } - } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java index 6ec3ad75e4..b5d65aa4e5 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java @@ -7,109 +7,67 @@ */ package org.dspace.statistics.export; +import java.sql.SQLException; + 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.DSpaceRunnable; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.dspace.statistics.export.factory.OpenURLTrackerLoggerServiceFactory; -import org.dspace.statistics.export.service.OpenUrlService; +import org.springframework.beans.factory.annotation.Autowired; /** - * Script to retry the failed url transmissions to IRUS - * This script also has an option to add new failed urls for testing purposes + * The {@link ScriptConfiguration} for the {@link RetryFailedOpenUrlTracker} script */ -public class RetryFailedOpenUrlTracker extends DSpaceRunnable { +public class RetryFailedOpenUrlTrackerScriptConfiguration + extends ScriptConfiguration { - private Context context = null; - private String lineToAdd = null; - private boolean help = false; - private boolean retryFailed = false; + @Autowired + private AuthorizeService authorizeService; - private OpenUrlService openUrlService; + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } /** - * Run the script - * When the -a option is used, a new "failed" url will be added to the database + * Generic setter for the dspaceRunnableClass * - * @throws Exception + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this RetryFailedOpenUrlTrackerScriptConfiguration */ - public void internalRun() throws Exception { - if (help) { - printHelp(); - return; - } - context.turnOffAuthorisationSystem(); + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } - if (StringUtils.isNotBlank(lineToAdd)) { - openUrlService.logfailed(context, lineToAdd); - 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(); + @Override + public boolean isAllowedToExecute(Context context) { try { - context.complete(); - } catch (Exception e) { - handler.logError(e.getMessage()); + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); } } - public ScriptConfiguration getScriptConfiguration() { - return null; - } + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); - /** - * Setups the parameters - * - * @throws ParseException - */ - public void setup() throws ParseException { - context = new Context(); - openUrlService = OpenURLTrackerLoggerServiceFactory.getInstance().getOpenUrlService(); + options.addOption("a", true, "Add a new \"failed\" row to the table with a url (test purposes only)"); + options.getOption("a").setType(String.class); - if (!(commandLine.hasOption('a') || commandLine.hasOption('r') || commandLine.hasOption('h'))) { - throw new ParseException("At least one of the parameters (-a, -r, -h) is required!"); + 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); + + super.options = options; } - - - if (commandLine.hasOption('h')) { - help = true; - } - if (commandLine.hasOption('a')) { - lineToAdd = commandLine.getOptionValue('a'); - } - if (commandLine.hasOption('r')) { - retryFailed = true; - } - } - - private RetryFailedOpenUrlTracker() { - Options options = constructOptions(); - this.options = options; - } - - /** - * Constructs the script options - * - * @return the constructed 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("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); - return 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 52fe95b31c..fed51ea9fb 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 @@ -24,7 +24,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 index 3883b12495..27b37aaf73 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 @@ -18,6 +18,7 @@ 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.configuration.ScriptConfiguration; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ScriptService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -82,11 +83,13 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { public void testAddNewFailedUrl() throws Exception { TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); - DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); + ScriptConfiguration retryOpenUrlTrackerConfig = scriptService.getScriptConfiguration("retry-tracker"); + DSpaceRunnable retryOpenUrlTracker = + scriptService.createDSpaceRunnableForScriptConfiguration(retryOpenUrlTrackerConfig); String urlToAdd = "test-failed-url"; String[] args = {"-a", urlToAdd}; - retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); + retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler, eperson); retryOpenUrlTracker.internalRun(); List all = failedOpenURLTrackerService.findAll(context); @@ -105,7 +108,9 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { public void testReprocessAllUrls() throws Exception { TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); - DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); + ScriptConfiguration retryOpenUrlTrackerConfig = scriptService.getScriptConfiguration("retry-tracker"); + DSpaceRunnable retryOpenUrlTracker = + scriptService.createDSpaceRunnableForScriptConfiguration(retryOpenUrlTrackerConfig); String[] args = {"-r"}; OpenURLTracker tracker1 = failedOpenURLTrackerService.create(context); @@ -116,7 +121,7 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { tracker3.setUrl("test-url-3"); - retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); + retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler, eperson); retryOpenUrlTracker.internalRun(); List all = failedOpenURLTrackerService.findAll(context); @@ -138,7 +143,9 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { public void testReprocessPartOfUrls() throws Exception { TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); - DSpaceRunnable retryOpenUrlTracker = scriptService.getScriptForName("retry-tracker"); + ScriptConfiguration retryOpenUrlTrackerConfig = scriptService.getScriptConfiguration("retry-tracker"); + DSpaceRunnable retryOpenUrlTracker = + scriptService.createDSpaceRunnableForScriptConfiguration(retryOpenUrlTrackerConfig); String[] args = {"-r"}; OpenURLTracker tracker1 = failedOpenURLTrackerService.create(context); @@ -153,7 +160,7 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { tracker5.setUrl("test-url-5"); - retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler); + retryOpenUrlTracker.initialize(args, testDSpaceRunnableHandler, eperson); retryOpenUrlTracker.internalRun(); List all = failedOpenURLTrackerService.findAll(context); 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 c1ba31305e..a68a080e59 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 @@ -82,7 +82,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), scriptConfigurations.get(2).getDescription()), ScriptMatcher.matchScript(scriptConfigurations.get(3).getName(), - scriptConfigurations.get(3).getDescription()) + scriptConfigurations.get(3).getDescription()), + ScriptMatcher.matchScript(scriptConfigurations.get(4).getName(), + scriptConfigurations.get(4).getDescription()) ))); } diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index aa6e6ad2c7..7504fefee1 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -19,7 +19,7 @@ - + From 459dc2163b5906e139d61de1efa35a85f9670928 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Jul 2020 12:02:15 -0400 Subject: [PATCH 223/465] Fix bad LGTM default that suppresses test JARs. --- .lgtm.yml | 1 + lgtm-build.sh | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 lgtm-build.sh diff --git a/.lgtm.yml b/.lgtm.yml index 132de8a6de..5f3609642c 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -7,3 +7,4 @@ extraction: index: # Specify the Java version required to build the project java_version: 11 + build_command: lgtm-build.sh diff --git a/lgtm-build.sh b/lgtm-build.sh new file mode 100644 index 0000000000..6ea7fb9258 --- /dev/null +++ b/lgtm-build.sh @@ -0,0 +1,13 @@ +/opt/dist/tools/preload_tracer /opt/out/snapshot/workspace/apache-maven-3.6.0/bin/mvn clean package \ + -f pom.xml \ + -B -V -e \ + -Dfindbugs.skip \ + -Dcheckstyle.skip \ + -Dpmd.skip=true \ + -Denforcer.skip \ + -Dmaven.javadoc.skip \ + -DskipTests \ + -Dlicense.skip=true \ + -Drat.skip=true \ + -Dmaven.repo.local=/opt/work/semmle_data/maven_repo \ + -t /opt/work/.m2/toolchains.xml From daed71b91bd7985507c67883ac4f3e8916320e21 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Jul 2020 15:07:25 -0400 Subject: [PATCH 224/465] Add shebang to lgtm-build in attempt to make it work. --- lgtm-build.sh | 2 ++ 1 file changed, 2 insertions(+) mode change 100644 => 100755 lgtm-build.sh diff --git a/lgtm-build.sh b/lgtm-build.sh old mode 100644 new mode 100755 index 6ea7fb9258..70d43f2167 --- a/lgtm-build.sh +++ b/lgtm-build.sh @@ -1,3 +1,5 @@ +$! /bin/sh + /opt/dist/tools/preload_tracer /opt/out/snapshot/workspace/apache-maven-3.6.0/bin/mvn clean package \ -f pom.xml \ -B -V -e \ From ef353a5f44e6dc6541deb5f027b41b91d127399a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Jul 2020 15:22:05 -0400 Subject: [PATCH 225/465] Try again to make LGTM run custom command --- .lgtm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lgtm.yml b/.lgtm.yml index 5f3609642c..08071dc430 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -7,4 +7,4 @@ extraction: index: # Specify the Java version required to build the project java_version: 11 - build_command: lgtm-build.sh + build_command: ./lgtm-build.sh From 453e1c9c914b975ca3f2f5d8607634a092fbfffc Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Jul 2020 15:28:12 -0400 Subject: [PATCH 226/465] Yet another attempt to understand undocumented LGTM build command(s) --- lgtm-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lgtm-build.sh b/lgtm-build.sh index 70d43f2167..984f43b678 100755 --- a/lgtm-build.sh +++ b/lgtm-build.sh @@ -1,6 +1,6 @@ $! /bin/sh -/opt/dist/tools/preload_tracer /opt/out/snapshot/workspace/apache-maven-3.6.0/bin/mvn clean package \ +/opt/dist/tools/preload_tracer mvn clean package \ -f pom.xml \ -B -V -e \ -Dfindbugs.skip \ From c1139ff0a3e425f2153aca73782eb31a2df8926d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Jul 2020 15:42:13 -0400 Subject: [PATCH 227/465] Trim lgtm build command some more --- lgtm-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lgtm-build.sh b/lgtm-build.sh index 984f43b678..0c0e115ce2 100755 --- a/lgtm-build.sh +++ b/lgtm-build.sh @@ -1,6 +1,6 @@ $! /bin/sh -/opt/dist/tools/preload_tracer mvn clean package \ +mvn clean package \ -f pom.xml \ -B -V -e \ -Dfindbugs.skip \ From 836876508f42f44583b8aadd22d4859473b3b9d9 Mon Sep 17 00:00:00 2001 From: jonas-atmire Date: Thu, 23 Jul 2020 08:59:10 +0200 Subject: [PATCH 228/465] Addition of explanation to tests + Small comment update --- .../dspace/app/rest/repository/BundleItemLinkRepository.java | 2 +- .../java/org/dspace/app/rest/BitstreamRestRepositoryIT.java | 5 +++++ .../java/org/dspace/app/rest/BundleRestRepositoryIT.java | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java index 3d3e5226f3..4df81d5054 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -36,7 +36,7 @@ public class BundleItemLinkRepository extends AbstractDSpaceRestRepository BundleService bundleService; /** - * Get the first item the provided bundle resides in + * Get the item where the provided bundle resides in */ @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") public ItemRest getItem(@Nullable HttpServletRequest request, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 9cbb5363b6..691bb7d07d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -1199,6 +1199,11 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest } @Test + /** + * This test proves that, if a bitstream is linked to multiple bundles, we only ever return the first bundle. + * **NOTE: DSpace does NOT support or expect to have a bitstream linked to multiple bundles**. + * But, because the database does allow for it, this test simply proves the REST API will respond without an error. + */ public void linksToFirstBundleWhenMultipleBundles() throws Exception { //We turn off the authorization system in order to create the structure as defined below context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 20590a1992..4aa25946ff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -660,6 +660,11 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { } @Test + /** + * This test proves that, if a bundle is linked to multiple items, we only ever return the first item. + * **NOTE: DSpace does NOT support or expect to have a bundle linked to multiple items**. + * But, because the database does allow for it, this test simply proves the REST API will respond without an error + */ public void linksToFirstItemWhenMultipleItems() throws Exception { context.turnOffAuthorisationSystem(); From 7a269f3460525eb6395af4b1a71fa3a91aafbb0b Mon Sep 17 00:00:00 2001 From: jonas-atmire Date: Thu, 23 Jul 2020 10:00:51 +0200 Subject: [PATCH 229/465] Curate.cfg properties cleanup based on PR feedback - https://github.com/DSpace/DSpace/pull/2820 --- dspace/config/modules/curate.cfg | 33 +++----------------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index c6b7914c8f..df6d4f855a 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -11,8 +11,8 @@ plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.NoOpCurationTask = noop plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ProfileFormats = profileformats plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RequiredMetadata = requiredmetadata -plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ClamScan = vscan -plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate +#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ClamScan = vscan +#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MetadataValueLinkChecker = checklinks # add new tasks here (or in additional config files) @@ -25,33 +25,6 @@ curate.taskqueue.dir = ${dspace.dir}/ctqueues # (optional) directory location of scripted (non-java) tasks # curate.script.dir = ${dspace.dir}/ctscripts -# Friendly names for curation tasks to appear in admin UI -# Also acts as a filter - i.e. tasks not enumerated here can still -# be invoked on cmd line, etc - just not in UI -curate.ui.tasknames = profileformats = Profile Bitstream Formats -curate.ui.tasknames = requiredmetadata = Check for Required Metadata -curate.ui.tasknames = checklinks = Check Links in Metadata - -# Tasks may be organized into named groups which display together in UI drop-downs -# curate.ui.taskgroups = \ -# general = General Purpose Tasks, - -# Group membership is defined using comma-separated lists of task names, one property per group -# curate.ui.taskgroup.general = profileformats, requiredmetadata, checklinks - -# Name of queue used when tasks queued in Admin UI -curate.ui.queuename = admin_ui - -# Localized names for curation status codes in Admin UI -curate.ui.statusmessages = \ - -3 = Unknown Task, \ - -2 = No Status Set, \ - -1 = Error, \ - 0 = Success, \ - 1 = Fail, \ - 2 = Skip, \ - other = Invalid Status - - +# Ensure list of Curation Tasks (defined above) is available via the REST API /api/config/properties endpoint rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask From b14f52313e0e992d3ca8c1d26046f9d4c7e809bb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 23 Jul 2020 10:32:10 -0400 Subject: [PATCH 230/465] Set JAVA_HOME trying to make LGTM work as UNdocumented. --- .lgtm.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.lgtm.yml b/.lgtm.yml index 08071dc430..f89a38ccd4 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -4,6 +4,9 @@ extraction: java: + before_index: + # Magic path from IncPlusPlus/bigtoolbox-io + export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" index: # Specify the Java version required to build the project java_version: 11 From 0076e6b37b178059c0e788f5726bd9cf19384096 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 23 Jul 2020 13:00:36 -0400 Subject: [PATCH 231/465] Clean up a lot of ErrorProne and Checkstyle warnings. #2983 So I can see what else is wrong. --- .../app/bulkedit/MetadataImportTest.java | 12 ++++++------ .../dspace/builder/AbstractCRUDBuilder.java | 4 ++++ .../builder/AbstractDSpaceObjectBuilder.java | 15 ++++++++++----- .../org/dspace/builder/BitstreamBuilder.java | 4 +++- .../java/org/dspace/builder/BundleBuilder.java | 5 ++++- .../org/dspace/builder/EPersonBuilder.java | 18 ++++++++---------- .../org/dspace/builder/EntityTypeBuilder.java | 3 ++- .../dspace/builder/MetadataFieldBuilder.java | 18 +++++------------- .../dspace/builder/MetadataSchemaBuilder.java | 13 ++++--------- .../org/dspace/builder/ProcessBuilder.java | 2 ++ .../dspace/builder/RelationshipBuilder.java | 3 ++- .../builder/RelationshipTypeBuilder.java | 3 ++- .../MockCCLicenseConnectorServiceImpl.java | 3 +++ .../java/org/dspace/solr/MockSolrServer.java | 2 +- .../statistics/MockSolrLoggerServiceImpl.java | 4 ---- .../xmlworkflow/XmlWorkflowFactoryTest.java | 15 +++++++++------ 16 files changed, 65 insertions(+), 59 deletions(-) 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 index c0eb2789bc..a4f6b67b06 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java @@ -22,16 +22,16 @@ 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(); + private final ItemService itemService + = ContentServiceFactory.getInstance().getItemService(); + private final CollectionService collectionService + = ContentServiceFactory.getInstance().getCollectionService(); + private final CommunityService communityService + = ContentServiceFactory.getInstance().getCommunityService(); @Test public void metadataImportTest() throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java index 72e08b062a..ff2bef51c2 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractCRUDBuilder.java @@ -13,6 +13,8 @@ import org.dspace.service.DSpaceCRUDService; /** * @author Jonas Van Goolen - (jonas@atmire.com) + * + * @param A specific kind of ReloadableEntity. */ public abstract class AbstractCRUDBuilder extends AbstractBuilder { @@ -20,8 +22,10 @@ public abstract class AbstractCRUDBuilder extends Ab super(context); } + @Override protected abstract DSpaceCRUDService getService(); + @Override public abstract T build(); public void delete(T dso) throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index 848bac54f4..df92d0ea4b 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -43,12 +43,15 @@ public abstract class AbstractDSpaceObjectBuilder this.context = context; } + @Override public abstract void cleanup() throws Exception; + @Override protected abstract DSpaceObjectService getService(); + @Override protected B handleException(final Exception e) { log.error(e.getMessage(), e); return null; @@ -231,13 +234,15 @@ public abstract class AbstractDSpaceObjectBuilder return (B) this; } + @Override public abstract T build() throws SQLException, AuthorizeException; + @Override public void delete(Context c, T dso) throws Exception { - if (dso != null) { - getService().delete(c, dso); - } - c.complete(); - indexingService.commit(); + if (dso != null) { + getService().delete(c, dso); + } + c.complete(); + indexingService.commit(); } } diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index 2b19489475..b8942a17d0 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -129,6 +129,7 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return this; } + @Override public Bitstream build() { try { bitstreamService.update(context, bitstream); @@ -152,7 +153,7 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { @Override public void cleanup() throws Exception { - try (Context c = new Context()) { + try (Context c = new Context()) { c.turnOffAuthorisationSystem(); // Ensure object and any related objects are reloaded before checking to see what needs cleanup bitstream = c.reloadEntity(bitstream); @@ -163,6 +164,7 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { } } + @Override protected DSpaceObjectService getService() { return bitstreamService; } diff --git a/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java index 6b15ca432f..614cd54c6d 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java @@ -25,7 +25,7 @@ public class BundleBuilder extends AbstractDSpaceObjectBuilder { private Bundle bundle; private Item item; private String name; - private List bitstreams = new ArrayList<>(); + private final List bitstreams = new ArrayList<>(); protected BundleBuilder(Context context) { super(context); @@ -52,6 +52,7 @@ public class BundleBuilder extends AbstractDSpaceObjectBuilder { return this; } + @Override public void cleanup() throws Exception { try (Context c = new Context()) { c.turnOffAuthorisationSystem(); @@ -64,10 +65,12 @@ public class BundleBuilder extends AbstractDSpaceObjectBuilder { } } + @Override protected DSpaceObjectService getService() { return bundleService; } + @Override public Bundle build() throws SQLException, AuthorizeException { bundle = bundleService.create(context, item, name); diff --git a/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java index 2f2c7522a5..08257030af 100644 --- a/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java @@ -10,6 +10,7 @@ package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; import java.util.UUID; +import org.apache.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.DSpaceObjectService; @@ -19,6 +20,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; public class EPersonBuilder extends AbstractDSpaceObjectBuilder { + private static final Logger LOG = Logger.getLogger(EPersonBuilder.class); private EPerson ePerson; @@ -39,20 +41,18 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { } } + @Override protected DSpaceObjectService getService() { return ePersonService; } + @Override public EPerson build() { try { ePersonService.update(context, ePerson); indexingService.commit(); - } catch (SearchServiceException e) { - e.printStackTrace(); - } catch (SQLException e) { - e.printStackTrace(); - } catch (AuthorizeException e) { - e.printStackTrace(); + } catch (SearchServiceException | SQLException | AuthorizeException e) { + LOG.warn("Failed to complete the EPerson", e); } return ePerson; } @@ -65,10 +65,8 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { private EPersonBuilder create() { try { ePerson = ePersonService.create(context); - } catch (SQLException e) { - e.printStackTrace(); - } catch (AuthorizeException e) { - e.printStackTrace(); + } catch (SQLException | AuthorizeException e) { + LOG.warn("Failed to create the EPerson", e); } return this; } diff --git a/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java index 5c17e9ef53..ae0e807198 100644 --- a/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java @@ -53,6 +53,7 @@ public class EntityTypeBuilder extends AbstractBuilder { } } + @Override public Process build() { try { processService.update(context, process); @@ -68,6 +69,7 @@ public class ProcessBuilder extends AbstractBuilder { return process; } + @Override protected ProcessService getService() { return processService; } diff --git a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java index f53340c345..773a4a8b8b 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java @@ -56,6 +56,7 @@ public class RelationshipBuilder extends AbstractBuilder retrieveLicenses(String language) { Map ccLicenses = new HashMap<>(); CCLicense mockLicense1 = createMockLicense(1, new int[]{3, 2, 3}); @@ -89,6 +90,7 @@ public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorService * @param answerMap - the answers to the different field questions * @return the CC License URI */ + @Override public String retrieveRightsByQuestion(final String licenseId, final String language, final Map answerMap) { @@ -105,6 +107,7 @@ public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorService * @return a mock license RDF document or null when the URI contains invalid * @throws IOException */ + @Override public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException { if (!StringUtils.contains(licenseURI, "invalid")) { InputStream cclicense = null; diff --git a/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java index d7893f779d..6faf9a7d1b 100644 --- a/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java +++ b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java @@ -110,7 +110,7 @@ public class MockSolrServer { server.deleteByQuery("*:*"); server.commit(); } catch (SolrServerException | IOException e) { - e.printStackTrace(System.err); + log.error("Failed to empty Solr index: {}", e.getMessage(), e); } loadedCores.put(coreName, server); diff --git a/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java b/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java index 791941c3b7..6216f7223a 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/statistics/MockSolrLoggerServiceImpl.java @@ -27,8 +27,6 @@ import com.maxmind.geoip2.record.MaxMind; import com.maxmind.geoip2.record.Postal; import com.maxmind.geoip2.record.RepresentedCountry; import com.maxmind.geoip2.record.Traits; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.solr.MockSolrServer; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; @@ -42,8 +40,6 @@ public class MockSolrLoggerServiceImpl extends SolrLoggerServiceImpl implements InitializingBean, DisposableBean { - private static final Logger log = LogManager.getLogger(); - private MockSolrServer mockSolrServer; public MockSolrLoggerServiceImpl() { diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java index a19e6a2622..7118d82621 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java @@ -11,6 +11,7 @@ import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; import java.sql.SQLException; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; @@ -35,9 +36,11 @@ import org.junit.Test; */ public class XmlWorkflowFactoryTest extends AbstractUnitTest { - private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - private XmlWorkflowFactory xmlWorkflowFactory + private final CollectionService collectionService + = ContentServiceFactory.getInstance().getCollectionService(); + private final CommunityService communityService + = ContentServiceFactory.getInstance().getCommunityService(); + private final XmlWorkflowFactory xmlWorkflowFactory = new DSpace().getServiceManager().getServiceByName("xmlWorkflowFactory", XmlWorkflowFactoryImpl.class); private Community owningCommunity; @@ -47,7 +50,7 @@ public class XmlWorkflowFactoryTest extends AbstractUnitTest { /** * log4j category */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(XmlWorkflowFactoryTest.class); + private static final Logger log = LogManager.getLogger(XmlWorkflowFactoryTest.class); /** * This method will be run before every test as per @Before. It will @@ -112,12 +115,12 @@ public class XmlWorkflowFactoryTest extends AbstractUnitTest { @Test public void workflowMapping_NonMappedCollection() throws WorkflowConfigurationException { Workflow workflow = xmlWorkflowFactory.getWorkflow(this.nonMappedCollection); - assertEquals(workflow.getID(), "defaultWorkflow"); + assertEquals("defaultWorkflow", workflow.getID()); } @Test public void workflowMapping_MappedCollection() throws WorkflowConfigurationException { Workflow workflow = xmlWorkflowFactory.getWorkflow(this.mappedCollection); - assertEquals(workflow.getID(), "selectSingleReviewer"); + assertEquals("selectSingleReviewer", workflow.getID()); } } From ca31be285989295b93ba66c211230e786faf34f3 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 23 Jul 2020 13:30:51 -0400 Subject: [PATCH 232/465] #2983 More Checkstyle errors --- .../src/test/java/org/dspace/builder/EPersonBuilder.java | 5 +++-- .../java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java index 08257030af..256b3432d4 100644 --- a/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java @@ -10,8 +10,9 @@ package org.dspace.builder; import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; @@ -20,7 +21,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; public class EPersonBuilder extends AbstractDSpaceObjectBuilder { - private static final Logger LOG = Logger.getLogger(EPersonBuilder.class); + private static final Logger LOG = LogManager.getLogger(EPersonBuilder.class); private EPerson ePerson; diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java index 7118d82621..644c834a80 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java @@ -7,12 +7,13 @@ */ package org.dspace.xmlworkflow; +import java.io.IOException; import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; import java.sql.SQLException; -import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; @@ -97,7 +98,7 @@ public class XmlWorkflowFactoryTest extends AbstractUnitTest { this.collectionService.delete(context, this.nonMappedCollection); this.collectionService.delete(context, this.mappedCollection); this.communityService.delete(context, this.owningCommunity); - } catch (Exception e) { + } catch (IOException | SQLException | AuthorizeException e) { log.error("Error in destroy", e); } From 1ec7c38675944b26b6ec2c81642f8510abefe678 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 23 Jul 2020 21:15:25 -0400 Subject: [PATCH 233/465] Use skipTests not maven.test.skip so tests always built; fix dependencies. #2983 --- dspace-api/pom.xml | 5 ++- .../xmlworkflow/XmlWorkflowFactoryTest.java | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace/modules/additions/pom.xml | 9 +++-- dspace/modules/server/pom.xml | 9 +++-- dspace/pom.xml | 10 +++--- pom.xml | 34 ++++++++++++------- 8 files changed, 47 insertions(+), 26 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 7a8662bcff..098f0bdc72 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -137,7 +137,7 @@ false @@ -158,7 +158,7 @@ false - maven.test.skip + skipTests false @@ -480,7 +480,6 @@ org.apache.solr solr-cell - ${solr.client.version} diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java index 644c834a80..c7239f1f5a 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowFactoryTest.java @@ -7,10 +7,10 @@ */ package org.dspace.xmlworkflow; -import java.io.IOException; import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; +import java.io.IOException; import java.sql.SQLException; import org.apache.logging.log4j.LogManager; diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index d415d12539..797be8d10f 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -37,7 +37,7 @@ false - maven.test.skip + skipTests false diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 95ab75f52e..1a355964d2 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -25,7 +25,7 @@ false - maven.test.skip + skipTests false diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 59ba279c3f..9de2329bae 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -49,7 +49,7 @@ false - maven.test.skip + skipTests false @@ -158,7 +158,6 @@ org.dspace dspace-api - 7.0-beta4-SNAPSHOT test-jar test @@ -174,6 +173,12 @@ org.dspace dspace-api + + org.dspace + dspace-api + test-jar + test + org.dspace dspace-api-lang diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 001dd0bd1f..d60cc32c8c 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -84,7 +84,7 @@ just adding new jar in the classloader false - maven.test.skip + skipTests false @@ -194,7 +194,6 @@ just adding new jar in the classloader org.dspace dspace-server-webapp - 7.0-beta4-SNAPSHOT test-jar test @@ -241,6 +240,12 @@ just adding new jar in the classloader test-jar test + + org.dspace + dspace-server-webapp + test-jar + test + org.springframework.boot spring-boot-starter-test diff --git a/dspace/pom.xml b/dspace/pom.xml index f468746b62..58f1af66b5 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -148,15 +148,17 @@ - + test-coverage-report false - maven.test.skip + skipTests false diff --git a/pom.xml b/pom.xml index 158886cb2f..c7208d223f 100644 --- a/pom.xml +++ b/pom.xml @@ -475,29 +475,33 @@ - + skiptests - + - !maven.test.skip + !skipTests - true + true - + skipits - + !skipITs @@ -532,7 +536,7 @@ false - maven.test.skip + skipTests false @@ -572,7 +576,7 @@ false - maven.test.skip + skipTests false @@ -1042,6 +1046,12 @@ dspace-services 7.0-beta4-SNAPSHOT + + org.dspace + dspace-server-webapp + test-jar + 7.0-beta4-SNAPSHOT + org.dspace dspace-rdf From 46dc547db86afb4f1bcfc6863bc9ae00a0c63e83 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 24 Jul 2020 10:33:26 -0400 Subject: [PATCH 234/465] #2893 Reconfigure test building on LGTM --- lgtm-build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lgtm-build.sh b/lgtm-build.sh index 0c0e115ce2..354bcd347c 100755 --- a/lgtm-build.sh +++ b/lgtm-build.sh @@ -8,7 +8,8 @@ mvn clean package \ -Dpmd.skip=true \ -Denforcer.skip \ -Dmaven.javadoc.skip \ - -DskipTests \ + -DskipTests=true \ + -Dmaven.test.skip=false \ -Dlicense.skip=true \ -Drat.skip=true \ -Dmaven.repo.local=/opt/work/semmle_data/maven_repo \ From bc4663b783b8bcaa09794f4473026d2b810c0f91 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 24 Jul 2020 10:39:23 -0400 Subject: [PATCH 235/465] #2893 try LGTM with default command again --- .lgtm.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.lgtm.yml b/.lgtm.yml index f89a38ccd4..132de8a6de 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -4,10 +4,6 @@ extraction: java: - before_index: - # Magic path from IncPlusPlus/bigtoolbox-io - export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" index: # Specify the Java version required to build the project java_version: 11 - build_command: ./lgtm-build.sh From 20a3d94a1b9c39b15067189079777c0b08919b12 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 24 Jul 2020 10:53:34 -0400 Subject: [PATCH 236/465] #2893 Re-enable test running in Travis; document changes to how-to-test. --- .travis.yml | 6 +++--- README.md | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index dfc4c31799..fc8680671b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,14 +30,14 @@ install: "echo 'Skipping install stage, dependencies will be downloaded during b script: # Summary of flags used (below): # license:check => Validate all source code license headers - # -Dmaven.test.skip=false => Enable DSpace Unit Tests + # -DskipTests=false => Enable DSpace Unit Tests # -DskipITs=false => Enable DSpace Integration Tests - # -Pdspace-rest => Enable optional dspace-rest module as part of build + # -Pdspace-rest => Enable optional dspace-rest module as part of build # -P !assembly => Skip assembly of "dspace-installer" directory (as it can be memory intensive) # -B => Maven batch/non-interactive mode (recommended for CI) # -V => Display Maven version info before build # -Dsurefire.rerunFailingTestsCount=2 => try again for flakey tests, and keep track of/report on number of retries - - "mvn clean install license:check -Dmaven.test.skip=false -DskipITs=false -Pdspace-rest -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2" + - "mvn clean install license:check -DskipTests=false -DskipITs=false -Pdspace-rest -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2" # After a successful build and test (see 'script'), send code coverage reports to coveralls.io # These code coverage reports are generated by jacoco-maven-plugin (during test process above). diff --git a/README.md b/README.md index f9b8d8c52a..32849d9e30 100644 --- a/README.md +++ b/README.md @@ -90,33 +90,33 @@ run automatically by [Travis CI](https://travis-ci.com/DSpace/DSpace/) for all P * How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`): ``` - mvn clean test -Dmaven.test.skip=false -DskipITs=false + mvn clean test -DskipTests=false -DskipITs=false ``` * How to run just Unit Tests: ``` - mvn test -Dmaven.test.skip=false + mvn test -DskipTests=false ``` * How to run a *single* Unit Test ``` # Run all tests in a specific test class # NOTE: failIfNoTests=false is required to skip tests in other modules - mvn test -Dmaven.test.skip=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false + mvn test -DskipTests=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false # Run one test method in a specific test class - mvn test -Dmaven.test.skip=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false + mvn test -DskipTests=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false ``` * How to run Integration Tests (requires enabling Unit tests too) ``` - mvn verify -Dmaven.test.skip=false -DskipITs=false + mvn verify -DskipTests=false -DskipITs=false ``` * How to run a *single* Integration Test (requires enabling Unit tests too) ``` # Run all integration tests in a specific test class # NOTE: failIfNoTests=false is required to skip tests in other modules - mvn test -Dmaven.test.skip=false -DskipITs=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false + mvn test -DskipTests=false -DskipITs=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false # Run one test method in a specific test class - mvn test -Dmaven.test.skip=false -DskipITs=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false + mvn test -DskipTests=false -DskipITs=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false ``` * How to run only tests of a specific DSpace module ``` From f6a0d76094e4120d7a6e1226b216cd9df3f8a08d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 24 Jul 2020 11:19:02 -0400 Subject: [PATCH 237/465] #2893 Discard un-needed custom LGTM build script. --- lgtm-build.sh | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100755 lgtm-build.sh diff --git a/lgtm-build.sh b/lgtm-build.sh deleted file mode 100755 index 354bcd347c..0000000000 --- a/lgtm-build.sh +++ /dev/null @@ -1,16 +0,0 @@ -$! /bin/sh - -mvn clean package \ - -f pom.xml \ - -B -V -e \ - -Dfindbugs.skip \ - -Dcheckstyle.skip \ - -Dpmd.skip=true \ - -Denforcer.skip \ - -Dmaven.javadoc.skip \ - -DskipTests=true \ - -Dmaven.test.skip=false \ - -Dlicense.skip=true \ - -Drat.skip=true \ - -Dmaven.repo.local=/opt/work/semmle_data/maven_repo \ - -t /opt/work/.m2/toolchains.xml From eb8f9375954a47fb8f55b09776b489521474a672 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jul 2020 15:27:01 -0500 Subject: [PATCH 238/465] Limit the errors we log --- .../exception/DSpaceApiExceptionControllerAdvice.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 6cf297ebf6..4cba9153d3 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 @@ -150,8 +150,12 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH //Make sure Spring picks up this exception request.setAttribute(EXCEPTION_ATTRIBUTE, ex); - //Log the full error and status code - log.error("{} (status:{})", message, statusCode, ex); + // For now, just logging server errors. + // We don't want to fill logs with bad/invalid REST API requests. + if (statusCode == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { + // Log the full error and status code + log.error("{} (status:{})", message, statusCode, ex); + } //Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter response.sendError(statusCode, message); From 7f0a6771bc7b0f36a7bb530b72f74da7b7d7cedd Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 24 Jul 2020 17:13:01 -0400 Subject: [PATCH 239/465] #2893 Rework a dspace-api IT to use Builders as a demo. --- ...aExportTest.java => MetadataExportIT.java} | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) rename dspace-api/src/test/java/org/dspace/app/bulkedit/{MetadataExportTest.java => MetadataExportIT.java} (52%) diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java similarity index 52% rename from dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java rename to dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java index 9594e2a2b1..7ebe7b781c 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java @@ -14,49 +14,53 @@ import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; -import org.dspace.AbstractIntegrationTest; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.WorkspaceItemBuilder; 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(); +public class MetadataExportIT + extends AbstractIntegrationTestWithDatabase { + private final ItemService itemService + = ContentServiceFactory.getInstance().getItemService(); + private final InstallItemService installItemService + = ContentServiceFactory.getInstance().getInstallItemService(); + private final 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); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + WorkspaceItem wi = WorkspaceItemBuilder.createWorkspaceItem(context, collection).build(); Item item = wi.getItem(); itemService.addMetadata(context, item, "dc", "contributor", "author", null, "Donald, Smith"); item = installItemService.installItem(context, wi); context.restoreAuthSystemState(); - String fileLocation = configurationService.getProperty("dspace.dir") + testProps.get("test.exportcsv") - .toString(); + 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(); + 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); + 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")); @@ -64,8 +68,6 @@ public class MetadataExportTest extends AbstractIntegrationTest { context.turnOffAuthorisationSystem(); itemService.delete(context, itemService.find(context, item.getID())); - collectionService.delete(context, collectionService.find(context, collection.getID())); - communityService.delete(context, communityService.find(context, community.getID())); context.restoreAuthSystemState(); } } From c3c683e23bf470a26dc62dc44d2b6aaf86d53567 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jul 2020 16:32:31 -0500 Subject: [PATCH 240/465] Fix two other info exposure through stack trace errors --- .../app/rest/security/DSpace401AuthenticationEntryPoint.java | 3 +-- .../java/org/purl/sword/server/ServiceDocumentServlet.java | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java index 68aea6a526..b70931336e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java @@ -35,7 +35,6 @@ public class DSpace401AuthenticationEntryPoint implements AuthenticationEntryPoi response.setHeader("WWW-Authenticate", restAuthenticationService.getWwwAuthenticateHeaderValue(request, response)); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, - authException.getMessage()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication is required"); } } diff --git a/dspace-sword/src/main/java/org/purl/sword/server/ServiceDocumentServlet.java b/dspace-sword/src/main/java/org/purl/sword/server/ServiceDocumentServlet.java index 28c309b2ca..494cbc9db4 100644 --- a/dspace-sword/src/main/java/org/purl/sword/server/ServiceDocumentServlet.java +++ b/dspace-sword/src/main/java/org/purl/sword/server/ServiceDocumentServlet.java @@ -164,7 +164,8 @@ public class ServiceDocumentServlet extends HttpServlet { } catch (SWORDException se) { log.error("Internal error", se); // Throw a HTTP 500 - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, se.getMessage()); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Internal error (check logs for more information)"); } } From 5c54554cd9fe816d8ce49fb31ff2df19c302f7fb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jul 2020 16:33:07 -0500 Subject: [PATCH 241/465] Fix improper AuthorizeException & more info exposure errors --- .../StatelessAuthenticationFilter.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java index 4ab9fb5371..573fd90a52 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java @@ -17,7 +17,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; @@ -77,20 +76,17 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { HttpServletResponse res, FilterChain chain) throws IOException, ServletException { - Authentication authentication = null; + Authentication authentication; try { authentication = getAuthentication(req, res); - } catch (AuthorizeException e) { - res.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); - log.error(e.getMessage(), e); - return; } catch (IllegalArgumentException | SQLException e) { - res.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); - log.error(e.getMessage(), e); + res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Authentication request is invalid or incorrect"); + log.error("Authentication request is invalid or incorrect (status:{})", + HttpServletResponse.SC_BAD_REQUEST, e); return; } catch (AccessDeniedException e) { - res.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); - log.error(e.getMessage(), e); + res.sendError(HttpServletResponse.SC_FORBIDDEN, "Access is denied"); + log.error("Access is denied (status:{})", HttpServletResponse.SC_FORBIDDEN, e); return; } if (authentication != null) { @@ -115,7 +111,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { * @throws IOException If something goes wrong */ private Authentication getAuthentication(HttpServletRequest request, HttpServletResponse res) - throws AuthorizeException, SQLException { + throws AccessDeniedException, SQLException { if (restAuthenticationService.hasAuthenticationData(request)) { // parse the token. @@ -146,7 +142,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { } } else { if (request.getHeader(ON_BEHALF_OF_REQUEST_PARAM) != null) { - throw new AuthorizeException("Only admins are allowed to use the login as feature"); + throw new AccessDeniedException("Only admins are allowed to use the login as feature"); } } From 9ae124bef0dce09fc4c7f1930c21d0a4eee8b167 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jul 2020 17:09:41 -0500 Subject: [PATCH 242/465] Revert changes to StatelessAuthenticationFilter. It is supposed to be a 401 error! --- .../rest/security/StatelessAuthenticationFilter.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java index 573fd90a52..ff845b00f2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java @@ -17,6 +17,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; @@ -79,6 +80,10 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { Authentication authentication; try { authentication = getAuthentication(req, res); + } catch (AuthorizeException e) { + // just return an error, but do not log + res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication is required"); + return; } catch (IllegalArgumentException | SQLException e) { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Authentication request is invalid or incorrect"); log.error("Authentication request is invalid or incorrect (status:{})", @@ -111,7 +116,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { * @throws IOException If something goes wrong */ private Authentication getAuthentication(HttpServletRequest request, HttpServletResponse res) - throws AccessDeniedException, SQLException { + throws AuthorizeException, SQLException { if (restAuthenticationService.hasAuthenticationData(request)) { // parse the token. @@ -130,7 +135,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { if (configurationService.getBooleanProperty("webui.user.assumelogin")) { return getOnBehalfOfAuthentication(context, onBehalfOfParameterValue, res); } else { - throw new IllegalArgumentException("The login as feature is not allowed" + + throw new IllegalArgumentException("The 'login as' feature is not allowed" + " due to the current configuration"); } } @@ -142,7 +147,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { } } else { if (request.getHeader(ON_BEHALF_OF_REQUEST_PARAM) != null) { - throw new AccessDeniedException("Only admins are allowed to use the login as feature"); + throw new AuthorizeException("Must be logged in (as an admin) to use the 'login as' feature"); } } From 667a362b25b9f852f2d0e8533ade8644a0a39f00 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 10:44:07 -0500 Subject: [PATCH 243/465] Add keyword "fixes" as a reminder. Remove JIRA --- .github/pull_request_template.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6542242af7..6799d875f4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,7 @@ ## References -_Add references/links to any related tickets or PRs. These may include:_ -* Link to [JIRA](https://jira.lyrasis.org/projects/DS/summary) ticket(s), if any -* Link to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any -* Link to [Angular issue or PR](https://github.com/DSpace/dspace-angular/issues) related to this PR, if any +_Add references/links to any related issues or PRs. These may include:_ +* Related to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any +* Fixes [GitHub issue](https://github.com/DSpace/DSpace/issues), if any ## Description Short summary of changes (1-2 sentences). From dcc651945dd9c4567a0a25028f6f36b39b2d8745 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 11:09:26 -0500 Subject: [PATCH 244/465] Throw errors when possible "zip slip" detected. --- .../app/itemimport/ItemImportServiceImpl.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index 12fcd84d04..13aa236f54 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -1519,6 +1519,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (!dir.exists() && !dir.mkdirs()) { log.error("Unable to create directory: " + dir.getAbsolutePath()); } + // Verify that the directory the entry is using is a subpath of zipDir (and not somewhere else!) + if (!dir.toPath().normalize().startsWith(zipDir)) { + throw new IOException("Bad zip entry: '" + entry.getName() + + "' in file '" + zipfile.getAbsolutePath() + "'!" + + " Cannot process this file."); + } //Entries could have too many directories, and we need to adjust the sourcedir // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|... @@ -1539,9 +1545,16 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } byte[] buffer = new byte[1024]; int len; + File outFile = new File(zipDir + entry.getName()); + // Verify that this file will be created in our zipDir (and not somewhere else!) + if (!outFile.toPath().normalize().startsWith(zipDir)) { + throw new IOException("Bad zip entry: '" + entry.getName() + + "' in file '" + zipfile.getAbsolutePath() + "'!" + + " Cannot process this file."); + } InputStream in = zf.getInputStream(entry); BufferedOutputStream out = new BufferedOutputStream( - new FileOutputStream(zipDir + entry.getName())); + new FileOutputStream(outFile)); while ((len = in.read(buffer)) >= 0) { out.write(buffer, 0, len); } From cf7daef43125e902ba6eb47506d4a8b5868b41fb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 11:12:18 -0500 Subject: [PATCH 245/465] Set invalidation cookie to "secure" just to avoid LGTM warning --- .../rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index 1b5ee6a0c5..8aff9cc884 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -154,6 +154,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication Cookie cookie = new Cookie(AUTHORIZATION_COOKIE, ""); cookie.setHttpOnly(true); cookie.setMaxAge(0); + cookie.setSecure(true); response.addCookie(cookie); } From 294bb02193b74b15f9193e6ef6af3958ca406f31 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 12:00:06 -0500 Subject: [PATCH 246/465] Fix HTTP Response splitting by validating URL --- .../dspace/rdf/negotiation/Negotiator.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java b/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java index c28b9ec1e6..d011d305b1 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java +++ b/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java @@ -15,6 +15,7 @@ import java.util.Iterator; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.validator.routines.UrlValidator; import org.apache.logging.log4j.Logger; import org.dspace.rdf.RDFUtil; import org.dspace.services.factory.DSpaceServicesFactory; @@ -197,6 +198,7 @@ public class Negotiator { if (extraPathInfo == null) { extraPathInfo = ""; } + UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); StringBuilder urlBuilder = new StringBuilder(); String lang = null; @@ -256,12 +258,15 @@ public class Negotiator { urlBuilder.append(handle).append("/").append(extraPathInfo); } String url = urlBuilder.toString(); - - log.debug("Will forward to '" + url + "'."); - response.setStatus(HttpServletResponse.SC_SEE_OTHER); - response.setHeader("Location", url); - response.flushBuffer(); - return true; + if (urlValidator.isValid(url)) { + log.debug("Will forward to '" + url + "'."); + response.setStatus(HttpServletResponse.SC_SEE_OTHER); + response.setHeader("Location", url); + response.flushBuffer(); + return true; + } else { + throw new IOException("Invalid URL '" + url + "', cannot redirect."); + } } // currently we cannot serve statistics as rdf @@ -287,10 +292,14 @@ public class Negotiator { urlBuilder.append("/handle/").append(handle); urlBuilder.append("/").append(lang); String url = urlBuilder.toString(); - log.debug("Will forward to '" + url + "'."); - response.setStatus(HttpServletResponse.SC_SEE_OTHER); - response.setHeader("Location", url); - response.flushBuffer(); - return true; + if (urlValidator.isValid(url)) { + log.debug("Will forward to '" + url + "'."); + response.setStatus(HttpServletResponse.SC_SEE_OTHER); + response.setHeader("Location", url); + response.flushBuffer(); + return true; + } else { + throw new IOException("Invalid URL '" + url + "', cannot redirect."); + } } } From b006b84039850be461611687c75e7e25267d5236 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 12:08:47 -0500 Subject: [PATCH 247/465] Use object's reported handle instead of user provided handle --- .../org/dspace/rdf/providing/LocalURIRedirectionServlet.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java b/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java index b6a6854938..7224bb9bfb 100644 --- a/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java +++ b/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java @@ -86,7 +86,8 @@ public class LocalURIRedirectionServlet extends HttpServlet { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - + // use object's reported handle for redirect (just in case user provided handle had odd characters) + handle = dso.getHandle(); // close the context and send forward. context.abort(); Negotiator.sendRedirect(response, handle, "", requestedMimeType, true); From 53df0814221a7bc36ad91299c48db65dce1621e0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 13:13:25 -0500 Subject: [PATCH 248/465] Fix possible XXE attacks by disabling DTD parsing for external service requests. --- .../java/org/dspace/app/sherpa/SHERPAResponse.java | 3 +++ .../org/dspace/content/packager/METSManifest.java | 14 +++++++++----- .../dspace/ctask/general/MetadataWebService.java | 3 +++ .../license/CCLicenseConnectorServiceImpl.java | 4 ++++ .../org/dspace/submit/lookup/ArXivService.java | 3 +++ .../org/dspace/submit/lookup/CiNiiService.java | 6 ++++++ .../org/dspace/submit/lookup/CrossRefService.java | 3 +++ .../org/dspace/submit/lookup/PubmedService.java | 9 +++++++++ 8 files changed, 40 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java index c5b8bbebf3..bd2909c0c1 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java @@ -48,6 +48,9 @@ public class SHERPAResponse { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder db = factory.newDocumentBuilder(); Document inDoc = db.parse(xmlData); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java index 53a8678df2..ed15037c11 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java @@ -272,12 +272,16 @@ public class METSManifest { // Set validation feature if (validate) { builder.setFeature("http://apache.org/xml/features/validation/schema", true); - } - // Tell the parser where local copies of schemas are, to speed up - // validation. Local XSDs are identified in the configuration file. - if (localSchemas.length() > 0) { - builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", localSchemas); + // Tell the parser where local copies of schemas are, to speed up + // validation & avoid XXE attacks from remote schemas. Local XSDs are identified in the configuration file. + if (localSchemas.length() > 0) { + builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", localSchemas); + } + } else { + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); } // Parse the METS file diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java index 2b6c52d0d6..754f3b4ab3 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java @@ -199,6 +199,9 @@ public class MetadataWebService extends AbstractCurationTask implements Namespac DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); try { + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); docBuilder = factory.newDocumentBuilder(); } catch (ParserConfigurationException pcE) { log.error("caught exception: " + pcE); diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index a237a91984..792c25d629 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -75,6 +75,10 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, .disableAutomaticRetries() .setMaxConnTotal(5) .build(); + + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); } /** diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java b/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java index 0a32871758..337fb4175a 100644 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java +++ b/dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java @@ -113,6 +113,9 @@ public class ArXivService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder db = factory.newDocumentBuilder(); Document inDoc = db.parse(response.getEntity().getContent()); diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/CiNiiService.java b/dspace-api/src/main/java/org/dspace/submit/lookup/CiNiiService.java index 23026353fd..bb59043e52 100644 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/CiNiiService.java +++ b/dspace-api/src/main/java/org/dspace/submit/lookup/CiNiiService.java @@ -102,6 +102,9 @@ public class CiNiiService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder db = factory.newDocumentBuilder(); Document inDoc = db.parse(response.getEntity().getContent()); @@ -178,6 +181,9 @@ public class CiNiiService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder db = factory.newDocumentBuilder(); Document inDoc = db.parse(response.getEntity().getContent()); diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/CrossRefService.java b/dspace-api/src/main/java/org/dspace/submit/lookup/CrossRefService.java index f73e9c0352..4b99cf1f8b 100644 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/CrossRefService.java +++ b/dspace-api/src/main/java/org/dspace/submit/lookup/CrossRefService.java @@ -99,6 +99,9 @@ public class CrossRefService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder db = factory .newDocumentBuilder(); diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java b/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java index fa30ee8ea5..a5e74322f5 100644 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java +++ b/dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java @@ -119,6 +119,9 @@ public class PubmedService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder builder; try { @@ -156,6 +159,9 @@ public class PubmedService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder builder = factory.newDocumentBuilder(); Document inDoc = builder.parse(stream); @@ -216,6 +222,9 @@ public class PubmedService { factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); + // disallow DTD parsing to ensure no XXE attacks can occur. + // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder builder = factory.newDocumentBuilder(); Document inDoc = builder From c323b989d2b3751fbb4a0b10aaac97423da47917 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 14:42:56 -0500 Subject: [PATCH 249/465] Escape any HTML in user provided param. --- .../main/java/org/dspace/app/rest/OpenSearchController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java index 42ad173f2e..62c6a9c573 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -34,6 +34,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.core.Utils; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; @@ -103,7 +104,8 @@ public class OpenSearchController { // do some sanity checking if (!openSearchService.getFormats().contains(format)) { - String err = "Format " + format + " is not supported."; + // Since we are returning error response as HTML, escape any HTML in "format" param + String err = "Format " + Utils.addEntities(format) + " is not supported."; response.setContentType("text/html"); response.setContentLength(err.length()); response.getWriter().write(err); From 79e4e3b497374e889161c15343faf461b250a454 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 15:08:05 -0500 Subject: [PATCH 250/465] Escape special chars in filename which includes user input --- dspace-sword/src/main/java/org/dspace/sword/DepositManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java b/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java index 7535302139..4491f876cc 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java @@ -245,6 +245,8 @@ public class DepositManager { String filenameBase = "sword-" + deposit.getUsername() + "-" + (new Date()).getTime(); + // No dots or slashes allowed in filename + filenameBase = filenameBase.replaceAll("\\.", "").replaceAll("/", ""). replaceAll("\\\\", ""); File packageFile = new File(path, filenameBase); File headersFile = new File(path, filenameBase + "-headers"); From 93903de1c599ffc54c5327cb1f202e44737053aa Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 15:23:43 -0500 Subject: [PATCH 251/465] Fix possible int overflow by converting to long before multiplication --- .../src/main/java/org/dspace/harvest/HarvestScheduler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestScheduler.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestScheduler.java index d668b09bc4..5d0545845c 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestScheduler.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestScheduler.java @@ -134,11 +134,13 @@ public class HarvestScheduler implements Runnable { if (maxActiveThreads == 0) { maxActiveThreads = 3; } - minHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.minHeartbeat") * 1000; + minHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.minHeartbeat"); + minHeartbeat = minHeartbeat * 1000; // multiple by 1000 to turn seconds to ms if (minHeartbeat == 0) { minHeartbeat = 30000; } - maxHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.maxHeartbeat") * 1000; + maxHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.maxHeartbeat"); + maxHeartbeat = maxHeartbeat * 1000; // multiple by 1000 to turn seconds to ms if (maxHeartbeat == 0) { maxHeartbeat = 3600000; } From 645a1800bb0f54af01269dd8456114efff142f83 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 27 Jul 2020 15:27:51 -0500 Subject: [PATCH 252/465] Fix potentially unsafe external link --- .../main/webapp/static/reports/restReport.js | 153 +++++++++--------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/dspace-rest/src/main/webapp/static/reports/restReport.js b/dspace-rest/src/main/webapp/static/reports/restReport.js index 3efc67e85e..8abe17be06 100644 --- a/dspace-rest/src/main/webapp/static/reports/restReport.js +++ b/dspace-rest/src/main/webapp/static/reports/restReport.js @@ -15,7 +15,7 @@ var Report = function() { this.ROOTPATH = "/xmlui/handle/" //this.ROOTPATH = "/jspui/handle/" //this.ROOTPATH = "/handle/" - + //Indicate if Password Authentication is supported this.makeAuthLink = function(){return false;}; @@ -27,34 +27,34 @@ var Report = function() { this.getId = function(obj) { return obj.uuid; } - + //Override this method is sortable.js has been included this.hasSorttable = function() { return false; } - + this.getDefaultParameters = function(){ return {}; } this.getCurrentParameters = function(){ return {}; } - + this.saveUrl = function() { this.myReportParameters.saveAsUrl(this.getCurrentParameters()); } - + this.getLoginPayload = function() { //Placeholder to allow a customized report to prompt for email/password //If not enabled, the authenticaton callback will be called immediately var email = $("#restemail").val(); var pass = $("#restpass").val(); if (email == "" || pass == "") { - return undefined; + return undefined; } else if (email == null || pass == null) { - return undefined; + return undefined; } else { - return {email: email, password: pass}; + return {email: email, password: pass}; } } this.getLangSuffix = function(){ @@ -82,15 +82,15 @@ var Report = function() { className: 'spinner', // The CSS class to assign to the spinner zIndex: 2e9, // The z-index (defaults to 2000000000) top: '400px', // Top position relative to parent - left: '600px' // Left position relative to parent + left: '600px' // Left position relative to parent }); - + this.displayItems = function(itemsTitle, offset, limit, total, funcdec, funcinc) { var count = $("#itemtable tr.data").length; - + var last = offset + limit; var suff = ""; - + if (total == null) { last = offset + count; suff = (count == limit) ? " of " + last + "+ " : " of " + last; @@ -102,7 +102,7 @@ var Report = function() { suff = " of " + total; } suff += " unfiltered; displaying " + count + " filtered" ; - + itemsTitle += " (" + (offset+1) + " - " + last + suff + ")"; $("#prev,#next").attr("disabled",true); $("#itemdiv h3").text(itemsTitle); @@ -110,34 +110,34 @@ var Report = function() { if (offset > 0) $("#prev").attr("disabled", false); $("#prev").off("click").on("click", funcdec); //in case of filters, always allow next - + if (total == null) { - $("#next").attr("disabled", false); + $("#next").attr("disabled", false); } else if (offset + limit < total) { - $("#next").attr("disabled", false); + $("#next").attr("disabled", false); $("#exlimit").addClass("red"); } else if (limit == total) { //total may only be accurate to one page - $("#next").attr("disabled", false); + $("#next").attr("disabled", false); $("#exlimit").addClass("red"); } $("#next").off("click").on("click", funcinc); } - + this.myReportParameters = undefined; this.myFilters = undefined; this.myMetadataFields = undefined; - + this.initMetadataFields = function() { this.myMetadataFields = new MetadataFields(self); - this.myMetadataFields.load(); + this.myMetadataFields.load(); } - + this.initBitstreamFields = function() { this.myBitstreamFields = new BitstreamFields(self); - this.myBitstreamFields.load(); + this.myBitstreamFields.load(); } - + this.baseInit = function() { this.myReportParameters = new ReportParameters( this.getDefaultParameters(), @@ -173,13 +173,13 @@ var Report = function() { }); return itemdata; } - + this.export = function(rows) { var itemdata = "data:text/csv;charset=utf-8," + this.makeCsv(rows); var encodedUri = encodeURI(itemdata); - window.open(encodedUri); + window.open(encodedUri); } - + //this is meant to be overridden for each report this.exportCol = function(colnum, col) { var data = ""; @@ -187,7 +187,7 @@ var Report = function() { data += self.exportCell(col); return data; } - + this.exportCell = function(col) { data = "\""; $(col).contents().each(function(i, node){ @@ -198,16 +198,16 @@ var Report = function() { if ($(node).is("div:not(:last-child)")) { data += "||"; } - } + } }); data += "\""; return data; } - + this.init = function() { - this.baseInit(); + this.baseInit(); } - + } var Auth = function(report) { @@ -242,17 +242,17 @@ var Auth = function(report) { self.authStat(); self.callback(); } - }); + }); } this.verifyShibLogin = function() { var self = this; $.ajax({ - url: "/rest/shibboleth-login", + url: "/rest/shibboleth-login", success: self.authStat }); } - + this.authStat = function() { var self = this; $.ajax({ @@ -264,7 +264,7 @@ var Auth = function(report) { success: function(data) { var user = ""; if (data.email != undefined) { - user = data.email; + user = data.email; } else { user = "You are not logged in. Some items may be excluded from reports."; } @@ -279,10 +279,10 @@ var Auth = function(report) { if (data.email == undefined && self.report.makeShibLink()) { self.verifyShibLogin(); } - } - }); + } + }); } - + this.logout = function() { var self = this; $.ajax({ @@ -293,7 +293,7 @@ var Auth = function(report) { complete: function(xhr, status) { self.authStat(); } - }); + }); } this.getHeaders = function() { var HEADERS = {}; @@ -314,14 +314,14 @@ var ReportParameters = function(defaultParams, prmstr) { var field = tmparr[0]; var val = decodeURIComponent(tmparr[1]); var pval = this.params[field]; - + if ($.isArray(pval)) { - pval[pval.length] = val; + pval[pval.length] = val; } else { this.params[field] = val; } } - $("#limit").val(this.params.limit); + $("#limit").val(this.params.limit); $("#offset").val(this.params.offset); this.limit = this.params.limit; this.offset = this.params.offset; @@ -350,11 +350,11 @@ var ReportParameters = function(defaultParams, prmstr) { var lim = $("#limit").val(); if ($.isNumeric(val) && $.isNumeric(lim)) { if (increment) { - $("#offset").val(this.getNextOffset()); + $("#offset").val(this.getNextOffset()); } else { - $("#offset").val(this.getPrevOffset()); + $("#offset").val(this.getPrevOffset()); } - } + } } this.saveAsUrl = function(params) { @@ -381,7 +381,7 @@ var Filters = function() { $("#filter-reload").attr("disabled", false); } ); - + $.getJSON( "/rest/filters", function(data){ @@ -444,13 +444,13 @@ var Filters = function() { list = "none"; } return list; - } + } } var MetadataFields = function(report) { this.metadataSchemas = undefined; var self = this; - + this.load = function(){ $.ajax({ url: "/rest/registries/schema", @@ -463,15 +463,15 @@ var MetadataFields = function(report) { }, complete: function(xhr, status) { } - }); + }); } - + this.initFields = function(data, report) { var params = report.myReportParameters.params; self.metadataSchemas = data; self.drawShowFields(params["show_fields[]"]); } - + this.getShowFields = function(){ var val = $("#show-fields select").val(); return val == null ? Array() : val; @@ -497,7 +497,7 @@ var MetadataFields = function(report) { }); }); } - + this.initQueries = function(){}; } @@ -508,15 +508,15 @@ var BitstreamFields = function(report) { } this.map = [ { - key: "original-file-names", - name: "Original File Names", + key: "original-file-names", + name: "Original File Names", ftest: self.isOriginal, fval: function(bit) { return bit.name; } }, { - key: "mime-type", + key: "mime-type", name: "Mime Type", ftest: self.isOriginal, fval: function(bit) { @@ -524,7 +524,7 @@ var BitstreamFields = function(report) { } }, { - key: "bitstream-format", + key: "bitstream-format", name: "Bitstream Format", ftest: self.isOriginal, fval: function(bit) { @@ -532,7 +532,7 @@ var BitstreamFields = function(report) { } }, { - key: "bitstream-description", + key: "bitstream-description", name: "Bitstream Description", ftest: self.isOriginal, fval: function(bit) { @@ -540,7 +540,7 @@ var BitstreamFields = function(report) { } }, { - key: "bitstream-size", + key: "bitstream-size", name: "Bitstream Size", ftest: self.isOriginal, fval: function(bit) { @@ -548,18 +548,18 @@ var BitstreamFields = function(report) { } }, { - key: "bitstream-checksum", + key: "bitstream-checksum", name: "MD5 Checksum", ftest: self.isOriginal, fval: function(bit) { if (bit.checkSum.checkSumAlgorithm === "MD5") { - return bit.checkSum.value; + return bit.checkSum.value; } return ""; } }, ]; - + this.load = function(){ self.initFields(report); } @@ -568,7 +568,7 @@ var BitstreamFields = function(report) { var params = report.myReportParameters.params; self.drawShowFieldsBits(params["show_fields_bits[]"]); }; - + this.hasBitstreamFields = function() { return self.getShowFieldsBits() != null; } @@ -576,20 +576,20 @@ var BitstreamFields = function(report) { var val = $("#show-fields-bits select").val(); return val == null ? Array() : val; } - + this.drawShowFieldsBits = function(pfieldsBits) { var sel = $("