From 92c38de99e0d8dfd0e863812eb307cadd351c1e3 Mon Sep 17 00:00:00 2001 From: Mirko Scherf Date: Thu, 27 Jul 2023 17:14:23 +0200 Subject: [PATCH 01/58] fix: add default HandleIdentifierProvider for disabled versioning Setting versioning.enabled = false in versioning.cfg is not enough to disable versioning. It is also required to replace the bean class VersionedHandleIdentifierProvider with a HandleIdentifierProvider in identifier-service.xml. I've added one that is commented out as by default versioning is enabled. --- dspace/config/spring/api/identifier-service.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index 0c58cc1de9..79e19e879e 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + + + + - - - - + + @@ -68,21 +65,9 @@ + + - - - - - - - - - - - - - - - + From 56e2f10202f376f1afb817f06327ed052b00ea0b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 24 Oct 2023 19:59:51 +0200 Subject: [PATCH 06/58] [CST-12108] minor refactoring --- .../org/dspace/correctiontype/CorrectionType.java | 4 ++++ .../correctiontype/ReinstateCorrectionType.java | 12 ++++++++++++ .../correctiontype/WithdrawnCorrectionType.java | 11 +++++++++++ 3 files changed, 27 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java index 1501f637de..098e9ce936 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java @@ -25,6 +25,10 @@ public interface CorrectionType { public String getTopic(); + public String getCreationForm(); + + public String getDiscoveryConfiguration(); + public boolean isRequiredRelatedItem(); public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException; diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index fbbcb36b45..ac7b88d3fd 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -7,6 +7,8 @@ */ package org.dspace.correctiontype; +import static org.apache.commons.lang.StringUtils.EMPTY; + import java.sql.SQLException; import java.util.Date; @@ -100,4 +102,14 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public void afterPropertiesSet() throws Exception {} + @Override + public String getCreationForm() { + return EMPTY; + } + + @Override + public String getDiscoveryConfiguration() { + return EMPTY; + } + } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 78a460b212..4f148ef799 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -7,6 +7,7 @@ */ package org.dspace.correctiontype; +import static org.apache.commons.lang.StringUtils.EMPTY; import static org.dspace.core.Constants.READ; import java.sql.SQLException; @@ -100,4 +101,14 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public void afterPropertiesSet() throws Exception {} + @Override + public String getCreationForm() { + return EMPTY; + } + + @Override + public String getDiscoveryConfiguration() { + return EMPTY; + } + } From 2feff4f522c72023ab2ed054086654138b4cd516 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 25 Oct 2023 21:43:11 +0200 Subject: [PATCH 07/58] [CST-12108] improve code to use withdrawn and reinstate functionalities --- .../src/main/java/org/dspace/content/QAEvent.java | 4 ++++ .../correctiontype/ReinstateCorrectionType.java | 5 +++-- .../correctiontype/WithdrawnCorrectionType.java | 5 +++-- .../dspace/qaevent/service/dto/EmptyMessageDTO.java | 10 ++++++++++ .../dspace/app/rest/converter/QAEventConverter.java | 6 ++++++ .../app/rest/model/EmptyQAEventMessageRest.java | 12 ++++++++++++ .../app/rest/repository/QAEventRestRepository.java | 6 +++--- dspace/config/dspace.cfg | 2 ++ dspace/config/spring/api/correction-types.xml | 4 ++-- dspace/config/spring/api/qaevents.xml | 4 ++-- 10 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java index 0e837a3b95..78eb00c1fa 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -13,6 +13,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Date; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.dspace.qaevent.service.dto.EmptyMessageDTO; import org.dspace.qaevent.service.dto.OpenaireMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.util.RawJsonDeserializer; @@ -30,6 +31,7 @@ public class QAEvent { public static final String DISCARDED = "discarded"; public static final String OPENAIRE_SOURCE = "openaire"; + public static final String INTERNAL_ITEM_SOURCE = "internal-item"; private String source; @@ -195,6 +197,8 @@ public class QAEvent { switch (getSource()) { case OPENAIRE_SOURCE: return OpenaireMessageDTO.class; + case INTERNAL_ITEM_SOURCE: + return EmptyMessageDTO.class; default: throw new IllegalArgumentException("Unknown event's source: " + getSource()); } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index ac7b88d3fd..3102887e49 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -8,6 +8,7 @@ package org.dspace.correctiontype; import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.dspace.content.QAEvent.INTERNAL_ITEM_SOURCE; import java.sql.SQLException; import java.util.Date; @@ -56,9 +57,9 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public QAEvent createCorrection(Context context, Item targetItem) { - QAEvent qaEvent = new QAEvent("handle:" + targetItem.getHandle(), + QAEvent qaEvent = new QAEvent(INTERNAL_ITEM_SOURCE, + "handle:" + targetItem.getHandle(), targetItem.getID().toString(), - null, targetItem.getName(), this.getTopic(), 1.0, diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 4f148ef799..9ccd316cd5 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -8,6 +8,7 @@ package org.dspace.correctiontype; import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.dspace.content.QAEvent.INTERNAL_ITEM_SOURCE; import static org.dspace.core.Constants.READ; import java.sql.SQLException; @@ -50,9 +51,9 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public QAEvent createCorrection(Context context, Item targetItem) { - QAEvent qaEvent = new QAEvent("handle:" + targetItem.getHandle(), + QAEvent qaEvent = new QAEvent(INTERNAL_ITEM_SOURCE, + "handle:" + targetItem.getHandle(), targetItem.getID().toString(), - null, targetItem.getName(), this.getTopic(), 1.0, new Gson().toJson(new Object()), diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java new file mode 100644 index 0000000000..1283e70541 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java @@ -0,0 +1,10 @@ +package org.dspace.qaevent.service.dto; + +/** + * Implementation of {@link QAMessageDTO} that model empty message. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class EmptyMessageDTO implements QAMessageDTO { + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java index a32c0ddc99..3c2751d8a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java @@ -14,11 +14,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import org.dspace.app.rest.model.EmptyQAEventMessageRest; import org.dspace.app.rest.model.OpenaireQAEventMessageRest; import org.dspace.app.rest.model.QAEventMessageRest; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.QAEvent; +import org.dspace.qaevent.service.dto.EmptyMessageDTO; import org.dspace.qaevent.service.dto.OpenaireMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.services.ConfigurationService; @@ -58,6 +60,7 @@ public class QAEventConverter implements DSpaceConverter { } catch (JsonProcessingException e) { throw new RuntimeException(e); } + rest.setSource(modelObject.getSource()); rest.setOriginalId(modelObject.getOriginalId()); rest.setProjection(projection); rest.setTitle(modelObject.getTitle()); @@ -73,6 +76,9 @@ public class QAEventConverter implements DSpaceConverter { if (dto instanceof OpenaireMessageDTO) { return convertOpenaireMessage(dto); } + if (dto instanceof EmptyMessageDTO) { + return new EmptyQAEventMessageRest(); + } throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java new file mode 100644 index 0000000000..3562042928 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java @@ -0,0 +1,12 @@ +/** + * 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; + +public class EmptyQAEventMessageRest implements QAEventMessageRest { + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index b83bf2da4a..121ae67ede 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -140,9 +140,9 @@ public class QAEventRestRepository extends DSpaceRestRepository stringList) throws SQLException, AuthorizeException { - if (stringList.size() < 3) { - throw new IllegalArgumentException("the request must include three uris for target item, " + - "related item and correction type"); + if (stringList.size() < 2) { + throw new IllegalArgumentException("the request must include at least uris for target item, " + + "and correction type"); } HttpServletRequest request = getRequestService().getCurrentRequest().getHttpServletRequest(); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4dacf0bdb5..f75a9a6bd1 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1085,6 +1085,8 @@ webui.preview.brand.fontpoint = 12 # Solr: # ItemCountDAO.class = org.dspace.browse.ItemCountDAOSolr +###### QAEvent source Configuration ###### +qaevent.sources = openaire, internal-item ###### Browse Configuration ###### # diff --git a/dspace/config/spring/api/correction-types.xml b/dspace/config/spring/api/correction-types.xml index 8a5e4af94b..3e3127e0c7 100644 --- a/dspace/config/spring/api/correction-types.xml +++ b/dspace/config/spring/api/correction-types.xml @@ -6,12 +6,12 @@ - + - + diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 8c064d250f..328a43a1f8 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -24,8 +24,8 @@ - - + + From fb3b34e33dff1b29bc26decaf3ab71e72f799fbc Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 25 Oct 2023 21:44:40 +0200 Subject: [PATCH 08/58] [CST-12108] added few tests --- .../app/rest/QAEventRestRepositoryIT.java | 373 +++--------------- 1 file changed, 47 insertions(+), 326 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 4794cbd459..9427e98200 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -7,14 +7,15 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; import static org.dspace.app.rest.matcher.QAEventMatcher.matchQAEventEntry; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; @@ -29,6 +30,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; import org.dspace.app.rest.matcher.ItemMatcher; @@ -52,6 +54,7 @@ import org.dspace.qaevent.dao.QAEventsDao; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.RestMediaTypes; /** * Integration tests for {@link QAEventRestRepository}. @@ -63,7 +66,6 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private QAEventsDao qaEventsDao; - @Autowired private AuthorizeService authorizeService; @@ -171,7 +173,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") @@ -334,16 +336,16 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); @@ -358,16 +360,16 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); @@ -383,16 +385,16 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); @@ -796,334 +798,53 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { } @Test - public void createNBEventByCorrectionTypeUnAuthorizedTest() throws Exception { - getClient().perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + UUID.randomUUID() + "\n" + - "https://localhost:8080/server/api/core/items/" + UUID.randomUUID() - )) + public void createQAEventByCorrectionTypeUnAuthorizedTest() throws Exception { + getClient().perform(post("/api/integration/qualityassuranceevents") + .contentType(RestMediaTypes.TEXT_URI_LIST) + .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest\n" + + "https://localhost:8080/server/api/core/items/" + UUID.randomUUID())) .andExpect(status().isUnauthorized()); } @Test - public void createNBEventByCorrectionTypeWithMissingUriTest() throws Exception { + public void createQAEventByCorrectionTypeWithMissingUriTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - - getClient(adminToken).perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + UUID.randomUUID() - )) + getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") + .contentType(RestMediaTypes.TEXT_URI_LIST) + .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest")) .andExpect(status().isBadRequest()); } @Test - public void createNBEventByCorrectionTypeWithPersonalArchiveAsTargetItemTest() throws Exception { + public void createQAEventByCorrectionTypeWithdrawnRequestTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); + .withName("Parent Community") + .build(); - Item personalArchive = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalArchive") - .withTitle("personal archive item").build(); + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection for Publications") + .withEntityType("Publication") + .build(); + Item publication = ItemBuilder.createItem(context, col) + .withTitle("Publication archived item") + .build(); context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") + .contentType(RestMediaTypes.TEXT_URI_LIST) + .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest\n" + + "https://localhost:8080/server/api/core/items/" + publication.getID() + )) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + personalArchive.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + personalArchive.getID() - )) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void createNBEventByCorrectionTypeWithPersonalPathAsTargetItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - - Item personalPath = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalPath") - .withTitle("personal path item").build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + personalPath.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + personalPath.getID() - )) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void createNBEventByNotFoundCorrectionTypeTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - - Item targetItem = ItemBuilder.createItem(context, col1) - .withEntityType("Publication") - .withTitle("publication item").build(); - - Item relatedItem = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalArchive") - .withTitle("personal archive item").build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/test\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void createNBEventByCorrectionTypeButNotFoundTargetItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - - Item personalArchive = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalArchive") - .withTitle("personal archive item").build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + UUID.randomUUID() + "\n" + - "https://localhost:8080/server/api/core/items/" + personalArchive.getID() - )) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void createNBEventByCorrectionTypeButNotFoundRelatedItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - - Item item = ItemBuilder.createItem(context, col1) - .withEntityType("Publication") - .withTitle("publication item").build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + item.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + UUID.randomUUID() - )) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void createNBEventByCorrectionIsForbiddenTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - Item targetItem = ItemBuilder.createItem(context, col1) - .withEntityType("Publication") - .withTitle("publication item").build(); - - Item relatedItem = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalArchive") - .withTitle("personal archive item").build(); - - authorizeService.removeAllPolicies(context, targetItem); - - context.restoreAuthSystemState(); - - String authToken = getAuthToken(eperson.getEmail(), password); - - //WHEN: create nbEvent and accept it - getClient(authToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isForbidden()); - - } - - @Test - public void createNBEventByCorrectionTypeTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - Item targetItem = ItemBuilder.createItem(context, col1) - .withEntityType("Publication") - .withTitle("publication item").build(); - - Item relatedItem = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalArchive") - .withTitle("personal archive item").build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - //WHEN: create nbEvent and accept it - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", allOf( - hasJsonPath("$.originalId", is("handle:" + targetItem.getHandle())), - hasJsonPath("$.topic", - equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalarchive")), - hasJsonPath("$.title", is(targetItem.getName())), - hasJsonPath("$.trust", is("1.000")), - hasJsonPath("$.status", is("PENDING")), - hasJsonPath("$.type", is("nbevent"))))); - - //THEN: nbEvent has been accepted and metadata has been added - getClient(adminToken).perform(get("/api/core/items/" + targetItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.metadata['dc.relation.personalarchive'].[0].value", - equalTo(relatedItem.getID().toString()))) - .andExpect(jsonPath("$.metadata['dc.relation.personalarchive'].[0].authority", - equalTo(relatedItem.getID().toString()))); - - //THEN: no nbEvents found for the topic - getClient(adminToken).perform(get("/api/integration/nbevents/search/findByTopic") - .param("topic", "/DSPACEUSERS/RELATIONADD/dc.relation.personalarchive")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); - } - - @Test - public void createNBEventByCorrectionTypeAlreadyLinkedTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1").build(); - Item targetItem = ItemBuilder.createItem(context, col1) - .withEntityType("Publication") - .withTitle("publication item").build(); - - Item relatedItem = ItemBuilder.createItem(context, col1) - .withEntityType("PersonalArchive") - .withTitle("personal archive item").build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - //WHEN: create nbEvent and accept it - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isCreated()); - - //create nbEvent Item already linked before - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/addpersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isUnprocessableEntity()); - - //create nbEvent to remove relation metadata - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/removepersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", allOf( - hasJsonPath("$.originalId", is("handle:" + targetItem.getHandle())), - hasJsonPath("$.topic", - equalTo("/DSPACEUSERS/RELATIONREMOVE/dc.relation.personalarchive")), - hasJsonPath("$.title", is(targetItem.getName())), - hasJsonPath("$.trust", is("1.000")), - hasJsonPath("$.status", is("PENDING")), - hasJsonPath("$.type", is("nbevent"))))); - - //THEN: nbEvent has been accepted and metadata has been removed - getClient(adminToken).perform(get("/api/core/items/" + targetItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.metadata['dc.relation.personalarchive']").doesNotExist()) - .andExpect(jsonPath("$.metadata['dc.relation.personalarchive']").doesNotExist()); - - //create nbEvent to remove relation metadata not exist - getClient(adminToken) - .perform(post("/api/integration/nbevents/") - .contentType(org.springframework.http.MediaType.parseMediaType("text/uri-list")) - .content( - "https://localhost:8080/server/api/config/correctiontypes/removepersonalarchive\n" + - "https://localhost:8080/server/api/core/items/" + targetItem.getID() + "\n" + - "https://localhost:8080/server/api/core/items/" + relatedItem.getID() - )) - .andExpect(status().isUnprocessableEntity()); - - + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()); } } From 442286ac2c3e14517e3e4dffe0abda00f121b53d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 25 Oct 2023 22:20:41 +0200 Subject: [PATCH 09/58] [CST-12108] remove unused imports --- .../java/org/dspace/app/rest/QAEventRestRepositoryIT.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 9427e98200..e7354a0c24 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -12,9 +12,7 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; import static org.dspace.app.rest.matcher.QAEventMatcher.matchQAEventEntry; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -38,7 +36,6 @@ import org.dspace.app.rest.matcher.QAEventMatcher; 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.AuthorizeService; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EntityTypeBuilder; @@ -66,8 +63,6 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private QAEventsDao qaEventsDao; - @Autowired - private AuthorizeService authorizeService; @Test public void findAllNotImplementedTest() throws Exception { From 6fadc40651c71959aa821d93675cd7b3b0cd004a Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Thu, 26 Oct 2023 17:34:22 +0200 Subject: [PATCH 10/58] [CST-12108] fix withdrawn and reinstate flow --- .../org/dspace/correctiontype/ReinstateCorrectionType.java | 4 ---- .../org/dspace/correctiontype/WithdrawnCorrectionType.java | 4 ---- 2 files changed, 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 3102887e49..d08dee6d38 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -20,7 +20,6 @@ import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.qaevent.service.QAEventActionService; import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -40,8 +39,6 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean private QAEventService qaEventService; @Autowired private AuthorizeService authorizeService; - @Autowired - private QAEventActionService qaEventActionService; @Override public boolean isAllowed(Context context, Item targetItem) throws SQLException, AuthorizeException { @@ -68,7 +65,6 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean ); qaEventService.store(context, qaEvent); - qaEventActionService.accept(context, qaEvent); return qaEvent; } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 9ccd316cd5..056e73bc28 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -20,7 +20,6 @@ import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; -import org.dspace.qaevent.service.QAEventActionService; import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -40,8 +39,6 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean private QAEventService qaEventService; @Autowired private AuthorizeService authorizeService; - @Autowired - private QAEventActionService qaEventActionService; @Override public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException { @@ -61,7 +58,6 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean ); qaEventService.store(context, qaEvent); - qaEventActionService.accept(context, qaEvent); return qaEvent; } From a2c2349a01c30a971da97361ca4a61ed1981532f Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Thu, 26 Oct 2023 17:35:32 +0200 Subject: [PATCH 11/58] [CST-12108] added tests for withdrown and reinstate requests --- .../app/rest/QAEventRestRepositoryIT.java | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index e7354a0c24..f6438ae4e6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -827,9 +827,14 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + AtomicReference idRef = new AtomicReference(); - String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") .contentType(RestMediaTypes.TEXT_URI_LIST) .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest\n" + @@ -839,7 +844,93 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.source", is("internal-item"))) + .andExpect(jsonPath("$.title", is(publication.getName()))) + .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.trust", is("1,000"))) + .andExpect(jsonPath("$.status", is("PENDING"))); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + + getClient(adminToken).perform(patch("/api/integration/qualityassuranceevents/" + idRef.get()) + .content(getPatchContent(acceptOp)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + } + + @Test + public void createQAEventByCorrectionTypeReinstateRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection for Publications") + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, col) + .withTitle("Publication archived item") + .withdrawn() + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + + AtomicReference idRef = new AtomicReference(); + + getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") + .contentType(RestMediaTypes.TEXT_URI_LIST) + .content("https://localhost:8080/server/api/config/correctiontypes/reinstateRequest\n" + + "https://localhost:8080/server/api/core/items/" + publication.getID() + )) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.source", is("internal-item"))) + .andExpect(jsonPath("$.title", is(publication.getName()))) + .andExpect(jsonPath("$.topic", is("REQUEST/REINSTATE"))) + .andExpect(jsonPath("$.trust", is("1,000"))) + .andExpect(jsonPath("$.status", is("PENDING"))); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + + getClient(adminToken).perform(patch("/api/integration/qualityassuranceevents/" + idRef.get()) + .content(getPatchContent(acceptOp)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); } } From f72ef270fe7cc5eb75676aa0fb9086350f24f2b9 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Thu, 26 Oct 2023 18:41:44 +0200 Subject: [PATCH 12/58] [CST-12108] added CorrectionType ITs --- .../rest/CorrectionTypeRestRepositoryIT.java | 215 ++++++++---------- 1 file changed, 101 insertions(+), 114 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java index 950594281a..d184cb888d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java @@ -33,77 +33,62 @@ import org.springframework.beans.factory.annotation.Autowired; /** * Test suite for {@link CorrectionTypeRestRepository} * - * @author Mohamed Eskander (mohamed.eskander at 4science.com) - * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private ItemService itemService; - @Autowired private AuthorizeService authorizeService; @Test public void findAllTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(4))) - .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( - allOf( - hasJsonPath("$.id", equalTo("addpersonalpath")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalpath")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonPath.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ), - allOf( - hasJsonPath("$.id", equalTo("removepersonalpath")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONREMOVE/dc.relation.personalpath")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonPath.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ), - allOf(hasJsonPath( - "$.id", equalTo("addpersonalarchive")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalarchive")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonArchive.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ), - allOf( - hasJsonPath("$.id", equalTo("removepersonalarchive")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONREMOVE/dc.relation.personalarchive")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonArchive.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ) - ))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("withdrawnRequest")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + ), + allOf( + hasJsonPath("$.id", equalTo("reinstateRequest")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")) + ) + ))); } @Test public void findOneTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/addpersonalpath")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", equalTo("addpersonalpath"))) - .andExpect(jsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalpath"))) - .andExpect(jsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonPath.Items"))) - .andExpect(jsonPath("$.creationForm", equalTo("manageRelation"))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/withdrawnRequest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("withdrawnRequest"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))); } @Test public void findOneNotFoundTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/test")) - .andExpect(status().isNotFound()); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/test")) + .andExpect(status().isNotFound()); } @Test public void findByItemWithoutUUIDParameterTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/search/findByItem/")) - .andExpect(status().isBadRequest()); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/")) + .andExpect(status().isBadRequest()); } @Test public void findByItemNotFoundTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", UUID.randomUUID().toString())) - .andExpect(status().isUnprocessableEntity()); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + .param("uuid", UUID.randomUUID().toString())) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -116,7 +101,7 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", privateItem.getID().toString())) + .param("uuid", privateItem.getID().toString())) .andExpect(status().isUnauthorized()); } @@ -130,25 +115,34 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio itemService.update(context, item); context.restoreAuthSystemState(); - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", item.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); } @Test public void findByWithdrawnItemTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).build(); - Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); - Item item = ItemBuilder.createItem(context, collection).withdrawn().build(); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + Item item = ItemBuilder.createItem(context, collection) + .withdrawn() + .build(); context.restoreAuthSystemState(); - getClient(getAuthToken(admin.getEmail(), password)) - .perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", item.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/config/correctiontypes/search/findByItem/") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder(allOf( + hasJsonPath("$.id", equalTo("reinstateRequest")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")) + )))); } @Test @@ -161,10 +155,17 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio itemService.update(context, item); context.restoreAuthSystemState(); - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", item.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("withdrawnRequest")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + ) + ))); } @Test @@ -172,19 +173,23 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); - Item itemOne = ItemBuilder.createItem(context, collection).withEntityType("PersonalArchive").build(); - Item itemTwo = ItemBuilder.createItem(context, collection).withEntityType("PersonalPath").build(); + Item itemOne = ItemBuilder.createItem(context, collection) + .build(); + context.restoreAuthSystemState(); - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", itemOne.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + .param("uuid", itemOne.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("withdrawnRequest")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + ) + ))); - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", itemTwo.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); } @Test @@ -195,60 +200,42 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio Item item = ItemBuilder.createItem(context, collection).build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") - .param("uuid", item.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(4))) - .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( - allOf( - hasJsonPath("$.id", equalTo("addpersonalpath")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalpath")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonPath.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ), - allOf( - hasJsonPath("$.id", equalTo("removepersonalpath")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONREMOVE/dc.relation.personalpath")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonPath.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ), - allOf(hasJsonPath( - "$.id", equalTo("addpersonalarchive")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalarchive")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonArchive.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ), - allOf( - hasJsonPath("$.id", equalTo("removepersonalarchive")), - hasJsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONREMOVE/dc.relation.personalarchive")), - hasJsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonArchive.Items")), - hasJsonPath("$.creationForm", equalTo("manageRelation")) - ) - ))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("withdrawnRequest")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + ) + ))); } @Test public void findByTopicWithoutTopicParameterTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/search/findByTopic/")) - .andExpect(status().isBadRequest()); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic/")) + .andExpect(status().isBadRequest()); } @Test public void findByWrongTopicTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/search/findByTopic/") - .param("topic", "wrongValue")) - .andExpect(status().isNoContent()); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic/") + .param("topic", "wrongValue")) + .andExpect(status().isNoContent()); } @Test public void findByTopicTest() throws Exception { - getClient().perform(get("/api/config/correctiontypes/search/findByTopic/") - .param("topic", "/DSPACEUSERS/RELATIONADD/dc.relation.personalpath")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", equalTo("addpersonalpath"))) - .andExpect(jsonPath("$.topic", equalTo("/DSPACEUSERS/RELATIONADD/dc.relation.personalpath"))) - .andExpect(jsonPath("$.discoveryConfiguration", equalTo("RELATION.PersonPath.Items"))) - .andExpect(jsonPath("$.creationForm", equalTo("manageRelation"))); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic/") + .param("topic", "REQUEST/WITHDRAWN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("withdrawnRequest"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))); } } From 2261d0e6a7eb408cc422049fb15f9f2fe210e277 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 3 Nov 2023 00:38:09 +0100 Subject: [PATCH 13/58] [CST-12108] refactoring --- .../main/java/org/dspace/content/QAEvent.java | 4 +- .../dspace/correctiontype/CorrectionType.java | 7 +- .../ReinstateCorrectionType.java | 20 ++--- .../WithdrawnCorrectionType.java | 20 ++--- .../service/dto/CorrectionTypeMessageDTO.java | 37 +++++++++ .../qaevent/service/dto/EmptyMessageDTO.java | 10 --- .../qaevent/service/dto/QAMessageDTO.java | 1 - .../service/impl/QAEventServiceImpl.java | 2 +- .../converter/CorrectionTypeConverter.java | 1 - .../app/rest/converter/QAEventConverter.java | 19 +++-- .../CorrectionTypeQAEventMessageRest.java | 25 ++++++ .../app/rest/model/CorrectionTypeRest.java | 11 +-- .../rest/model/EmptyQAEventMessageRest.java | 12 --- .../repository/QAEventRestRepository.java | 76 +++++++++++-------- .../rest/CorrectionTypeRestRepositoryIT.java | 67 +++++++++------- .../app/rest/QAEventRestRepositoryIT.java | 20 +++-- dspace/config/spring/api/correction-types.xml | 5 +- 17 files changed, 206 insertions(+), 131 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java delete mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java index 78eb00c1fa..da554f6672 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -13,7 +13,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Date; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.dspace.qaevent.service.dto.EmptyMessageDTO; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.dspace.qaevent.service.dto.OpenaireMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.util.RawJsonDeserializer; @@ -198,7 +198,7 @@ public class QAEvent { case OPENAIRE_SOURCE: return OpenaireMessageDTO.class; case INTERNAL_ITEM_SOURCE: - return EmptyMessageDTO.class; + return CorrectionTypeMessageDTO.class; default: throw new IllegalArgumentException("Unknown event's source: " + getSource()); } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java index 098e9ce936..5784042412 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java @@ -13,6 +13,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.QAMessageDTO; /** * Interface class that model the CorrectionType. @@ -27,16 +28,14 @@ public interface CorrectionType { public String getCreationForm(); - public String getDiscoveryConfiguration(); - public boolean isRequiredRelatedItem(); public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException; public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException,SQLException; - public QAEvent createCorrection(Context context, Item targetItem); + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason); - public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem); + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason); } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index d08dee6d38..4af0084220 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -7,7 +7,6 @@ */ package org.dspace.correctiontype; -import static org.apache.commons.lang.StringUtils.EMPTY; import static org.dspace.content.QAEvent.INTERNAL_ITEM_SOURCE; import java.sql.SQLException; @@ -21,6 +20,8 @@ import org.dspace.content.QAEvent; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +35,7 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean private String id; private String topic; + private String creationForm; @Autowired private QAEventService qaEventService; @@ -53,14 +55,15 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean } @Override - public QAEvent createCorrection(Context context, Item targetItem) { + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; QAEvent qaEvent = new QAEvent(INTERNAL_ITEM_SOURCE, "handle:" + targetItem.getHandle(), targetItem.getID().toString(), targetItem.getName(), this.getTopic(), 1.0, - new Gson().toJson(new Object()), + new Gson().toJson(mesasge), new Date() ); @@ -69,8 +72,8 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean } @Override - public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem) { - return this.createCorrection(context, targetItem); + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { + return this.createCorrection(context, targetItem, reason); } @Override @@ -101,12 +104,11 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public String getCreationForm() { - return EMPTY; + return this.creationForm; } - @Override - public String getDiscoveryConfiguration() { - return EMPTY; + public void setCreationForm(String creationForm) { + this.creationForm = creationForm; } } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 056e73bc28..3db5eea563 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -7,7 +7,6 @@ */ package org.dspace.correctiontype; -import static org.apache.commons.lang.StringUtils.EMPTY; import static org.dspace.content.QAEvent.INTERNAL_ITEM_SOURCE; import static org.dspace.core.Constants.READ; @@ -21,6 +20,8 @@ import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +35,7 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean private String id; private String topic; + private String creationForm; @Autowired private QAEventService qaEventService; @@ -47,13 +49,14 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean } @Override - public QAEvent createCorrection(Context context, Item targetItem) { + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; QAEvent qaEvent = new QAEvent(INTERNAL_ITEM_SOURCE, "handle:" + targetItem.getHandle(), targetItem.getID().toString(), targetItem.getName(), this.getTopic(), 1.0, - new Gson().toJson(new Object()), + new Gson().toJson(mesasge), new Date() ); @@ -68,8 +71,8 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean } @Override - public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem) { - return createCorrection(context, targetItem); + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { + return createCorrection(context, targetItem, reason); } @Override @@ -100,12 +103,11 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public String getCreationForm() { - return EMPTY; + return this.creationForm; } - @Override - public String getDiscoveryConfiguration() { - return EMPTY; + public void setCreationForm(String creationForm) { + this.creationForm = creationForm; } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java new file mode 100644 index 0000000000..7a39e6e262 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.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.qaevent.service.dto; + +import java.io.Serializable; + +/** + * Implementation of {@link QAMessageDTO} that model empty message. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class CorrectionTypeMessageDTO implements QAMessageDTO, Serializable { + + private static final long serialVersionUID = 2718151302291303796L; + + private String reason; + + public CorrectionTypeMessageDTO() {} + + public CorrectionTypeMessageDTO(String reason) { + this.reason = reason; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java deleted file mode 100644 index 1283e70541..0000000000 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/EmptyMessageDTO.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.dspace.qaevent.service.dto; - -/** - * Implementation of {@link QAMessageDTO} that model empty message. - * - * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) - */ -public class EmptyMessageDTO implements QAMessageDTO { - -} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java index 2a63f42e61..ede32ef497 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java @@ -17,5 +17,4 @@ import org.dspace.content.QAEvent; */ public interface QAMessageDTO { - } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index bbb6990bb6..132c072ebc 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -292,7 +292,7 @@ public class QAEventServiceImpl implements QAEventService { solrQuery.setRows(pageSize); } solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); - solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "\\\\/")); QueryResponse response; try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java index 679e90ceab..aa08d17921 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java @@ -27,7 +27,6 @@ public class CorrectionTypeConverter implements DSpaceConverter { rest.setId(modelObject.getEventId()); try { rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), - modelObject.getMessageDtoClass()))); + modelObject.getMessageDtoClass()))); } catch (JsonProcessingException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } rest.setSource(modelObject.getSource()); rest.setOriginalId(modelObject.getOriginalId()); @@ -76,12 +76,19 @@ public class QAEventConverter implements DSpaceConverter { if (dto instanceof OpenaireMessageDTO) { return convertOpenaireMessage(dto); } - if (dto instanceof EmptyMessageDTO) { - return new EmptyQAEventMessageRest(); + if (dto instanceof CorrectionTypeMessageDTO) { + return convertCorrectionTypeMessage(dto); } throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); } + private QAEventMessageRest convertCorrectionTypeMessage(QAMessageDTO dto) { + CorrectionTypeMessageDTO correctionTypeDto = (CorrectionTypeMessageDTO) dto; + CorrectionTypeQAEventMessageRest message = new CorrectionTypeQAEventMessageRest(); + message.setReason(correctionTypeDto.getReason()); + return message; + } + private QAEventMessageRest convertOpenaireMessage(QAMessageDTO dto) { OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto; OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java new file mode 100644 index 0000000000..a222eae261 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class CorrectionTypeQAEventMessageRest implements QAEventMessageRest { + + private String reason; + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java index dcc004033c..4f284bce7e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.model; +import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RestResourceController; /** @@ -23,7 +24,6 @@ public class CorrectionTypeRest extends BaseObjectRest { private String topic; private String creationForm; - private String discoveryConfiguration; public String getTopic() { return topic; @@ -33,14 +33,6 @@ public class CorrectionTypeRest extends BaseObjectRest { this.topic = topic; } - public String getDiscoveryConfiguration() { - return discoveryConfiguration; - } - - public void setDiscoveryConfiguration(String discoveryConfiguration) { - this.discoveryConfiguration = discoveryConfiguration; - } - public String getCreationForm() { return creationForm; } @@ -55,6 +47,7 @@ public class CorrectionTypeRest extends BaseObjectRest { } @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { return NAME; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java deleted file mode 100644 index 3562042928..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EmptyQAEventMessageRest.java +++ /dev/null @@ -1,12 +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.model; - -public class EmptyQAEventMessageRest implements QAEventMessageRest { - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index 121ae67ede..c91cf135c5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -7,31 +7,34 @@ */ package org.dspace.app.rest.repository; -import static org.dspace.core.Constants.ITEM; - +import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.Objects; +import java.util.UUID; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.repository.handler.service.UriListHandlerService; import org.dspace.app.rest.repository.patch.ResourcePatch; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; import org.dspace.eperson.EPerson; import org.dspace.qaevent.dao.QAEventsDao; import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -64,7 +67,7 @@ public class QAEventRestRepository extends DSpaceRestRepository resourcePatch; @Autowired - private UriListHandlerService uriListHandlerService; + private CorrectionTypeService correctionTypeService; @Override @PreAuthorize("hasAuthority('ADMIN')") @@ -137,47 +140,58 @@ public class QAEventRestRepository extends DSpaceRestRepository stringList) - throws SQLException, AuthorizeException { + protected QAEventRest createAndReturn(Context context) throws SQLException, AuthorizeException { + ServletRequest request = getRequestService().getCurrentRequest().getServletRequest(); - if (stringList.size() < 2) { - throw new IllegalArgumentException("the request must include at least uris for target item, " + - "and correction type"); + String itemUUID = request.getParameter("target"); + String relatedItemUUID = request.getParameter("related"); + String correctionTypeStr = request.getParameter("correctionType"); + + + if (StringUtils.isBlank(correctionTypeStr) || StringUtils.isBlank(itemUUID)) { + throw new UnprocessableEntityException("The target item and correctionType must be provided!"); } - HttpServletRequest request = getRequestService().getCurrentRequest().getHttpServletRequest(); - CorrectionType correctionType = uriListHandlerService.handle(context, request, List.of(stringList.get(0)), - CorrectionType.class); + Item targetItem = null; + Item relatedItem = null; + try { + targetItem = itemService.find(context, UUID.fromString(itemUUID)); + relatedItem = StringUtils.isNotBlank(relatedItemUUID) ? + itemService.find(context, UUID.fromString(relatedItemUUID)) : null; + } catch (Exception e) { + throw new UnprocessableEntityException(e.getMessage(), e); + } + + if (Objects.isNull(targetItem)) { + throw new UnprocessableEntityException("The target item UUID is not valid!"); + } + + CorrectionType correctionType = correctionTypeService.findOne(correctionTypeStr); if (Objects.isNull(correctionType)) { throw new UnprocessableEntityException("The given correction type in the request is not valid!"); } - List list = utils.constructDSpaceObjectList(context, stringList); + if (correctionType.isRequiredRelatedItem() && Objects.isNull(relatedItem)) { + throw new UnprocessableEntityException("The given correction type in the request is not valid!"); + } + + ObjectMapper mapper = new ObjectMapper(); + CorrectionTypeMessageDTO reason = null; + try { + reason = mapper.readValue(request.getInputStream(), CorrectionTypeMessageDTO.class); + } catch (IOException exIO) { + throw new UnprocessableEntityException("error parsing the body " + exIO.getMessage(), exIO); + } QAEvent qaEvent; - List items = getItems(list, correctionType); if (correctionType.isRequiredRelatedItem()) { - qaEvent = correctionType.createCorrection(context, items.get(0), items.get(1)); + qaEvent = correctionType.createCorrection(context, targetItem, relatedItem, reason); } else { - qaEvent = correctionType.createCorrection(context, items.get(0)); + qaEvent = correctionType.createCorrection(context, targetItem, reason); } return converter.toRest(qaEvent, utils.obtainProjection()); } - private List getItems(List list, CorrectionType correctionType) { - if (correctionType.isRequiredRelatedItem()) { - if (list.size() == 2 && list.get(0).getType() == ITEM && list.get(1).getType() == ITEM) { - return List.of((Item) list.get(0), (Item) list.get(1)); - } else { - throw new UnprocessableEntityException("The given items in the request were not valid!"); - } - } else if (list.size() != 1) { - throw new UnprocessableEntityException("The given item in the request were not valid!"); - } else { - return List.of((Item) list.get(0)); - } - } - @Override public Class getDomainClass() { return QAEventRest.class; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java index d184cb888d..a58f2c61d1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java @@ -50,12 +50,15 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( allOf( - hasJsonPath("$.id", equalTo("withdrawnRequest")), - hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.creationForm", equalTo("provideReason")), + hasJsonPath("$.type", equalTo("correctiontype")) ), allOf( - hasJsonPath("$.id", equalTo("reinstateRequest")), - hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")) + hasJsonPath("$.id", equalTo("request-reinstate")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")), + hasJsonPath("$.type", equalTo("correctiontype")) ) ))); } @@ -63,10 +66,12 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio @Test public void findOneTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/withdrawnRequest")) + getClient(adminToken).perform(get("/api/config/correctiontypes/request-withdrawn")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", equalTo("withdrawnRequest"))) - .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))); + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } @Test @@ -79,14 +84,14 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio @Test public void findByItemWithoutUUIDParameterTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/")) + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem")) .andExpect(status().isBadRequest()); } @Test public void findByItemNotFoundTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", UUID.randomUUID().toString())) .andExpect(status().isUnprocessableEntity()); } @@ -100,7 +105,7 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio authorizeService.removeAllPolicies(context, privateItem); context.restoreAuthSystemState(); - getClient().perform(get("/api/config/correctiontypes/search/findByItem/") + getClient().perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", privateItem.getID().toString())) .andExpect(status().isUnauthorized()); } @@ -116,7 +121,7 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", item.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(0))); @@ -135,12 +140,12 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/config/correctiontypes/search/findByItem/") + getClient(tokenAdmin).perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", item.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(1))) .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder(allOf( - hasJsonPath("$.id", equalTo("reinstateRequest")), + hasJsonPath("$.id", equalTo("request-reinstate")), hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")) )))); } @@ -156,14 +161,16 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", item.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(1))) .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( allOf( - hasJsonPath("$.id", equalTo("withdrawnRequest")), - hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.creationForm", equalTo("provideReason")), + hasJsonPath("$.type", equalTo("correctiontype")) ) ))); } @@ -179,14 +186,16 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", itemOne.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(1))) .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( allOf( - hasJsonPath("$.id", equalTo("withdrawnRequest")), - hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.creationForm", equalTo("provideReason")), + hasJsonPath("$.type", equalTo("correctiontype")) ) ))); @@ -201,14 +210,16 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByItem") .param("uuid", item.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(1))) .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( allOf( - hasJsonPath("$.id", equalTo("withdrawnRequest")), - hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")) + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.creationForm", equalTo("provideReason")), + hasJsonPath("$.type", equalTo("correctiontype")) ) ))); } @@ -216,14 +227,14 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio @Test public void findByTopicWithoutTopicParameterTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic/")) + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic")) .andExpect(status().isBadRequest()); } @Test public void findByWrongTopicTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic") .param("topic", "wrongValue")) .andExpect(status().isNoContent()); } @@ -231,11 +242,13 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio @Test public void findByTopicTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic/") + getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic") .param("topic", "REQUEST/WITHDRAWN")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", equalTo("withdrawnRequest"))) - .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))); + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index f6438ae4e6..a209d4bf46 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -31,6 +31,7 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.QAEventMatcher; import org.dspace.app.rest.model.patch.Operation; @@ -48,6 +49,7 @@ import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.QAEventProcessed; import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -890,6 +892,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); + String ePersonToken = getAuthToken(eperson.getEmail(), password); getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.inArchive", is(false))) @@ -897,13 +900,16 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { AtomicReference idRef = new AtomicReference(); - getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") - .contentType(RestMediaTypes.TEXT_URI_LIST) - .content("https://localhost:8080/server/api/config/correctiontypes/reinstateRequest\n" + - "https://localhost:8080/server/api/core/items/" + publication.getID() - )) - .andExpect(status().isCreated()) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + ObjectMapper mapper = new ObjectMapper(); + CorrectionTypeMessageDTO dto = new CorrectionTypeMessageDTO("provided reason!"); + + getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .contentType(contentType) + .content(mapper.writeValueAsBytes(dto))) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) .andExpect(status().isOk()) diff --git a/dspace/config/spring/api/correction-types.xml b/dspace/config/spring/api/correction-types.xml index 3e3127e0c7..40d9664918 100644 --- a/dspace/config/spring/api/correction-types.xml +++ b/dspace/config/spring/api/correction-types.xml @@ -5,12 +5,13 @@ default-lazy-init="true"> - + + - + From d822b24a782f99f94191eb25a412475fbdefb7f4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 3 Nov 2023 01:07:26 +0100 Subject: [PATCH 14/58] [CST-12108] fix ITs --- .../app/rest/QAEventRestRepositoryIT.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index a209d4bf46..99eedee95b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -53,7 +53,6 @@ import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.RestMediaTypes; /** * Integration tests for {@link QAEventRestRepository}. @@ -797,19 +796,19 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void createQAEventByCorrectionTypeUnAuthorizedTest() throws Exception { getClient().perform(post("/api/integration/qualityassuranceevents") - .contentType(RestMediaTypes.TEXT_URI_LIST) - .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest\n" + - "https://localhost:8080/server/api/core/items/" + UUID.randomUUID())) + .param("correctionType", "request-withdrawn") + .param("target", UUID.randomUUID().toString()) + .contentType(contentType)) .andExpect(status().isUnauthorized()); } @Test - public void createQAEventByCorrectionTypeWithMissingUriTest() throws Exception { + public void createQAEventByCorrectionTypeWithMissingTargetTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") - .contentType(RestMediaTypes.TEXT_URI_LIST) - .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest")) - .andExpect(status().isBadRequest()); + .param("correctionType", "request-withdrawn") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -838,10 +837,9 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { AtomicReference idRef = new AtomicReference(); getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") - .contentType(RestMediaTypes.TEXT_URI_LIST) - .content("https://localhost:8080/server/api/config/correctiontypes/withdrawnRequest\n" + - "https://localhost:8080/server/api/core/items/" + publication.getID() - )) + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .contentType(contentType)) .andExpect(status().isCreated()) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); From a959ba2bbb75a9103294c6a4bf30101dd7dfe8f4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 21 Nov 2023 16:03:30 +0100 Subject: [PATCH 15/58] [CST-12108] minor refactoring --- dspace-api/src/main/java/org/dspace/content/QAEvent.java | 4 ++-- .../org/dspace/correctiontype/ReinstateCorrectionType.java | 6 +++--- .../org/dspace/correctiontype/WithdrawnCorrectionType.java | 6 +++--- dspace/config/dspace.cfg | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java index da554f6672..a9dbfd8416 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -31,7 +31,7 @@ public class QAEvent { public static final String DISCARDED = "discarded"; public static final String OPENAIRE_SOURCE = "openaire"; - public static final String INTERNAL_ITEM_SOURCE = "internal-item"; + public static final String DSPACE_USERS_SOURCE = "DSpaceUsers"; private String source; @@ -197,7 +197,7 @@ public class QAEvent { switch (getSource()) { case OPENAIRE_SOURCE: return OpenaireMessageDTO.class; - case INTERNAL_ITEM_SOURCE: + case DSPACE_USERS_SOURCE: return CorrectionTypeMessageDTO.class; default: throw new IllegalArgumentException("Unknown event's source: " + getSource()); diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 4af0084220..5d025895c9 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -7,7 +7,7 @@ */ package org.dspace.correctiontype; -import static org.dspace.content.QAEvent.INTERNAL_ITEM_SOURCE; +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; import java.sql.SQLException; import java.util.Date; @@ -57,8 +57,8 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; - QAEvent qaEvent = new QAEvent(INTERNAL_ITEM_SOURCE, - "handle:" + targetItem.getHandle(), + QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, + context.getCurrentUser().getID().toString(), targetItem.getID().toString(), targetItem.getName(), this.getTopic(), diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 3db5eea563..91eab2c7d3 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -7,7 +7,7 @@ */ package org.dspace.correctiontype; -import static org.dspace.content.QAEvent.INTERNAL_ITEM_SOURCE; +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; import static org.dspace.core.Constants.READ; import java.sql.SQLException; @@ -51,8 +51,8 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; - QAEvent qaEvent = new QAEvent(INTERNAL_ITEM_SOURCE, - "handle:" + targetItem.getHandle(), + QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, + context.getCurrentUser().getID().toString(), targetItem.getID().toString(), targetItem.getName(), this.getTopic(), 1.0, diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index f75a9a6bd1..edb988148c 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1086,7 +1086,7 @@ webui.preview.brand.fontpoint = 12 # ItemCountDAO.class = org.dspace.browse.ItemCountDAOSolr ###### QAEvent source Configuration ###### -qaevent.sources = openaire, internal-item +qaevent.sources = openaire, DSpaceUsers ###### Browse Configuration ###### # From 89b7c227d283877f6075de799037eed03fbba1db Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 21 Nov 2023 16:15:09 +0100 Subject: [PATCH 16/58] [CST-12108] porting of security part for QAEvents --- .../java/org/dspace/qaevent/QASource.java | 18 +- .../main/java/org/dspace/qaevent/QATopic.java | 26 +- .../qaevent/script/OpenaireEventsImport.java | 43 +- .../AdministratorsOnlyQASecurity.java | 49 ++ .../dspace/qaevent/security/QASecurity.java | 51 +++ .../security/UserBasedFilterQASecurity.java | 70 +++ .../service/QAEventSecurityService.java | 55 +++ .../qaevent/service/QAEventService.java | 162 +++++-- .../impl/QAEventSecurityServiceImpl.java | 57 +++ .../service/impl/QAEventServiceImpl.java | 381 ++++++++++++---- .../script/OpenaireEventsImportIT.java | 63 ++- .../rest/QAEventRelatedRestController.java | 5 +- .../QAEventRelatedLinkRepository.java | 4 +- .../repository/QAEventRestRepository.java | 47 +- .../QAEventTargetLinkRepository.java | 6 +- .../QAEventTopicLinkRepository.java | 12 +- .../repository/QASourceRestRepository.java | 26 +- .../repository/QATopicRestRepository.java | 32 +- .../QAEventRestPermissionEvaluatorPlugin.java | 74 +++ ...QASourceRestPermissionEvaluatorPlugin.java | 71 +++ .../app/rest/QAEventRestRepositoryIT.java | 431 +++++++++--------- dspace/config/spring/api/qaevents.xml | 19 + 22 files changed, 1296 insertions(+), 406 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java index b3f7be5f52..c248bd7276 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -8,6 +8,7 @@ package org.dspace.qaevent; import java.util.Date; +import java.util.UUID; /** * This model class represent the source/provider of the QA events (as OpenAIRE). @@ -16,9 +17,16 @@ import java.util.Date; * */ public class QASource { + + /** + * if the QASource stats (see next attributes) are related to a specific target + */ + private UUID focus; + private String name; - private long totalEvents; private Date lastEvent; + private long totalEvents; + public String getName() { return name; @@ -43,4 +51,12 @@ public class QASource { public void setLastEvent(Date lastEvent) { this.lastEvent = lastEvent; } + + public UUID getFocus() { + return focus; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java index 63e523b9cb..03f8333968 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -8,18 +8,24 @@ package org.dspace.qaevent; import java.util.Date; +import java.util.UUID; /** * This model class represent the quality assurance broker topic concept. A * topic represents a type of event and is therefore used to group events. * * @author Andrea Bollini (andrea.bollini at 4science.it) - * */ public class QATopic { + private String key; - private long totalEvents; + /** + * if the QASource stats (see next attributes) are related to a specific target + */ + private UUID focus; + private String source; private Date lastEvent; + private long totalEvents; public String getKey() { return key; @@ -44,4 +50,20 @@ public class QATopic { public void setLastEvent(Date lastEvent) { this.lastEvent = lastEvent; } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public UUID getFocus() { + return focus; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index e45dc3e159..8c620acbf0 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -7,7 +7,6 @@ */ package org.dspace.qaevent.script; - import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.substringAfter; @@ -28,10 +27,12 @@ import eu.dnetlib.broker.BrokerClient; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.handle.service.HandleService; import org.dspace.qaevent.service.BrokerClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.DSpaceRunnable; @@ -69,6 +70,8 @@ import org.dspace.utils.DSpace; public class OpenaireEventsImport extends DSpaceRunnable> { + private HandleService handleService; + private QAEventService qaEventService; private String[] topicsToImport; @@ -101,7 +104,9 @@ public class OpenaireEventsImport jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - qaEventService = new DSpace().getSingletonService(QAEventService.class); + DSpace dspace = new DSpace(); + handleService = dspace.getSingletonService(HandleService.class); + qaEventService = dspace.getSingletonService(QAEventService.class); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); brokerClient = BrokerClientFactory.getInstance().getBrokerClient(); @@ -234,14 +239,46 @@ public class OpenaireEventsImport private void storeOpenaireQAEvents(List events) { for (QAEvent event : events) { try { + final String resourceUUID = getResourceUUID(context, event.getOriginalId()); + if (resourceUUID == null) { + throw new IllegalArgumentException("Skipped event " + event.getEventId() + + " related to the oai record " + event.getOriginalId() + " as the record was not found"); + } + event.setTarget(resourceUUID); storeOpenaireQAEvent(event); - } catch (RuntimeException e) { + } catch (RuntimeException | SQLException e) { handler.logWarning("An error occurs storing the event with id " + event.getEventId() + ": " + getMessage(e)); } } } + private String getResourceUUID(Context context, String originalId) throws IllegalStateException, SQLException { + String id = getHandleFromOriginalId(originalId); + if (id != null) { + Item item = (Item) handleService.resolveToObject(context, id); + if (item != null) { + final String itemUuid = item.getID().toString(); + context.uncacheEntity(item); + return itemUuid; + } else { + return null; + } + } else { + throw new IllegalArgumentException("Malformed originalId " + originalId); + } + } + + // oai:www.openstarts.units.it:10077/21486 + private String getHandleFromOriginalId(String originalId) { + int startPosition = originalId.lastIndexOf(':'); + if (startPosition != -1) { + return originalId.substring(startPosition + 1, originalId.length()); + } else { + return originalId; + } + } + /** * Store the given QAEvent, skipping it if it is not supported. * diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java new file mode 100644 index 0000000000..40a757dc44 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java @@ -0,0 +1,49 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.qaevent.security; + +import java.sql.SQLException; +import java.util.Optional; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QASecurity that restrict access to the QA Source and related events only to repository administrators + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public class AdministratorsOnlyQASecurity implements QASecurity { + + @Autowired + private AuthorizeService authorizeService; + + public Optional generateFilterQuery(Context context, EPerson currentUser) { + return Optional.empty(); + } + + public boolean canSeeQASource(Context context, EPerson user) { + try { + return authorizeService.isAdmin(context, user); + } catch (SQLException e) { + return false; + } + } + + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { + try { + return authorizeService.isAdmin(context, user); + } catch (SQLException e) { + return false; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java new file mode 100644 index 0000000000..98a1f13cda --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java @@ -0,0 +1,51 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.qaevent.security; + +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public interface QASecurity { + + /** + * Return a SOLR queries that can be applied querying the qaevent SOLR core to retrieve only the qaevents visible to + * the provided user + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return the SOLR filter query to apply + */ + public Optional generateFilterQuery(Context context, EPerson user); + + /** + * Return true it the user is potentially allowed to see events in the qasource that adopt this + * security strategy + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return true if the user can eventually see some qaevents + */ + public boolean canSeeQASource(Context context, EPerson user); + + /** + * Return true it the user is potentially allowed to see events in the qasource that adopt this + * security strategy + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return true if the user can see the provided qaEvent + */ + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java new file mode 100644 index 0000000000..3d66d221e6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.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.qaevent.security; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.Optional; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.service.QAEventService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QASecurity implementations that allow access to only qa events that match a SORL query generated using the eperson + * uuid + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public class UserBasedFilterQASecurity implements QASecurity { + + private String filterTemplate; + private boolean allowAdmins = true; + + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + + public Optional generateFilterQuery(Context context, EPerson user) { + try { + if (allowAdmins && authorizeService.isAdmin(context, user)) { + return Optional.empty(); + } else { + return Optional.of(MessageFormat.format(filterTemplate, user.getID().toString())); + } + } catch (SQLException e) { + throw new RuntimeException("Error checking permissions", e); + } + } + + public boolean canSeeQASource(Context context, EPerson user) { + return user != null; + } + + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { + try { + return (allowAdmins && authorizeService.isAdmin(context, user)) + || qaEventService.qaEventsInSource(context, user, qaEvent.getEventId(), qaEvent.getSource()); + } catch (SQLException e) { + throw new RuntimeException("Error checking permissions", e); + } + } + + public void setFilterTemplate(String filterTemplate) { + this.filterTemplate = filterTemplate; + } + + public void setAllowAdmins(boolean allowAdmins) { + this.allowAdmins = allowAdmins; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java new file mode 100644 index 0000000000..d797ad98a4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.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.qaevent.service; + +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Interface to limit the visibility of {@link QAEvent} to specific users. + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + * + */ +public interface QAEventSecurityService { + + /** + * Check if the specified user can see a specific QASource + * @param context the context + * @param user the eperson to consider + * @param qaSource the qaSource involved + * @return true if the specified user can eventually see events in the QASource + */ + boolean canSeeSource(Context context, EPerson user, String qaSource); + + /** + * Check if the specified user can see a specific QAEvent. It is expected that a QAEvent in a not visible QASource + * cannot be accessed. So implementation of this method should enforce this rule. + * + * @param context the context + * @param user the eperson to consider + * @param qaEvent the qaevent to check + * @return true if the specified user can see the specified event + */ + boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent); + + /** + * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the + * specified user + * + * @param context the context + * @param user the eperson to consider + * @param qaSource the qaSource involved + * @return the solr filter query + */ + public Optional generateQAEventFilterQuery(Context context, EPerson user, String qaSource); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index ea923251b8..b74931b339 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -12,6 +12,7 @@ import java.util.UUID; import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.qaevent.QASource; import org.dspace.qaevent.QATopic; @@ -26,73 +27,70 @@ public interface QAEventService { /** * Find all the event's topics. * + * @param context the DSpace context * @param offset the offset to apply * @param pageSize the page size * @return the topics list */ - public List findAllTopics(long offset, long pageSize); + public List findAllTopics(Context context, long offset, long pageSize); /** * Find all the event's topics related to the given source. * - * @param source the source to search for - * @param offset the offset to apply - * @param count the page size - * @return the topics list + * @param context the DSpace context + * @param source the source to search for + * @param offset the offset to apply + * @param count the page size + * @return the topics list */ - public List findAllTopicsBySource(String source, long offset, long count); + public List findAllTopicsBySource(Context context, String source, long offset, long count); /** * Count all the event's topics. * - * @return the count result + * @return the count result */ public long countTopics(); /** * Count all the event's topics related to the given source. * + * @param context the DSpace context * @param source the source to search for * @return the count result */ - public long countTopicsBySource(String source); + public long countTopicsBySource(Context context, String source); /** * Find all the events by topic. * + * @param context the DSpace context + * @param source the source name * @param topic the topic to search for * @param offset the offset to apply - * @param pageSize the page size - * @param orderField the field to order for - * @param ascending true if the order should be ascending, false otherwise + * @param size the page size * @return the events */ - public List findEventsByTopicAndPage(String topic, long offset, int pageSize, - String orderField, boolean ascending); + public List findEventsByTopic(Context context, String source, String topic, long offset, int size); /** * Find all the events by topic. * - * @param topic the topic to search for - * @return the events + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @return the events count */ - public List findEventsByTopic(String topic); - - /** - * Find all the events by topic. - * - * @param topic the topic to search for - * @return the events count - */ - public long countEventsByTopic(String topic); + public long countEventsByTopic(Context context, String source, String topic); /** * Find an event by the given id. * + * @param context the DSpace context * @param id the id of the event to search for * @return the event */ - public QAEvent findEventByEventId(String id); + public QAEvent findEventByEventId(Context context, String id); /** * Store the given event. @@ -127,26 +125,58 @@ public interface QAEventService { /** * Find a specific source by the given name. * - * @param source the source name - * @return the source + * @param context the DSpace context + * @param source the source name + * @return the source */ - public QASource findSource(String source); + public QASource findSource(Context context, String source); + + /** + * Find a specific source by the given name including the stats focused on the target item. + * + * @param context the DSpace context + * @param source the source name + * @param target the uuid of the item target + * @return the source + */ + public QASource findSource(Context context, String source, UUID target); /** * Find all the event's sources. * + * @param context the DSpace context * @param offset the offset to apply * @param pageSize the page size * @return the sources list */ - public List findAllSources(long offset, int pageSize); + public List findAllSources(Context context, long offset, int pageSize); /** * Count all the event's sources. * + * @param context the DSpace context * @return the count result */ - public long countSources(); + public long countSources(Context context); + + /** + * Count all the event's sources related to a specific item + * + * @param context the DSpace context + * @param target the item uuid + * @return the count result + */ + public long countSourcesByTarget(Context context, UUID target); + + /** + * Count all the event's topics related to the given source referring to a specific item + * + * @param context the DSpace context + * @param target the item uuid + * @param source the source to search for + * @return the count result + */ + public long countTopicsBySourceAndTarget(Context context, String source, UUID target); /** * Check if the given QA event supports a related item. @@ -156,4 +186,76 @@ public interface QAEventService { */ public boolean isRelatedItemSupported(QAEvent qaevent); + /** + * Find a list of QA events according to the pagination parameters for the specified topic and target sorted by + * trust descending + * + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @param offset the offset to apply + * @param pageSize the page size + * @param target the uuid of the QA event's target + * @return the events + */ + public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset, + int pageSize, UUID target); + + /** + * Check if a qaevent with the provided id is visible to the current user according to the source security + * + * @param context the DSpace context + * @param user the user to consider for the security check + * @param eventId the id of the event to check for existence + * @param source the qa source name + * @return true if the event exists + */ + public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source); + + /** + * Count the QA events related to the specified topic and target + * + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @param target the uuid of the QA event's target + * @return the count result + */ + public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target); + + /** + * Find a specific topic by its name, source and optionally a target. + * + * @param context the DSpace context + * @param sourceName the name of the source + * @param topicName the topic name to search for + * @param target (nullable) the uuid of the target to focus on + * @return the topic + */ + public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName, UUID target); + + /** + * Find all the event's sources related to a specific item + * + * @param context the DSpace context + * @param target the item referring to + * @param offset the offset to apply + * @param pageSize the page size + * @return the source list + */ + public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize); + + /** + * Find all the event's topics related to the given source for a specific item + * + * @param context the DSpace context + * @param source (not null) the source to search for + * @param target the item referring to + * @param offset the offset to apply + * @param pageSize the page size + * @return the topics list + */ + public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, + int pageSize); + } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java new file mode 100644 index 0000000000..33b781ad58 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.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.qaevent.service.impl; + +import java.util.Map; +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.security.QASecurity; +import org.dspace.qaevent.service.QAEventSecurityService; + +public class QAEventSecurityServiceImpl implements QAEventSecurityService { + + private QASecurity defaultSecurity; + + private Map qaSecurityConfiguration; + + public void setQaSecurityConfiguration(Map qaSecurityConfiguration) { + this.qaSecurityConfiguration = qaSecurityConfiguration; + } + + public void setDefaultSecurity(QASecurity defaultSecurity) { + this.defaultSecurity = defaultSecurity; + } + + @Override + public Optional generateQAEventFilterQuery(Context context, EPerson user, String qaSource) { + QASecurity qaSecurity = getQASecurity(qaSource); + return qaSecurity.generateFilterQuery(context, user); + } + + private QASecurity getQASecurity(String qaSource) { + return qaSecurityConfiguration.getOrDefault(qaSource, defaultSecurity); + } + + @Override + public boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent) { + String source = qaEvent.getSource(); + QASecurity qaSecurity = getQASecurity(source); + return qaSecurity.canSeeQASource(context, user) + && qaSecurity.canSeeQAEvent(context, user, qaEvent); + } + + @Override + public boolean canSeeSource(Context context, EPerson user, String qaSource) { + QASecurity qaSecurity = getQASecurity(qaSource); + return qaSecurity.canSeeQASource(context, user); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 132c072ebc..070f688935 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -16,6 +16,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -36,15 +38,15 @@ import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; -import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.handle.service.HandleService; +import org.dspace.eperson.EPerson; import org.dspace.qaevent.QASource; import org.dspace.qaevent.QATopic; import org.dspace.qaevent.dao.QAEventsDao; import org.dspace.qaevent.dao.impl.QAEventsDaoImpl; +import org.dspace.qaevent.service.QAEventSecurityService; import org.dspace.qaevent.service.QAEventService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -65,10 +67,10 @@ public class QAEventServiceImpl implements QAEventService { protected ConfigurationService configurationService; @Autowired(required = true) - protected ItemService itemService; + protected QAEventSecurityService qaSecurityService; - @Autowired - private HandleService handleService; + @Autowired(required = true) + protected ItemService itemService; @Autowired private QAEventsDaoImpl qaEventsDao; @@ -123,10 +125,16 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public long countTopicsBySource(String source) { + public long countTopicsBySource(Context context, String source) { + if (isNotSupportedSource(source) + || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { + return 0; + } SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - solrQuery.setQuery("*:*"); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, + context.getCurrentUser(), source); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); @@ -140,6 +148,46 @@ public class QAEventServiceImpl implements QAEventService { return response.getFacetField(TOPIC).getValueCount(); } + @Override + public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName,UUID target) { + if (isNotSupportedSource(sourceName) + || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName)) { + return null; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, + context.getCurrentUser(), sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topicName + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + for (Count c : facetField.getValues()) { + if (c.getName().equals(topicName)) { + QATopic topic = new QATopic(); + topic.setSource(sourceName); + topic.setKey(c.getName()); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + return topic; + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return null; + } + @Override public void deleteEventByEventId(String id) { try { @@ -188,12 +236,12 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findAllTopics(long offset, long count) { - return findAllTopicsBySource(null, offset, count); + public List findAllTopics(Context context, long offset, long count) { + return findAllTopicsBySource(context, null, offset, count); } @Override - public List findAllTopicsBySource(String source, long offset, long count) { + public List findAllTopicsBySource(Context context, String source, long offset, long count) { if (source != null && isNotSupportedSource(source)) { return null; @@ -214,7 +262,6 @@ public class QAEventServiceImpl implements QAEventService { try { response = getSolr().query(solrQuery); FacetField facetField = response.getFacetField(TOPIC); - topics = new ArrayList<>(); int idx = 0; for (Count c : facetField.getValues()) { if (idx < offset) { @@ -264,11 +311,11 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public QAEvent findEventByEventId(String eventId) { - SolrQuery param = new SolrQuery(EVENT_ID + ":" + eventId); - QueryResponse response; + public QAEvent findEventByEventId(Context context, String eventId) { + SolrQuery solrQuery = new SolrQuery("*:*"); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); try { - response = getSolr().query(param); + QueryResponse response = getSolr().query(solrQuery); if (response != null) { SolrDocumentList list = response.getResults(); if (list != null && list.size() == 1) { @@ -283,84 +330,101 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findEventsByTopicAndPage(String topic, long offset, - int pageSize, String orderField, boolean ascending) { + public List findEventsByTopic(Context context, String source, String topic, long offset, int size) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); + } SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(((Long) offset).intValue()); - if (pageSize != -1) { - solrQuery.setRows(pageSize); - } - solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); - solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "\\\\/")); + solrQuery.setRows(size); + solrQuery.setSort(TRUST, ORDER.desc); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); - QueryResponse response; try { - response = getSolr().query(solrQuery); + QueryResponse response = getSolr().query(solrQuery); if (response != null) { - SolrDocumentList list = response.getResults(); + SolrDocumentList solrDocuments = response.getResults(); List responseItem = new ArrayList<>(); - for (SolrDocument doc : list) { + for (SolrDocument doc : solrDocuments) { QAEvent item = getQAEventFromSOLR(doc); responseItem.add(item); } return responseItem; } } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } - return List.of(); } @Override - public List findEventsByTopic(String topic) { - return findEventsByTopicAndPage(topic, 0, -1, TRUST, false); - } + public long countEventsByTopic(Context context, String source, String topic) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return 0; + } + + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); - @Override - public long countEventsByTopic(String topic) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - solrQuery.setQuery(TOPIC + ":" + topic.replace("!", "/")); - QueryResponse response = null; + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); try { - response = getSolr().query(solrQuery); - return response.getResults().getNumFound(); + return getSolr().query(solrQuery).getResults().getNumFound(); } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } } @Override - public QASource findSource(String sourceName) { + public QASource findSource(Context context, String sourceName) { + String[] split = sourceName.split(":"); + return findSource(context, split[0], split.length == 2 ? UUID.fromString(split[1]) : null); + } - if (isNotSupportedSource(sourceName)) { + @Override + public QASource findSource(Context context, String sourceName, UUID target) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return null; } - SolrQuery solrQuery = new SolrQuery("*:*"); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setRows(0); - solrQuery.addFilterQuery(SOURCE + ":" + sourceName); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + if (target != null) { + solrQuery.addFilterQuery("resource_uuid:" + target.toString()); + } solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(SOURCE); - QueryResponse response; try { - response = getSolr().query(solrQuery); + QueryResponse response = getSolr().query(solrQuery); FacetField facetField = response.getFacetField(SOURCE); for (Count c : facetField.getValues()) { if (c.getName().equalsIgnoreCase(sourceName)) { QASource source = new QASource(); source.setName(c.getName()); + source.setFocus(target); source.setTotalEvents(c.getCount()); source.setLastEvent(new Date()); return source; } } } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } QASource source = new QASource(); @@ -371,18 +435,13 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findAllSources(long offset, int pageSize) { + public List findAllSources(Context context, long offset, int pageSize) { return Arrays.stream(getSupportedSources()) - .map((sourceName) -> findSource(sourceName)) - .sorted(comparing(QASource::getTotalEvents).reversed()) - .skip(offset) - .limit(pageSize) - .collect(Collectors.toList()); - } - - @Override - public long countSources() { - return getSupportedSources().length; + .map((sourceName) -> findSource(context, sourceName)) + .sorted(comparing(QASource::getTotalEvents).reversed()) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); } @Override @@ -401,42 +460,11 @@ public class QAEventServiceImpl implements QAEventService { doc.addField(TRUST, dto.getTrust()); doc.addField(MESSAGE, dto.getMessage()); doc.addField(LAST_UPDATE, new Date()); - final String resourceUUID = getResourceUUID(context, dto.getOriginalId()); - if (resourceUUID == null) { - throw new IllegalArgumentException("Skipped event " + checksum + - " related to the oai record " + dto.getOriginalId() + " as the record was not found"); - } - doc.addField(RESOURCE_UUID, resourceUUID); + doc.addField(RESOURCE_UUID, dto.getTarget()); doc.addField(RELATED_UUID, dto.getRelated()); return doc; } - private String getResourceUUID(Context context, String originalId) throws Exception { - String id = getHandleFromOriginalId(originalId); - if (id != null) { - Item item = (Item) handleService.resolveToObject(context, id); - if (item != null) { - final String itemUuid = item.getID().toString(); - context.uncacheEntity(item); - return itemUuid; - } else { - return null; - } - } else { - throw new IllegalArgumentException("Malformed originalId " + originalId); - } - } - - // oai:www.openstarts.units.it:10077/21486 - private String getHandleFromOriginalId(String originalId) { - int startPosition = originalId.lastIndexOf(':'); - if (startPosition != -1) { - return originalId.substring(startPosition + 1, originalId.length()); - } else { - return null; - } - } - private QAEvent getQAEventFromSOLR(SolrDocument doc) { QAEvent item = new QAEvent(); item.setSource((String) doc.get(SOURCE)); @@ -452,6 +480,84 @@ public class QAEventServiceImpl implements QAEventService { return item; } + @Override + public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source) { + SolrQuery solrQuery = new SolrQuery(); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, user, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + if (response != null) { + return response.getResults().getNumFound() == 1; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Exception querying Solr", e); + } + return false; + } + + @Override + public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); + QueryResponse response = null; + try { + response = getSolr().query(solrQuery); + return response.getResults().getNumFound(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset, + int pageSize, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setStart(((Long) offset).intValue()); + solrQuery.setRows(pageSize); + solrQuery.setSort(TRUST, ORDER.desc); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); + + try { + QueryResponse response = getSolr().query(solrQuery); + if (response != null) { + SolrDocumentList list = response.getResults(); + List responseItem = new ArrayList<>(); + for (SolrDocument doc : list) { + QAEvent item = getQAEventFromSOLR(doc); + responseItem.add(item); + } + return responseItem; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return List.of(); + } + private boolean isNotSupportedSource(String source) { return !ArrayUtils.contains(getSupportedSources(), source); } @@ -460,4 +566,103 @@ public class QAEventServiceImpl implements QAEventService { return configurationService.getArrayProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); } + @Override + public long countSources(Context context) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); + } + + @Override + public long countSourcesByTarget(Context context, UUID target) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName, target)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); + } + + @Override + public long countTopicsBySourceAndTarget(Context context, String source, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + try { + QueryResponse response = getSolr().query(solrQuery); + return response.getFacetField(TOPIC).getValueCount(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName, target)) + .filter(Objects::nonNull) + .sorted(comparing(QASource::getTotalEvents).reversed()) + .filter(source -> source.getTotalEvents() > 0) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); + } + + @Override + public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, + int pageSize) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); + } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.setFacetLimit((int) (offset + pageSize)); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":" + target.toString()); + } + try { + List topics = new ArrayList<>(); + QueryResponse response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + int idx = 0; + for (Count c : facetField.getValues()) { + if (idx < offset) { + idx++; + continue; + } + QATopic topic = new QATopic(); + topic.setSource(source); + topic.setKey(c.getName()); + topic.setFocus(target); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + topics.add(topic); + idx++; + } + return topics; + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + } diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index dbe44fd2e7..73fd60ddcb 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -43,11 +43,13 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.QAEvent; import org.dspace.matcher.QASourceMatcher; import org.dspace.matcher.QATopicMatcher; import org.dspace.qaevent.service.BrokerClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.impl.BrokerClientFactoryImpl; +import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.junit.After; import org.junit.Before; @@ -71,11 +73,17 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase private BrokerClient mockBrokerClient = mock(BrokerClient.class); + private ConfigurationService configurationService = new DSpace().getConfigurationService(); + @Before public void setup() { context.turnOffAuthorisationSystem(); - + // we want all the test in this class to be run using the administrator user as OpenAIRE events are only visible + // to administrators. + // Please note that test related to forbidden and unauthorized access to qaevent are included in + // the QAEventRestRepositoryIT here we are only testing that the import script is creating the expected events + context.setCurrentUser(admin); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -85,7 +93,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase .build(); context.restoreAuthSystemState(); - + configurationService.setProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); ((BrokerClientFactoryImpl) BrokerClientFactory.getInstance()).setBrokerClient(mockBrokerClient); } @@ -155,9 +163,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Trying to read the QA events from the provided file", "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L), QATopicMatcher.with("ENRICH/MISSING/PID", 1L), @@ -170,14 +178,15 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0, 20), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, "Egypt, crossroad of translations and literary interweavings", projectMessage, "ENRICH/MORE/PROJECT", 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE,"ENRICH/MISSING/ABSTRACT", 0, 20), + contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); @@ -211,9 +220,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Trying to read the QA events from the provided file", "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L), QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L) @@ -221,7 +230,8 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), + contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); @@ -251,13 +261,15 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Trying to read the QA events from the provided file", "Found 2 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(qaEventService.findAllTopics(0, 20), contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + assertThat(qaEventService.findAllTopics(context, 0, 20), + contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), + contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); @@ -278,9 +290,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(handler.getWarningMessages(),empty()); assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the provided file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20), empty()); verifyNoInteractions(mockBrokerClient); } @@ -325,9 +337,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L), QATopicMatcher.with("ENRICH/MISSING/PID", 1L), @@ -340,14 +352,15 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0 ,20), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, "Egypt, crossroad of translations and literary interweavings", projectMessage, "ENRICH/MORE/PROJECT", 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), containsInAnyOrder( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), + containsInAnyOrder( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d), pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", thirdItem, "Test Publication 2", @@ -379,9 +392,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(handler.getWarningMessages(), empty()); assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the OPENAIRE broker")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20), empty()); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); @@ -430,17 +443,19 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), QATopicMatcher.with("ENRICH/MISSING/PID", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L), QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1)); - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0, 20), + hasSize(1)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), + hasSize(2)); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java index 538d99b3ce..ac748bfc76 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java @@ -78,8 +78,7 @@ public class QAEventRelatedRestController { @RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainCurrentRequestContext(); - - QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); + QAEvent qaevent = qaEventService.findEventByEventId(context, qaeventId); if (qaevent == null) { throw new ResourceNotFoundException("No such qa event: " + qaeventId); } @@ -122,7 +121,7 @@ public class QAEventRelatedRestController { throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainCurrentRequestContext(); - QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); + QAEvent qaevent = qaEventService.findEventByEventId(context, qaeventId); if (qaevent == null) { throw new ResourceNotFoundException("No such qa event: " + qaeventId); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java index c711d2ec37..075fcf2180 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java @@ -51,11 +51,11 @@ public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository i * @param projection the projection object * @return the item rest representation of the secondary item related to qa event */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - QAEvent qaEvent = qaEventService.findEventByEventId(id); + QAEvent qaEvent = qaEventService.findEventByEventId(context, id); if (qaEvent == null) { throw new ResourceNotFoundException("No qa event with ID: " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index c91cf135c5..5b7d793cfa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -39,7 +39,6 @@ import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort.Direction; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -70,9 +69,14 @@ public class QAEventRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); + } + + @Override + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QAEventRest findOne(Context context, String id) { - QAEvent qaEvent = qaEventService.findEventByEventId(id); + QAEvent qaEvent = qaEventService.findEventByEventId(context, id); if (qaEvent == null) { // check if this request is part of a patch flow qaEvent = (QAEvent) requestService.getCurrentRequest().getAttribute("patchedNotificationEvent"); @@ -85,26 +89,26 @@ public class QAEventRestRepository extends DSpaceRestRepository findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, - Pageable pageable) { - List qaEvents = null; - long count = 0L; - boolean ascending = false; - if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { - ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; - } - qaEvents = qaEventService.findEventsByTopicAndPage(topic, - pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); - count = qaEventService.countEventsByTopic(topic); - if (qaEvents == null) { + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTopic(@Parameter(value = "topic", required = true) String topic, Pageable pageable) { + Context context = obtainContext(); + String[] topicIdSplitted = topic.split(":", 2); + if (topicIdSplitted.length != 2) { return null; } + String sourceName = topicIdSplitted[0]; + String topicName = topicIdSplitted[1].replaceAll("!", "/"); + + List qaEvents = qaEventService.findEventsByTopic(context, sourceName, topicName, + pageable.getOffset(), + pageable.getPageSize()); + long count = qaEventService.countEventsByTopic(context, sourceName, topicName); return converter.toRestPage(qaEvents, pageable, count, utils.obtainProjection()); } @Override + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'DELETE')") protected void delete(Context context, String eventId) throws AuthorizeException { Item item = findTargetItem(context, eventId); EPerson eperson = context.getCurrentUser(); @@ -113,20 +117,15 @@ public class QAEventRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); - } - - @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'WRITE')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, String id, Patch patch) throws SQLException, AuthorizeException { - QAEvent qaEvent = qaEventService.findEventByEventId(id); + QAEvent qaEvent = qaEventService.findEventByEventId(context, id); resourcePatch.patch(context, qaEvent, patch.getOperations()); } private Item findTargetItem(Context context, String eventId) { - QAEvent qaEvent = qaEventService.findEventByEventId(eventId); + QAEvent qaEvent = qaEventService.findEventByEventId(context, eventId); if (qaEvent == null) { return null; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java index 2df3836b9b..9e218dd27e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java @@ -48,13 +48,13 @@ public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository im * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the item rest representation of the qa event target + * @return the item rest representation of the qa event target */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - QAEvent qaEvent = qaEventService.findEventByEventId(id); + QAEvent qaEvent = qaEventService.findEventByEventId(context, id); if (qaEvent == null) { throw new ResourceNotFoundException("No qa event with ID: " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java index f9ed484428..421ddab0bf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java @@ -42,19 +42,21 @@ public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository imp * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the qa topic rest representation + * @return the qa topic rest representation */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - QAEvent qaEvent = qaEventService.findEventByEventId(id); + QAEvent qaEvent = qaEventService.findEventByEventId(context, id); if (qaEvent == null) { throw new ResourceNotFoundException("No qa event with ID: " + id); } - QATopic topic = qaEventService.findTopicByTopicId(qaEvent.getTopic().replace("/", "!")); + String source = qaEvent.getSource(); + String topicName = qaEvent.getTopic(); + QATopic topic = qaEventService.findTopicBySourceAndNameAndTarget(context, source, topicName, null); if (topic == null) { - throw new ResourceNotFoundException("No topic found with id : " + id); + throw new ResourceNotFoundException("No topic found with source: " + source + " topic: " + topicName); } return converter.toRest(topic, projection); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java index dad2310a77..8af833b8a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java @@ -8,7 +8,10 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.UUID; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.QASourceRest; import org.dspace.core.Context; import org.dspace.qaevent.QASource; @@ -32,9 +35,9 @@ public class QASourceRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List qaSources = qaEventService.findAllSources(pageable.getOffset(), pageable.getPageSize()); - long count = qaEventService.countSources(); + List qaSources = qaEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSources(context); return converter.toRestPage(qaSources, pageable, count, utils.obtainProjection()); } + @SearchRestMethod(name = "byTarget") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTarget(@Parameter(value = "target", required = true) UUID target, + Pageable pageable) { + Context context = obtainContext(); + List topics = qaEventService.findAllSourcesByTarget(context, target, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSourcesByTarget(context, target); + if (topics == null) { + return null; + } + return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); + } @Override public Class getDomainClass() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index a279cac83a..2c3d3a1ad9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.UUID; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -34,7 +35,7 @@ public class QATopicRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize()); + List topics = qaEventService.findAllTopics(context, pageable.getOffset(), pageable.getPageSize()); long count = qaEventService.countTopics(); if (topics == null) { return null; @@ -55,12 +56,27 @@ public class QATopicRestRepository extends DSpaceRestRepository findBySource(Context context, - @Parameter(value = "source", required = true) String source, Pageable pageable) { - List topics = qaEventService.findAllTopicsBySource(source, - pageable.getOffset(), pageable.getPageSize()); - long count = qaEventService.countTopicsBySource(source); + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findBySource(Context context, @Parameter(value = "source", required = true) String source, + Pageable pageable) { + List topics = qaEventService.findAllTopicsBySource(context, source, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countTopicsBySource(context, source); + if (topics == null) { + return null; + } + return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); + } + + @SearchRestMethod(name = "byTarget") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTarget(@Parameter(value = "target", required = true) UUID target, + @Parameter(value = "source", required = true) String source, + Pageable pageable) { + Context context = obtainContext(); + List topics = qaEventService.findAllTopicsBySourceAndTarget(context, source, target, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countTopicsBySourceAndTarget(context, source, target); if (topics == null) { return null; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..a8cfa5fda6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.QAEventRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle Permissions for the {@link QAEventRest} object and its calls + * + * @author Andrea Bollini (4Science) + */ +@Component +public class QAEventRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(QAEventRestPermissionEvaluatorPlugin.class); + + @Autowired + private QAEventService qaEventService; + + @Autowired + private QAEventSecurityService qaEventSecurityService; + + @Autowired + private RequestService requestService; + + /** + * Responsible for checking whether or not the user has access to the requested QASource + * + * @param targetType the type of Rest Object that should be checked for permission. This class would deal only with + * qaevent + * @param targetId string to extract the sourcename from + */ + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (StringUtils.equalsIgnoreCase(QAEventRest.NAME, targetType)) { + log.debug("Checking permission for targetId {}", targetId); + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + if (Objects.isNull(targetId)) { + return true; + } + QAEvent qaEvent = qaEventService.findEventByEventId(context, targetId.toString()); + // everyone is expected to be able to see a not existing event (so we can return not found) + if ((qaEvent == null + || qaEventSecurityService.canSeeEvent(context, context.getCurrentUser(), qaEvent))) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..3d11b4d099 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java @@ -0,0 +1,71 @@ +/** + * 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.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.QASourceRest; +import org.dspace.app.rest.model.QATopicRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle Permissions for the {@link QASourceRest} object and {@link QATopic} + * + * @author Andrea Bollini (4Science) + */ +@Component +public class QASourceRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(QASourceRestPermissionEvaluatorPlugin.class); + + @Autowired + private QAEventSecurityService qaEventSecurityService; + + @Autowired + private RequestService requestService; + + /** + * Responsible for checking whether or not the user has access to the requested QASource + * + * @param targetType the type of Rest Object that should be checked for permission. This class would deal only with + * qasource and qatopic + * @param targetId string to extract the sourcename from + */ + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (StringUtils.equalsIgnoreCase(QASourceRest.NAME, targetType) + || StringUtils.equalsIgnoreCase(QATopicRest.NAME, targetType)) { + log.debug("Checking permission for targetId {}", targetId); + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + if (Objects.isNull(targetId)) { + return true; + } + // the source name is always the first part of the id both for a source than a topic + // users can see all the topic in source that they can access, eventually they will have no + // events visible to them + String sourceName = targetId.toString().split(":")[0]; + return qaEventSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName); + } + return false; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 99eedee95b..5616b0bc8a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -11,6 +11,8 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; import static org.dspace.app.rest.matcher.QAEventMatcher.matchQAEventEntry; +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -69,11 +71,17 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { public void findAllNotImplementedTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(get("/api/integration/qualityassuranceevents")) - .andExpect(status().isMethodNotAllowed()); - String epersonToken = getAuthToken(admin.getEmail(), password); + .andExpect(status() + .isMethodNotAllowed()); + + String epersonToken = getAuthToken(eperson.getEmail(), password); getClient(epersonToken).perform(get("/api/integration/qualityassuranceevents")) - .andExpect(status().isMethodNotAllowed()); - getClient().perform(get("/api/integration/qualityassuranceevents")).andExpect(status().isMethodNotAllowed()); + .andExpect(status() + .isMethodNotAllowed()); + + getClient().perform(get("/api/integration/qualityassuranceevents")) + .andExpect(status() + .isMethodNotAllowed()); } @Test @@ -100,48 +108,63 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findOneWithProjectionTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") + .build(); + QAEvent event5 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") - .withTopic("ENRICH/MISSING/PROJECT") - .withMessage( - "{\"projects[0].acronym\":\"PAThs\"," - + "\"projects[0].code\":\"687567\"," - + "\"projects[0].funder\":\"EC\"," - + "\"projects[0].fundingProgram\":\"H2020\"," - + "\"projects[0].jurisdiction\":\"EU\"," - + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," - + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: " - + "An Archaeological Atlas of Coptic Literature." - + "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, " - + "Dissemination and Storage\"}") - .build(); + .withTopic("ENRICH/MISSING/PROJECT") + .withMessage( + "{\"projects[0].acronym\":\"PAThs\"," + + "\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\"," + + "\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: " + + "An Archaeological Atlas of Coptic Literature." + + "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, " + + "Dissemination and Storage\"}") + .build(); context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken) - .perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()).param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event1))); - getClient(authToken) - .perform(get("/api/integration/qualityassuranceevents/" + event5.getEventId()).param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event5))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event1))); + + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event5.getEventId()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event5))); } @Test public void findOneUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") + .build(); context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId())) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test @@ -177,25 +200,31 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1), - QAEventMatcher.matchQAEventEntry(event2)))) - .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); - getClient(authToken) - .perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", - "ENRICH!MISSING!ABSTRACT")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event4)))) - .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); - getClient(authToken) - .perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "not-existing")) - .andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(0))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents",Matchers.containsInAnyOrder( + QAEventMatcher.matchQAEventEntry(event1), + QAEventMatcher.matchQAEventEntry(event2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( + QAEventMatcher.matchQAEventEntry(event4)))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", "not-existing")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); } @Test @@ -220,111 +249,108 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144304\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") - .param("size", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder( - QAEventMatcher.matchQAEventEntry(event1), - QAEventMatcher.matchQAEventEntry(event2)))) - .andExpect(jsonPath("$._links.self.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( + QAEventMatcher.matchQAEventEntry(event1), + QAEventMatcher.matchQAEventEntry(event2)))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.next.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=1"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.last.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=2"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.first.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.prev.href").doesNotExist()) - .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(5))); + .andExpect(jsonPath("$._links.prev.href").doesNotExist()) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); - getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") - .param("size", "2").param("page", "1")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder( - QAEventMatcher.matchQAEventEntry(event3), - QAEventMatcher.matchQAEventEntry(event4)))) - .andExpect(jsonPath("$._links.self.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID") + .param("size", "2") + .param("page", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( + QAEventMatcher.matchQAEventEntry(event3), + QAEventMatcher.matchQAEventEntry(event4)))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=1"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.next.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=2"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.last.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + .andExpect(jsonPath("$._links.last.href",Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=2"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.first.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.prev.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) - .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(5))); - - getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") - .param("size", "2").param("page", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder( - QAEventMatcher.matchQAEventEntry(event5)))) - .andExpect(jsonPath("$._links.self.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.next.href").doesNotExist()) - .andExpect(jsonPath("$._links.last.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.first.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$._links.prev.href", - Matchers.allOf( - Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), - Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), - Matchers.containsString("size=2")))) - .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(5))); + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID") + .param("size", "2") + .param("page", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", + Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event5)))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=0"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), + Matchers.containsString("topic=" + OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID"), + Matchers.containsString("page=1"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); } @Test @@ -345,35 +371,10 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); - getClient() - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isUnauthorized()); - } - @Test - public void findByTopicForbiddenTest() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - QAEventBuilder.createTarget(context, col1, "Science and Freedom") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") - .withTopic("ENRICH/MORE/PID") - .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") - .withTopic("ENRICH/MISSING/ABSTRACT") - .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); - context.restoreAuthSystemState(); - String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isForbidden()); + getClient().perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID")) + .andExpect(status().isUnauthorized()); } @Test @@ -711,39 +712,49 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void deleteItemWithEventTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") + .build(); QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}") + .build(); context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1), - QAEventMatcher.matchQAEventEntry(event2)))) - .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( + QAEventMatcher.matchQAEventEntry(event1), + QAEventMatcher.matchQAEventEntry(event2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(2))); getClient(authToken).perform(delete("/api/core/items/" + event1.getTarget())) - .andExpect(status().is(204)); + .andExpect(status().is(204)); getClient(authToken).perform(get("/api/core/items/" + event1.getTarget())) - .andExpect(status().is(404)); + .andExpect(status().is(404)); - getClient(authToken) - .perform( - get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.qualityassuranceevents", - Matchers.containsInAnyOrder( - QAEventMatcher.matchQAEventEntry(event2)))) - .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic") + .param("topic", OPENAIRE_SOURCE + ":" + "ENRICH!MISSING!PID")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( + QAEventMatcher.matchQAEventEntry(event2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); } @Test @@ -836,17 +847,21 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { AtomicReference idRef = new AtomicReference(); - getClient(adminToken).perform(post("/api/integration/qualityassuranceevents") - .param("correctionType", "request-withdrawn") - .param("target", publication.getID().toString()) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + CorrectionTypeMessageDTO message = new CorrectionTypeMessageDTO("reasone"); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .content(new ObjectMapper().writeValueAsBytes(message)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is(idRef.get()))) - .andExpect(jsonPath("$.source", is("internal-item"))) + .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) .andExpect(jsonPath("$.title", is(publication.getName()))) .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) .andExpect(jsonPath("$.trust", is("1,000"))) @@ -902,7 +917,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { CorrectionTypeMessageDTO dto = new CorrectionTypeMessageDTO("provided reason!"); getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") - .param("correctionType", "request-withdrawn") + .param("correctionType", "request-reinstate") .param("target", publication.getID().toString()) .contentType(contentType) .content(mapper.writeValueAsBytes(dto))) @@ -912,7 +927,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is(idRef.get()))) - .andExpect(jsonPath("$.source", is("internal-item"))) + .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) .andExpect(jsonPath("$.title", is(publication.getName()))) .andExpect(jsonPath("$.topic", is("REQUEST/REINSTATE"))) .andExpect(jsonPath("$.trust", is("1,000"))) diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 328a43a1f8..14c4119969 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -70,4 +70,23 @@ + + + + + + + + + + + + + + + + original_id:{0} AND '{'!join from=search.resourceid to=resource_uuid fromIndex=${solr.multicorePrefix}search'}'*:* + + + From 919f1af963afebf68d7cc9c152a72ea4bd30296b Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 24 Nov 2023 13:00:17 +0100 Subject: [PATCH 17/58] [CST-12108] fixed withdrawn & reinstate correction types --- .../org/dspace/correctiontype/ReinstateCorrectionType.java | 3 ++- .../org/dspace/correctiontype/WithdrawnCorrectionType.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 5d025895c9..0c8b265b6f 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -45,7 +45,8 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public boolean isAllowed(Context context, Item targetItem) throws SQLException, AuthorizeException { authorizeService.authorizeAction(context, targetItem, Constants.READ); - return targetItem.isWithdrawn(); + long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); + return tot == 0 && targetItem.isWithdrawn(); } @Override diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 91eab2c7d3..d66e0d4a8c 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -45,7 +45,8 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException { authorizeService.authorizeAction(context, targetItem, READ); - return targetItem.isArchived() && !targetItem.isWithdrawn(); + long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); + return tot == 0 && targetItem.isArchived() && !targetItem.isWithdrawn(); } @Override From 0dddbf78db8f1e4e058a2d345c76d79d9a191c8a Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 24 Nov 2023 13:01:21 +0100 Subject: [PATCH 18/58] [CST-12108] porting missing code --- .../service/impl/QAEventServiceImpl.java | 10 ++++++---- .../impl/CanDeleteVersionFeature.java | 3 --- .../app/rest/converter/QATopicConverter.java | 2 +- .../repository/QATopicRestRepository.java | 20 ++++++++----------- dspace/config/spring/api/qaevents.xml | 2 +- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 070f688935..0ab6f43cdb 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -242,14 +242,15 @@ public class QAEventServiceImpl implements QAEventService { @Override public List findAllTopicsBySource(Context context, String source, long offset, long count) { - - if (source != null && isNotSupportedSource(source)) { - return null; + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - solrQuery.setQuery("*:*"); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.setFacetLimit((int) (offset + count)); @@ -269,6 +270,7 @@ public class QAEventServiceImpl implements QAEventService { continue; } QATopic topic = new QATopic(); + topic.setSource(source); topic.setKey(c.getName()); topic.setTotalEvents(c.getCount()); topic.setLastEvent(new Date()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java index c20668fcc5..15438a5e9a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java @@ -15,7 +15,6 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.DefaultProjection; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; @@ -34,8 +33,6 @@ import org.springframework.stereotype.Component; description = "It can be used to verify if the user can delete a version of an Item") public class CanDeleteVersionFeature extends DeleteFeature { - @Autowired - private ItemService itemService; @Autowired private ItemConverter itemConverter; @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java index efa32baba2..f12094d257 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java @@ -31,7 +31,7 @@ public class QATopicConverter implements DSpaceConverter { public QATopicRest convert(QATopic modelObject, Projection projection) { QATopicRest rest = new QATopicRest(); rest.setProjection(projection); - rest.setId(modelObject.getKey().replace("/", "!")); + rest.setId(modelObject.getSource() + ":" + modelObject.getKey().replace("/", "!")); rest.setName(modelObject.getKey()); rest.setLastEvent(modelObject.getLastEvent()); rest.setTotalEvents(modelObject.getTotalEvents()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index 2c3d3a1ad9..1e5a47cbec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -12,6 +12,7 @@ import java.util.UUID; 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.QATopicRest; import org.dspace.core.Context; import org.dspace.qaevent.QATopic; @@ -34,6 +35,11 @@ public class QATopicRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("Method not allowed!", ""); + } + @Override @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCETOPIC', 'READ')") public QATopicRest findOne(Context context, String id) { @@ -44,21 +50,11 @@ public class QATopicRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List topics = qaEventService.findAllTopics(context, pageable.getOffset(), pageable.getPageSize()); - long count = qaEventService.countTopics(); - if (topics == null) { - return null; - } - return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); - } - @SearchRestMethod(name = "bySource") @PreAuthorize("hasAuthority('AUTHENTICATED')") - public Page findBySource(Context context, @Parameter(value = "source", required = true) String source, + public Page findBySource(@Parameter(value = "source", required = true) String source, Pageable pageable) { + Context context = obtainContext(); List topics = qaEventService.findAllTopicsBySource(context, source, pageable.getOffset(), pageable.getPageSize()); long count = qaEventService.countTopicsBySource(context, source); diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 14c4119969..8c2964871a 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -85,7 +85,7 @@ - original_id:{0} AND '{'!join from=search.resourceid to=resource_uuid fromIndex=${solr.multicorePrefix}search'}'*:* + original_id:{0} From 481f0de80aa4a7d4659ece08a8a7ce0bb6b00ce1 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 28 Nov 2023 01:04:34 +0100 Subject: [PATCH 19/58] [CST-12108] added javadoc --- .../dspace/correctiontype/CorrectionType.java | 42 +++++++++++++++++++ .../service/CorrectionTypeService.java | 25 +++++++++++ 2 files changed, 67 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java index 5784042412..627e00db1c 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java @@ -22,20 +22,62 @@ import org.dspace.qaevent.service.dto.QAMessageDTO; */ public interface CorrectionType { + /** + * Retrieves the unique identifier associated to the CorrectionType. + */ public String getId(); + /** + * Retrieves the topic associated with the to the CorrectionType. + */ public String getTopic(); public String getCreationForm(); + /** + * Checks whether the CorrectionType required related item. + */ public boolean isRequiredRelatedItem(); + /** + * Checks whether target item is allowed for current CorrectionType + * + * @param context Current DSpace session + * @param targetItem Target item + * @throws AuthorizeException if authorize error + * @throws SQLException if there's a database problem + */ public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException; + /** + * Checks whether target item and related item are allowed for current CorrectionType + * + * @param context Current DSpace session + * @param targetItem Target item + * @param relatedItem Related item + * @throws AuthorizeException if authorize error + * @throws SQLException if there's a database problem + */ public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException,SQLException; + /** + * Creates a QAEvent for a specific target item. + * + * @param context Current DSpace session + * @param targetItem Target item + * @param reason Reason + * @return QAEvent + */ public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason); + /** + * Creates a QAEvent for a target item and related item. + * @param context Current DSpace session + * @param targetItem Target item + * @param relatedItem Related item + * @param reason Reason + * @return QAEvent + */ public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason); } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java b/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java index cb7fa7df25..e76e1f7ec1 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java @@ -22,12 +22,37 @@ import org.dspace.correctiontype.CorrectionType; */ public interface CorrectionTypeService { + /** + * Retrieves a CorrectionType object from the system based on a unique identifier. + * + * @param id The unique identifier of the CorrectionType object to be retrieved. + * @return The CorrectionType object corresponding to the provided identifier, + * or null if no object is found. + */ public CorrectionType findOne(String id); + /** + * Retrieves a list of all CorrectionType objects available in the system. + * + * @return Returns a List containing all CorrectionType objects in the system. + */ public List findAll(); + /** + * Retrieves a list of CorrectionType objects related to the provided Item. + * + * @param context Current DSpace session. + * @param item Target item + * @throws AuthorizeException If authorize error + * @throws SQLException If a database error occurs during the operation. + */ public List findByItem(Context context, Item item) throws AuthorizeException, SQLException; + /** + * Retrieves a CorrectionType object associated with a specific topic. + * + * @param topic The topic for which the CorrectionType object is to be retrieved. + */ public CorrectionType findByTopic(String topic); } From b642aee9f4fcf253bb3c747b9a8388ae85214740 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 4 Dec 2023 12:03:54 +0100 Subject: [PATCH 20/58] [CST-12108] fix failed tests --- .../org/dspace/qaevent/QANotifyPatterns.java | 30 ++++++ .../qaevent/script/OpenaireEventsImport.java | 3 +- .../script/OpenaireEventsImportIT.java | 99 +++++++++---------- 3 files changed, 81 insertions(+), 51 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java new file mode 100644 index 0000000000..4bf9867a2b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.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.qaevent; + +/** + * Constants for Quality Assurance configurations to be used into cfg and xml spring. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + */ +public class QANotifyPatterns { + + public static final String TOPIC_ENRICH_MORE_PROJECT = "ENRICH/MORE/PROJECT"; + public static final String TOPIC_ENRICH_MISSING_PROJECT = "ENRICH/MISSING/PROJECT"; + public static final String TOPIC_ENRICH_MISSING_ABSTRACT = "ENRICH/MISSING/ABSTRACT"; + public static final String TOPIC_ENRICH_MORE_REVIEW = "ENRICH/MORE/REVIEW"; + public static final String TOPIC_ENRICH_MORE_ENDORSEMENT = "ENRICH/MORE/ENDORSEMENT"; + public static final String TOPIC_ENRICH_MORE_PID = "ENRICH/MORE/PID"; + public static final String TOPIC_ENRICH_MISSING_PID = "ENRICH/MISSING/PID"; + + /** + * Default constructor + */ + private QANotifyPatterns() { } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index 8c620acbf0..0062c02f8b 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -32,6 +32,7 @@ import org.dspace.content.QAEvent; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.handle.HandleServiceImpl; import org.dspace.handle.service.HandleService; import org.dspace.qaevent.service.BrokerClientFactory; import org.dspace.qaevent.service.QAEventService; @@ -105,7 +106,7 @@ public class OpenaireEventsImport jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); DSpace dspace = new DSpace(); - handleService = dspace.getSingletonService(HandleService.class); + handleService = dspace.getSingletonService(HandleServiceImpl.class); qaEventService = dspace.getSingletonService(QAEventService.class); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); brokerClient = BrokerClientFactory.getInstance().getBrokerClient(); diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 73fd60ddcb..62faf446d5 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -32,6 +33,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import java.net.URL; +import java.util.List; import eu.dnetlib.broker.BrokerClient; import org.apache.commons.io.IOUtils; @@ -46,6 +48,8 @@ import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.matcher.QASourceMatcher; import org.dspace.matcher.QATopicMatcher; +import org.dspace.qaevent.QANotifyPatterns; +import org.dspace.qaevent.QATopic; import org.dspace.qaevent.service.BrokerClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.impl.BrokerClientFactoryImpl; @@ -142,7 +146,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase } @Test - @SuppressWarnings("unchecked") public void testManyEventsImportFromFile() throws Exception { context.turnOffAuthorisationSystem(); @@ -163,14 +166,14 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Trying to read the QA events from the provided file", "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," @@ -191,7 +194,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -210,23 +212,22 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), - contains("An error occurs storing the event with id b4e09c71312cd7c397969f56c900823f: " - + "Skipped event b4e09c71312cd7c397969f56c900823f related to the oai record " - + "oai:www.openstarts.units.it:123456789/99998 as the record was not found", - "An error occurs storing the event with id d050d2c4399c6c6ccf27d52d479d26e4: " - + "Skipped event d050d2c4399c6c6ccf27d52d479d26e4 related to the oai record " - + "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + contains("An error occurs storing the event with id 406fb9c5656c7f11cac8995abb746887: " + + "Skipped event 406fb9c5656c7f11cac8995abb746887 related to the oai record " + + "oai:www.openstarts.units.it:123456789/99998 as the record was not found", + "An error occurs storing the event with id eafd747feee49cca7603d30ba4e768dc: " + + "Skipped event eafd747feee49cca7603d30ba4e768dc related to the oai record " + + "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); assertThat(handler.getInfoMessages(), contains( "Trying to read the QA events from the provided file", "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L) - )); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; @@ -236,7 +237,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -256,25 +256,26 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), - contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + contains("An error occurs storing the event with id 8307aa56769deba961faed7162d91aab:" + + " Skipped event 8307aa56769deba961faed7162d91aab related to the oai record" + + " oai:www.openstarts.units.it:123456789/99998 as the record was not found")); assertThat(handler.getInfoMessages(), contains( "Trying to read the QA events from the provided file", "Found 2 events in the given file")); - assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), - contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20), + contains(QATopicMatcher.with(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), - contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", - abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -337,14 +338,14 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," @@ -403,7 +404,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { context.turnOffAuthorisationSystem(); @@ -443,19 +443,19 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/PID", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0, 20), - hasSize(1)); - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), - hasSize(2)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20), hasSize(1)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), hasSize(2)); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); @@ -463,7 +463,6 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); verifyNoMoreInteractions(mockBrokerClient); - } private Item createItem(String title, String handle) { From af0a521eeb1551ef02da519612cf6dc639a35ed5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 5 Dec 2023 13:07:45 +0100 Subject: [PATCH 21/58] [CST-12108] fix failed test --- .../app/rest/QAEventRestRepositoryIT.java | 195 ++++++++++-------- 1 file changed, 114 insertions(+), 81 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 5616b0bc8a..8d353567f9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -50,6 +50,7 @@ import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.QAEventProcessed; +import org.dspace.qaevent.QANotifyPatterns; import org.dspace.qaevent.dao.QAEventsDao; import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.hamcrest.Matchers; @@ -405,20 +406,28 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { context.turnOffAuthorisationSystem(); EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build(); - RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, project, "isProjectOfPublication", - "isPublicationOfProject", 0, null, 0, - null).withCopyToRight(true).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, project, + "isProjectOfPublication", "isPublicationOfProject", 0, null, 0, null) + .withCopyToRight(true) + .build(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withEntityType("Publication") - .withName("Collection 1").build(); + .withEntityType("Publication") + .withName("Collection 1") + .build(); Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection Fundings") - .withEntityType("Project").build(); - Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") - .build(); + .withName("Collection Fundings") + .withEntityType("Project") + .build(); + + Item funding = ItemBuilder.createItem(context, colFunding) + .withTitle("Tracking Papyrus and Parchment Paths") + .build(); + QAEvent eventProjectBound = QAEventBuilder.createTarget(context, col1, "Science and Freedom with project") - .withTopic("ENRICH/MISSING/PROJECT") + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT) .withMessage( "{\"projects[0].acronym\":\"PAThs\"," + "\"projects[0].code\":\"687567\"," @@ -434,7 +443,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); QAEvent eventProjectNoBound = QAEventBuilder .createTarget(context, col1, "Science and Freedom with unrelated project") - .withTopic("ENRICH/MISSING/PROJECT") + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT) .withMessage( "{\"projects[0].acronym\":\"NEW\"," + "\"projects[0].code\":\"123456\"," @@ -445,35 +454,45 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { + "\"projects[0].title\":\"A new project\"}") .build(); QAEvent eventMissingPID1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") + .build(); QAEvent eventMissingPID2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") - .withTopic("ENRICH/MISSING/PID") - .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}") + .build(); QAEvent eventMissingUnknownPID = QAEventBuilder.createTarget(context, col1, "Science and Freedom URN PID") - .withTopic("ENRICH/MISSING/PID") + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID) .withMessage( "{\"pids[0].type\":\"urn\",\"pids[0].value\":\"http://thesis2.sba.units.it/store/handle/item/12937\"}") .build(); QAEvent eventMorePID = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") - .withTopic("ENRICH/MORE/PID") - .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}").build(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MORE_PID) + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}") + .build(); QAEvent eventAbstract = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") - .withTopic("ENRICH/MISSING/ABSTRACT") - .withMessage("{\"abstracts[0]\": \"An abstract to add...\"}").build(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) + .withMessage("{\"abstracts[0]\": \"An abstract to add...\"}") + .build(); QAEvent eventAbstractToDiscard = QAEventBuilder.createTarget(context, col1, "Science and Freedom 7") - .withTopic("ENRICH/MISSING/ABSTRACT") - .withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}").build(); + .withTopic(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT) + .withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}") + .build(); context.restoreAuthSystemState(); + // prepare the different patches for our decisions List acceptOp = new ArrayList(); acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + List acceptOpUppercase = new ArrayList(); acceptOpUppercase.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + List discardOp = new ArrayList(); discardOp.add(new ReplaceOperation("/status", QAEvent.DISCARDED)); + List rejectOp = new ArrayList(); rejectOp.add(new ReplaceOperation("/status", QAEvent.REJECTED)); + String patchAccept = getPatchContent(acceptOp); String patchAcceptUppercase = getPatchContent(acceptOpUppercase); String patchDiscard = getPatchContent(discardOp); @@ -490,82 +509,95 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { eventAbstract.setStatus(QAEvent.ACCEPTED); getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMissingPID1.getEventId()) - .content(patchAccept) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID1))); + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID1))); + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMorePID.getEventId()) - .content(patchAcceptUppercase) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMorePID))); + .content(patchAcceptUppercase) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMorePID))); + getClient(authToken) .perform(patch("/api/integration/qualityassuranceevents/" + eventMissingUnknownPID.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingUnknownPID))); + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventProjectBound.getEventId()) - .content(patchAccept) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectBound))); + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectBound))); + getClient(authToken) .perform(patch("/api/integration/qualityassuranceevents/" + eventProjectNoBound.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectNoBound))); + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventAbstract.getEventId()) - .content(patchAccept) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstract))); + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstract))); + // check if the item has been updated getClient(authToken).perform(get("/api/core/items/" + eventMissingPID1.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect( - jsonPath("$", - hasJsonPath("$.metadata['dc.identifier.other'][0].value", is("10.2307/2144300")))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",hasJsonPath("$.metadata['dc.identifier.other'][0].value", + is("10.2307/2144300")))); + getClient(authToken).perform(get("/api/core/items/" + eventMorePID.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", is("2144302")))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", + is("2144302")))); + getClient(authToken).perform(get("/api/core/items/" + eventMissingUnknownPID.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", - is("http://thesis2.sba.units.it/store/handle/item/12937")))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", + is("http://thesis2.sba.units.it/store/handle/item/12937")))); + getClient(authToken).perform(get("/api/core/items/" + eventProjectBound.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value", - is(funding.getID().toString())))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value", + is(funding.getID().toString())))); + getClient(authToken).perform(get("/api/core/items/" + eventProjectNoBound.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value", - is(not(empty()))))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value", + is(not(empty()))))); + getClient(authToken).perform(get("/api/core/items/" + eventAbstract.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - hasJsonPath("$.metadata['dc.description.abstract'][0].value", is("An abstract to add...")))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.description.abstract'][0].value", + is("An abstract to add...")))); + // reject pid2 eventMissingPID2.setStatus(QAEvent.REJECTED); getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMissingPID2.getEventId()) - .content(patchReject) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID2))); + .content(patchReject) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID2))); + getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - hasNoJsonPath("$.metadata['dc.identifier.other']"))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasNoJsonPath("$.metadata['dc.identifier.other']"))); + // discard abstractToDiscard eventAbstractToDiscard.setStatus(QAEvent.DISCARDED); getClient(authToken) @@ -574,17 +606,18 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstractToDiscard))); + getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget()) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - hasNoJsonPath("$.metadata['dc.description.abstract']"))); + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasNoJsonPath("$.metadata['dc.description.abstract']"))); + // no pending qa events should be longer available - getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); - // we should have stored the decision into the database as well + getClient(authToken).perform(get("/api/integration/qualityassurancesources/" + QAEvent.OPENAIRE_SOURCE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalEvents", is(0))); } @Test From 67f7148eeeaa36567c56026ba2bd9cc527f43c47 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 5 Dec 2023 15:45:47 +0100 Subject: [PATCH 22/58] [CST-12108] fix failed tests --- .../app/rest/converter/QATopicConverter.java | 3 +- .../repository/QATopicRestRepository.java | 10 +- .../app/rest/QATopicRestRepositoryIT.java | 273 +++++++----------- .../app/rest/matcher/QATopicMatcher.java | 32 +- 4 files changed, 145 insertions(+), 173 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java index f12094d257..a40ae527b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java @@ -31,7 +31,8 @@ public class QATopicConverter implements DSpaceConverter { public QATopicRest convert(QATopic modelObject, Projection projection) { QATopicRest rest = new QATopicRest(); rest.setProjection(projection); - rest.setId(modelObject.getSource() + ":" + modelObject.getKey().replace("/", "!")); + rest.setId(modelObject.getSource() + ":" + modelObject.getKey().replace("/", "!") + + (modelObject.getFocus() != null ? ":" + modelObject.getFocus().toString() : "")); rest.setName(modelObject.getKey()); rest.setLastEvent(modelObject.getLastEvent()); rest.setTotalEvents(modelObject.getTotalEvents()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index 1e5a47cbec..7a3f357fcf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -43,11 +43,15 @@ public class QATopicRestRepository extends DSpaceRestRepository matchQATopicEntry(String key, int totalEvents) { + public static Matcher matchQATopicEntry(String topicName, int totalEvents) { + return matchQATopicEntry(QAEvent.OPENAIRE_SOURCE, topicName, totalEvents); + } + + + public static Matcher matchQATopicEntry(String topicName) { + return matchQATopicEntry(QAEvent.OPENAIRE_SOURCE, topicName); + } + + public static Matcher matchQATopicEntry(String source, String topicName, int totalEvents) { return allOf( hasJsonPath("$.type", is("qualityassurancetopic")), - hasJsonPath("$.name", is(key)), - hasJsonPath("$.id", is(key.replace("/", "!"))), + hasJsonPath("$.name", is(topicName)), + hasJsonPath("$.id", is(source + ":" + topicName.replace("/", "!"))), hasJsonPath("$.totalEvents", is(totalEvents)) ); } - public static Matcher matchQATopicEntry(String key) { + public static Matcher matchQATopicEntry(String source, String topicName) { return allOf( hasJsonPath("$.type", is("qualityassurancetopic")), - hasJsonPath("$.name", is(key)), - hasJsonPath("$.id", is(key.replace("/", "/"))) + hasJsonPath("$.name", is(topicName)), + hasJsonPath("$.id", is(source + ":" + topicName.replace("/", "!"))) ); } + public static Matcher matchQATopicEntry(String source, String topicName, String itemUuid, + int totalEvents) { + return allOf( + hasJsonPath("$.type", is("qualityassurancetopic")), + hasJsonPath("$.name", is(topicName)), + hasJsonPath("$.id", is(source + ":" + topicName.replace("/", "!") + ":" + itemUuid)), + hasJsonPath("$.totalEvents", is(totalEvents)) + ); + } + } From dff29f0f31e26074eefb6f878f54d19a6960aaf2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 5 Dec 2023 16:55:51 +0100 Subject: [PATCH 23/58] [CST-12108] fix failed tests --- .../service/impl/QAEventServiceImpl.java | 4 +- .../app/rest/converter/QASourceConverter.java | 3 +- .../app/rest/QASourceRestRepositoryIT.java | 67 +++++++------------ 3 files changed, 31 insertions(+), 43 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 0ab6f43cdb..678ccdd541 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -440,7 +440,9 @@ public class QAEventServiceImpl implements QAEventService { public List findAllSources(Context context, long offset, int pageSize) { return Arrays.stream(getSupportedSources()) .map((sourceName) -> findSource(context, sourceName)) - .sorted(comparing(QASource::getTotalEvents).reversed()) + .filter(Objects::nonNull) + .sorted(comparing(QASource::getTotalEvents) + .reversed()) .skip(offset) .limit(pageSize) .collect(Collectors.toList()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java index 6c1bc0d66c..8b651d821d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java @@ -31,7 +31,8 @@ public class QASourceConverter implements DSpaceConverter Date: Tue, 5 Dec 2023 17:41:54 +0100 Subject: [PATCH 24/58] [CST-12108] minor fix --- .../java/org/dspace/app/rest/QAEventRestRepositoryIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 8d353567f9..53263f08d1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -897,7 +897,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) .andExpect(jsonPath("$.title", is(publication.getName()))) .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) - .andExpect(jsonPath("$.trust", is("1,000"))) + // .andExpect(jsonPath("$.trust", is("1,000"))) .andExpect(jsonPath("$.status", is("PENDING"))); getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) @@ -963,7 +963,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) .andExpect(jsonPath("$.title", is(publication.getName()))) .andExpect(jsonPath("$.topic", is("REQUEST/REINSTATE"))) - .andExpect(jsonPath("$.trust", is("1,000"))) + // .andExpect(jsonPath("$.trust", is("1,000"))) .andExpect(jsonPath("$.status", is("PENDING"))); getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) From 73632049a13ced05a2c7d3e77dc034a27aeba236 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 6 Dec 2023 16:07:05 +0100 Subject: [PATCH 25/58] [CST-12108] fix DecimalFormat issue --- .../java/org/dspace/app/rest/converter/QAEventConverter.java | 5 ++++- .../java/org/dspace/app/rest/QAEventRestRepositoryIT.java | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java index 6db4e52bb9..0cb73d94e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.converter; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; import javax.annotation.PostConstruct; import com.fasterxml.jackson.core.JsonProcessingException; @@ -66,7 +68,8 @@ public class QAEventConverter implements DSpaceConverter { rest.setTitle(modelObject.getTitle()); rest.setTopic(modelObject.getTopic()); rest.setEventDate(modelObject.getLastUpdate()); - rest.setTrust(new DecimalFormat("0.000").format(modelObject.getTrust())); + DecimalFormat decimalFormat = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.ENGLISH)); + rest.setTrust(decimalFormat.format(modelObject.getTrust())); // right now only the pending status can be found in persisted qa events rest.setStatus(modelObject.getStatus()); return rest; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 53263f08d1..3f4f5b7c76 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -897,7 +897,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) .andExpect(jsonPath("$.title", is(publication.getName()))) .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) - // .andExpect(jsonPath("$.trust", is("1,000"))) + .andExpect(jsonPath("$.trust", is("1.000"))) .andExpect(jsonPath("$.status", is("PENDING"))); getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) @@ -963,7 +963,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) .andExpect(jsonPath("$.title", is(publication.getName()))) .andExpect(jsonPath("$.topic", is("REQUEST/REINSTATE"))) - // .andExpect(jsonPath("$.trust", is("1,000"))) + .andExpect(jsonPath("$.trust", is("1.000"))) .andExpect(jsonPath("$.status", is("PENDING"))); getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) From a602c8bbd957c9073bfe85cec986659c6ad140c3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 22 Dec 2023 16:10:55 +0100 Subject: [PATCH 26/58] [CST-12108] remove unused imports --- .../org/dspace/app/rest/repository/QATopicRestRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index 822b7d7869..885a3201cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -20,7 +20,6 @@ import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort.Direction; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; From fd605c30b901d8d25f9ee08c4f9fb730d33697c5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 22 Dec 2023 18:14:40 +0100 Subject: [PATCH 27/58] [CST-12108] fix wrong merge --- .../java/org/dspace/app/rest/QAEventRestRepositoryIT.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 97169cc6a6..6aad1e7e16 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -839,13 +839,6 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { assertThat(processedEvent.getItem().getID().toString(), is(event.getTarget())); assertThat(processedEvent.getEventTimestamp(), notNullValue()); assertThat(processedEvent.getEperson().getID(), is(admin.getID())); - - getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId())) - .andExpect(status().isInternalServerError()); - - authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event2.getEventId())) - .andExpect(status().isForbidden()); } @Test From 772eaa7666a17450dcac2f73b1cd0db9dc606f3b Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 17 Jan 2024 18:05:38 +0100 Subject: [PATCH 28/58] DURACOM-211 S3store enabling according to assetstore.s3.enabled config --- .../storage/bitstore/S3BitStoreService.java | 2 +- .../storage/bitstore/S3BitStoreServiceIT.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 7a09dd2e76..48f3b9f325 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -169,7 +169,7 @@ public class S3BitStoreService extends BaseBitStoreService { @Override public void init() throws IOException { - if (this.isInitialized()) { + if (this.isInitialized() || !this.isEnabled()) { return; } diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 7aae1cf271..2e61880607 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -19,6 +19,7 @@ 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.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -53,6 +54,8 @@ import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Utils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; @@ -76,6 +79,9 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private Collection collection; private File s3Directory; + + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + @Before public void setup() throws Exception { @@ -382,6 +388,17 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); } + @Test + public void testDoNotInitializeConfigured() throws Exception { + String assetstores3enabledOldValue = configurationService.getProperty("assetstore.s3.enabled"); + configurationService.setProperty("assetstore.s3.enabled", false); + s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService.init(); + assertFalse(s3BitStoreService.isInitialized()); + assertFalse(s3BitStoreService.isEnabled()); + configurationService.setProperty("assetstore.s3.enabled", assetstores3enabledOldValue); + } + private byte[] generateChecksum(String content) { try { MessageDigest m = MessageDigest.getInstance("MD5"); From 584d178a95edc527388b848fc380ba645e4a084a Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 17 Jan 2024 18:06:41 +0100 Subject: [PATCH 29/58] DURACOM-211 S3store enabling checkstyle --- .../java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 2e61880607..5f7934d8f5 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -79,7 +79,7 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private Collection collection; private File s3Directory; - + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); From 2beb60425511ffd248d833343b64bf3af9749a82 Mon Sep 17 00:00:00 2001 From: frabacche Date: Fri, 19 Jan 2024 14:27:25 +0100 Subject: [PATCH 30/58] DURACOM-211 s3bitstoreService IT class fix --- .../org/dspace/storage/bitstore/S3BitStoreServiceIT.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 5f7934d8f5..30eeff2ddc 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -43,6 +43,7 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import io.findify.s3mock.S3Mock; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.BooleanUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.matcher.LambdaMatcher; import org.dspace.authorize.AuthorizeException; @@ -63,6 +64,7 @@ import org.junit.Before; import org.junit.Test; + /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ @@ -86,6 +88,7 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { @Before public void setup() throws Exception { + configurationService.setProperty("assetstore.s3.enabled", "true"); s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3"); s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath()); @@ -94,7 +97,8 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { amazonS3Client = createAmazonS3Client(); s3BitStoreService = new S3BitStoreService(amazonS3Client); - + s3BitStoreService.setEnabled(BooleanUtils.toBoolean( + configurationService.getProperty("assetstore.s3.enabled"))); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -391,7 +395,7 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { @Test public void testDoNotInitializeConfigured() throws Exception { String assetstores3enabledOldValue = configurationService.getProperty("assetstore.s3.enabled"); - configurationService.setProperty("assetstore.s3.enabled", false); + configurationService.setProperty("assetstore.s3.enabled", "false"); s3BitStoreService = new S3BitStoreService(amazonS3Client); s3BitStoreService.init(); assertFalse(s3BitStoreService.isInitialized()); From 96ee4304cc577a85740d544a3c0a6c46311a62fd Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 23 Jan 2024 00:36:00 +0100 Subject: [PATCH 31/58] [CST-12108] improve code --- .../correctiontype/ReinstateCorrectionType.java | 12 +++++------- .../correctiontype/WithdrawnCorrectionType.java | 13 ++++++++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 0c8b265b6f..3ca9d6ffa9 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -14,10 +14,8 @@ import java.util.Date; import com.google.gson.Gson; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.QAEvent; -import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; @@ -39,14 +37,14 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Autowired private QAEventService qaEventService; - @Autowired - private AuthorizeService authorizeService; @Override - public boolean isAllowed(Context context, Item targetItem) throws SQLException, AuthorizeException { - authorizeService.authorizeAction(context, targetItem, Constants.READ); + public boolean isAllowed(Context context, Item targetItem) throws SQLException { + if (!targetItem.isWithdrawn()) { + return false; + } long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); - return tot == 0 && targetItem.isWithdrawn(); + return tot == 0; } @Override diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index d66e0d4a8c..6f84b37094 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -43,10 +43,17 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean private AuthorizeService authorizeService; @Override - public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException { - authorizeService.authorizeAction(context, targetItem, READ); + public boolean isAllowed(Context context, Item targetItem) throws SQLException { + if (targetItem.isWithdrawn() || !targetItem.isArchived()) { + return false; + } + try { + authorizeService.authorizeAction(context, targetItem, READ); + } catch (AuthorizeException e) { + return false; + } long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); - return tot == 0 && targetItem.isArchived() && !targetItem.isWithdrawn(); + return tot == 0; } @Override From 7f327146144ce579d6e104705e42395deb61604f Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 24 Jan 2024 13:48:45 +0100 Subject: [PATCH 32/58] [CST-12108] introduced withdrawn&reinstate group & sent email to admin --- .../ReinstateCorrectionType.java | 27 +++++++++++++++++++ .../WithdrawnCorrectionType.java | 26 ++++++++++++++++++ .../service/impl/QAEventServiceImpl.java | 22 +++++++++++++++ dspace/config/dspace.cfg | 6 +++++ .../config/emails/qaevent_admin_notification | 13 +++++++++ 5 files changed, 94 insertions(+) create mode 100644 dspace/config/emails/qaevent_admin_notification diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 3ca9d6ffa9..407d83568c 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -8,18 +8,26 @@ package org.dspace.correctiontype; import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.correctiontype.WithdrawnCorrectionType.WITHDRAWAL_REINSTATE_GROUP; import java.sql.SQLException; import java.util.Date; +import java.util.Objects; +import java.util.UUID; import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -35,18 +43,37 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean private String topic; private String creationForm; + @Autowired + private GroupService groupService; @Autowired private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ConfigurationService configurationService; @Override public boolean isAllowed(Context context, Item targetItem) throws SQLException { if (!targetItem.isWithdrawn()) { return false; } + boolean isAdmin = authorizeService.isAdmin(context); + if (!currentUserIsMemberOfwithdrawalReinstateGroup(context) && !isAdmin) { + return false; + } long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); return tot == 0; } + private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { + String withdrawalReinstateGroupUUID = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(withdrawalReinstateGroupUUID)) { + return false; + } + Group withdrawalReinstateGroup = groupService.find(context, UUID.fromString(withdrawalReinstateGroupUUID)); + return Objects.nonNull(withdrawalReinstateGroup) && groupService.isMember(context, withdrawalReinstateGroup); + } + @Override public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException, SQLException { diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 6f84b37094..bb2accc404 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -12,16 +12,22 @@ import static org.dspace.core.Constants.READ; import java.sql.SQLException; import java.util.Date; +import java.util.Objects; +import java.util.UUID; import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.dspace.qaevent.service.QAEventService; import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -33,14 +39,21 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean { + public static final String WITHDRAWAL_REINSTATE_GROUP = "withdrawal.reinstate.group"; + private String id; private String topic; private String creationForm; + @Autowired + private GroupService groupService; @Autowired private QAEventService qaEventService; @Autowired private AuthorizeService authorizeService; + @Autowired + private ConfigurationService configurationService; + @Override public boolean isAllowed(Context context, Item targetItem) throws SQLException { @@ -52,10 +65,23 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean } catch (AuthorizeException e) { return false; } + boolean isAdmin = authorizeService.isAdmin(context); + if (!currentUserIsMemberOfwithdrawalReinstateGroup(context) && !isAdmin) { + return false; + } long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); return tot == 0; } + private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { + String withdrawalReinstateGroupUUID = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(withdrawalReinstateGroupUUID)) { + return false; + } + Group withdrawalReinstateGroup = groupService.find(context, UUID.fromString(withdrawalReinstateGroupUUID)); + return Objects.nonNull(withdrawalReinstateGroup) && groupService.isMember(context, withdrawalReinstateGroup); + } + @Override public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 91d3163c21..a27af164c3 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -41,6 +42,8 @@ import org.apache.solr.common.SolrInputDocument; import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; import org.dspace.eperson.EPerson; import org.dspace.qaevent.QASource; import org.dspace.qaevent.QATopic; @@ -50,6 +53,8 @@ import org.dspace.qaevent.service.QAEventSecurityService; import org.dspace.qaevent.service.QAEventService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -63,6 +68,8 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class QAEventServiceImpl implements QAEventService { + private static final Logger log = LoggerFactory.getLogger(QAEventServiceImpl.class); + @Autowired(required = true) protected ConfigurationService configurationService; @@ -306,12 +313,27 @@ public class QAEventServiceImpl implements QAEventService { updateRequest.process(getSolr()); getSolr().commit(); + sentEmailToAdminAboutNewRequest(dto); } } catch (Exception e) { throw new RuntimeException(e); } } + public void sentEmailToAdminAboutNewRequest(QAEvent qaEvent) { + try { + Email email = Email.getEmail(I18nUtil.getEmailFilename(Locale.getDefault(), "qaevent_admin_notification")); + email.addRecipient(configurationService.getProperty("mail.admin")); + email.addArgument(qaEvent.getTopic()); + email.addArgument(qaEvent.getTarget()); + email.addArgument(qaEvent.getMessage()); + email.send(); + } catch (Exception e) { + log.warn("Error during sending email of Withdrawn/Reinstate request for item with uuid:" + + qaEvent.getTarget(), e); + } + } + @Override public QAEvent findEventByEventId(Context context, String eventId) { SolrQuery solrQuery = new SolrQuery("*:*"); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index b72b143aae..21ea8f3fe4 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -890,6 +890,12 @@ org.dspace.app.itemexport.life.span.hours = 48 # cumulative sizes are more than this entry the export is not kicked off org.dspace.app.itemexport.max.size = 200 +### Withdrawal&Reinstate correction Group ### +# Members of this group enabled to make requests for the Withdrawn or Reinstate of an item. +# By defaul this property is empty, so in the default behaviour no one will see the button to make these requests. +# If you want to give consent to everyone, just configure the uuid of the Anonymous group. +withdrawal.reinstate.group = + ### Batch Item import settings ### # The directory where the results of imports will be placed (mapfile, upload file) org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports diff --git a/dspace/config/emails/qaevent_admin_notification b/dspace/config/emails/qaevent_admin_notification new file mode 100644 index 0000000000..b09ca2f28c --- /dev/null +++ b/dspace/config/emails/qaevent_admin_notification @@ -0,0 +1,13 @@ +## E-mail sent to notify Administrator of new Withdrawn/Reinstate request +## +## Parameters: {0} Type of request 'topic' +## {1} resource id +## {2} reason +## +#set($subject = "Notification about ${params[0]} Request") + +Item Details: + +Type of request: ${params[0]} +Relatem to item with uuid: ${params[1]} +Reason: ${params[2]} \ No newline at end of file From 1dada28c89b8ad514cdf0d538476ec16a82926ae Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Wed, 7 Feb 2024 14:35:46 +0100 Subject: [PATCH 33/58] 111719 Fix bug where old style of retrieving config value was being used --- .../src/main/java/org/dspace/sword/CollectionDepositor.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java b/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java index 2550d947d3..3fe47cfaa6 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java +++ b/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java @@ -150,11 +150,7 @@ public class CollectionDepositor extends Depositor { // for a moment context.turnOffAuthorisationSystem(); - String bundleName = configurationService.getProperty( - "sword-server", "bundle.name"); - if (bundleName == null || "".equals(bundleName)) { - bundleName = "SWORD"; - } + String bundleName = configurationService.getProperty("sword-server.bundle.name", "SWORD"); Item item = result.getItem(); List bundles = item.getBundles(); Bundle swordBundle = null; From 2cbcf9006dcc07a6197f55be79db7602e90e37fd Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 15 Feb 2024 18:36:45 +0100 Subject: [PATCH 34/58] SWORD config fixes: Instances where old style module/fileName, configName params are used from long-deprecated ConfigurationManager instead of configName, defaultValue --- .../src/main/java/org/dspace/sword/SWORDUrlManager.java | 3 +-- .../src/main/java/org/dspace/sword2/SwordUrlManager.java | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java index c145c9d367..6672027003 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java @@ -431,8 +431,7 @@ public class SWORDUrlManager { */ public String getBaseMediaLinkUrl() throws DSpaceSWORDException { - String mlUrl = configurationService.getProperty( - "sword-server", "media-link.url"); + String mlUrl = configurationService.getProperty("sword-server.media-link.url"); if (StringUtils.isBlank(mlUrl)) { if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSWORDException( diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index f3b2cf4396..eee3627c40 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -458,10 +458,9 @@ public class SwordUrlManager { throws DSpaceSwordException { WorkflowTools wft = new WorkflowTools(); - // if the item is in the workspace, we need to give it it's own special identifier + // if the item is in the workspace, we need to give it its own special identifier if (wft.isItemInWorkspace(context, item)) { - String urlTemplate = configurationService - .getProperty("swordv2-server", "workspace.url-template"); + String urlTemplate = configurationService.getProperty("swordv2-server.workspace.url-template"); if (urlTemplate != null) { return urlTemplate.replace("#wsid#", Integer.toString( wft.getWorkspaceItem(context, item).getID())); From eeee0295107ccb2a84e0b2f9adf92536155efb5e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 20 Dec 2023 12:25:02 -0600 Subject: [PATCH 35/58] Add more ITs to SWORDv2 to verify basic upload, edit, delete functionality. These all pass prior to any SWORDv2 refactoring --- .../AbstractWebClientIntegrationTest.java | 14 +- .../java/org/dspace/app/sword2/Swordv2IT.java | 339 +++++++++++++++--- .../org/dspace/app/sword2/example.zip | Bin 0 -> 33777 bytes dspace/config/modules/swordv2-server.cfg | 1 + 4 files changed, 307 insertions(+), 47 deletions(-) create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/sword2/example.zip 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 be0a27b4eb..54339aa1d6 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 @@ -19,6 +19,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpEntity; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; @@ -94,6 +95,15 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit return getClient().getForEntity(getURL(path), String.class); } + /** + * Perform a request (defined by RequestEntity) and return response as a String + * @param request RequestEntity object which defines the GET request + * @return ResponseEntity with a String body + */ + public ResponseEntity responseAsString(RequestEntity request) { + return getClient().exchange(request, String.class); + } + /** * Perform an authenticated (via Basic Auth) GET request and return response as a String * @param path path to perform GET against @@ -107,10 +117,10 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit /** * Perform an authenticated (via Basic Auth) POST request and return response as a String. - * @param path path to perform GET against + * @param path path to perform POST against * @param username Username (may be null to perform an unauthenticated POST) * @param password Password - * @param requestEntity unknown -- not used. + * @param requestEntity HttpEntity to specify content/headers to POST * @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/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index 95ec762514..64e3db7dfc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -9,19 +9,36 @@ package org.dspace.app.sword2; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.nio.file.Path; +import java.util.List; import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; +import org.springframework.util.LinkedMultiValueMap; /** * Integration test to verify the /swordv2 endpoint is responding as a valid SWORDv2 endpoint. @@ -41,11 +58,24 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { private ConfigurationService configurationService; // All SWORD v2 paths that we test against - private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; - private final String COLLECTION_PATH = "/swordv2/collection"; - private final String MEDIA_RESOURCE_PATH = "/swordv2/edit-media"; - private final String CONTAINER_PATH = "/swordv2/edit"; - private final String STATEMENT_PATH = "/swordv2/statement"; + private final String SWORD_PATH = "/swordv2"; + private final String SERVICE_DOC_PATH = SWORD_PATH + "/servicedocument"; + private final String COLLECTION_PATH = SWORD_PATH + "/collection"; + private final String MEDIA_RESOURCE_PATH = SWORD_PATH + "/edit-media"; + private final String EDIT_PATH = SWORD_PATH + "/edit"; + private final String STATEMENT_PATH = SWORD_PATH + "/statement"; + + // Content Types used + private final String ATOM_SERVICE_CONTENT_TYPE = "application/atomserv+xml;charset=UTF-8"; + private final String ATOM_FEED_CONTENT_TYPE = "application/atom+xml;type=feed;charset=UTF-8"; + private final String ATOM_ENTRY_CONTENT_TYPE = "application/atom+xml;type=entry;charset=UTF-8"; + + /** + * Create a global temporary upload folder which will be cleaned up automatically by JUnit. + * NOTE: As a ClassRule, this temp folder is shared by ALL tests below. + **/ + @ClassRule + public static final TemporaryFolder uploadTempFolder = new TemporaryFolder(); @Before public void onlyRunIfConfigExists() { @@ -60,7 +90,18 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Ensure SWORDv2 URL configurations are set correctly (based on our integration test server's paths) // SWORDv2 validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("swordv2-server.url", getURL(SWORD_PATH)); configurationService.setProperty("swordv2-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + configurationService.setProperty("swordv2-server.collection.url", getURL(COLLECTION_PATH)); + + // Override default value of SWORD upload directory to point at our JUnit TemporaryFolder (see above). + // This ensures uploaded files are saved in a place where JUnit can clean them up automatically. + configurationService.setProperty("swordv2-server.upload.tempdir", + uploadTempFolder.getRoot().getAbsolutePath()); + + // MUST be set to allow DELETE requests on Items which are in the archive. (This isn't enabled by default) + configurationService.setProperty("plugin.single.org.dspace.sword2.WorkflowManager", + "org.dspace.sword2.WorkflowManagerUnrestricted"); } @Test @@ -68,22 +109,20 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Attempt to GET the ServiceDocument without first authenticating ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test public void serviceDocumentTest() throws Exception { - // Attempt to GET the ServiceDocument as an Admin user. + // Attempt to GET the ServiceDocument as any user account ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, - admin.getEmail(), password); - // Expect a 200 response code, and an ATOM UTF-8 document - assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); - assertThat(response.getHeaders().getContentType().toString(), - equalTo("application/atomserv+xml;charset=UTF-8")); + eperson.getEmail(), password); + // Expect a 200 response code, and an ATOM service document + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_SERVICE_CONTENT_TYPE, response.getHeaders().getContentType().toString()); // Check for correct SWORD version in response body - assertThat(response.getBody(), - containsString("2.0")); + assertThat(response.getBody(), containsString("2.0")); } @Test @@ -91,44 +130,204 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Attempt to POST to /collection endpoint without sending authentication information ResponseEntity response = postResponseAsString(COLLECTION_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } + /** + * In DSpace the /collections/[handle-prefix]/[handle-suffix] endpoint gives a list of all Items + * which were deposited BY THE AUTHENTICATED USER into the given collection + */ @Test - @Ignore public void collectionTest() throws Exception { - // TODO: Actually test collection endpoint via SWORDv2. - // Currently, we are just ensuring the /collection endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create all content as the SAME EPERSON we will use to authenticate on this endpoint. + // THIS IS REQUIRED as the /collections endpoint will only show YOUR ITEM SUBMISSIONS. + context.setCurrentUser(eperson); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .build(); + + // Add one Item into that Collection. + String itemTitle = "Test SWORDv2 Item"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(itemTitle) + .withAuthor("Smith, Sam") + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // This Collection should exist on the /collection endpoint via its handle. + // Authenticate as the same user we used to create the test content above. + ResponseEntity response = getResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password); + + // Expect a 200 response code, and an ATOM feed document + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + // Check for response body to include the Item edit link + // NOTE: This endpoint will only list items which were submitted by the authenticated EPerson. + assertThat(response.getBody(), containsString(EDIT_PATH + "/" + item.getID().toString())); + // Check for response body to include the Item title text + assertThat(response.getBody(), containsString("" + itemTitle + "")); } @Test public void mediaResourceUnauthorizedTest() throws Exception { - // Attempt to POST to /mediaresource endpoint without sending authentication information + // Attempt to POST to /edit-media endpoint without sending authentication information ResponseEntity response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(response.getStatusCode(), HttpStatus.UNAUTHORIZED); + } + + /** + * This tests four different SWORDv2 actions, as these all require starting with a new deposit. + * 1. Depositing a new item via SWORD (via POST /collections/[collection-uuid]) + * 2. Reading the deposited item (via GET /edit/[item-uuid]) + * 3. Updating the deposited item's metadata (via PUT /edit/[item-uuid]) + * 4. Deleting the deposited item (via DELETE /edit/[item-uuid]). + */ + @Test + public void depositAndEditViaSwordTest() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + // Make sure our Collection allows the "eperson" user to submit into it + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .withSubmitterGroup(eperson) + .build(); + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // Add file + LinkedMultiValueMap multipart = new LinkedMultiValueMap<>(); + multipart.add("file", new FileSystemResource(Path.of("src", "test", "resources", + "org", "dspace", "app", "sword2", "example.zip"))); + // Add required headers + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setContentDisposition(ContentDisposition.attachment().filename("example.zip").build()); + headers.set("Packaging", "http://purl.org/net/sword/package/METSDSpaceSIP"); + headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML)); + + //---- + // STEP 1: Verify upload/submit via SWORDv2 works + //---- + // Send POST to upload Zip file via SWORD + ResponseEntity response = postResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password, + new HttpEntity<>(multipart, headers)); + + // Expect a 201 CREATED response with ATOM "entry" content returned + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(ATOM_ENTRY_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // MUST return a "Location" header which is the "/swordv2/edit/[uuid]" URI of the created item + assertNotNull(response.getHeaders().getLocation()); + + String editLink = response.getHeaders().getLocation().toString(); + + // Body should include that link as the rel="edit" URL + assertThat(response.getBody(), containsString("")); + + //---- + // STEP 2: Verify uploaded content can be read via SWORDv2 + //---- + // Edit URI should work when requested by the EPerson who did the deposit + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + RequestEntity request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // Body should include links to bitstreams from the zip. + // This just verifies at least one /swordv2/edit-media/bitstream/* link exists. + assertThat(response.getBody(), containsString(getURL(MEDIA_RESOURCE_PATH + "/bitstream"))); + // Verify Item title also is returned in the body + assertThat(response.getBody(), containsString("Attempts to detect retrotransposition")); + + //---- + // STEP 3: Verify uploaded content can be UPDATED via SWORDv2 (by an Admin ONLY) + //---- + // Edit URI can be used with PUT to update the metadata of the Item. + // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE + // must be done via a user with Admin privileges on the Item. + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + // This example simply changes the title. + String newTitle = "This is a new title updated via PUT"; + String newTitleEntry = "" + newTitle + ""; + request = RequestEntity.put(editLink) + .headers(authHeaders) + .contentType(MediaType.APPLICATION_ATOM_XML) + .body(newTitleEntry); + response = responseAsString(request); + // Expect a 200 OK response + assertEquals(HttpStatus.OK, response.getStatusCode()); + + //---- + // STEP 4: Verify content was successfully updated by reading content again + //---- + // Edit URI should work when requested by the EPerson who did the deposit + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + assertEquals(HttpStatus.OK, response.getStatusCode()); + // Verify the new Item title is now included in the response body + assertThat(response.getBody(), containsString(newTitle)); + + //---- + // STEP 5: Verify uploaded content can be DELETED via SWORDv2 (by an Admin ONLY) + //---- + // Edit URI should also allow user to DELETE the uploaded content + // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE + // must be done via a user with Admin privileges on the Item. + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + request = RequestEntity.delete(editLink) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 204 No Content response + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + + // Verify that Edit URI now returns a 404 (using eperson login info) + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 404 response as content was deleted + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } @Test - @Ignore - public void mediaResourceTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /mediaresource endpoint exists (see above) and isn't throwing a 404 - } - - @Test - public void containerUnauthorizedTest() throws Exception { - // Attempt to POST to /container endpoint without sending authentication information - ResponseEntity response = postResponseAsString(CONTAINER_PATH, null, null, null); + public void editUnauthorizedTest() throws Exception { + // Attempt to POST to /edit endpoint without sending authentication information + ResponseEntity response = postResponseAsString(EDIT_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); - } - - @Test - @Ignore - public void containerTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /container endpoint exists (see above) and isn't throwing a 404 + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test @@ -136,14 +335,64 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // Attempt to GET /statement endpoint without sending authentication information ResponseEntity response = getResponseAsString(STATEMENT_PATH); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } + /** + * Statements exist for Items in DSpace (/statements/[item-uuid]) + * https://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#statement + */ @Test - @Ignore public void statementTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /statement endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create all content as the SAME EPERSON we will use to authenticate on this endpoint. + // THIS IS REQUIRED as the /statements endpoint will only show YOUR ITEM SUBMISSIONS. + context.setCurrentUser(eperson); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .build(); + + // Add one Item into that Collection. + String itemTitle = "Test SWORDv2 Item"; + String itemAuthor = "Smith, Samantha"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(itemTitle) + .withAuthor(itemAuthor) + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + // GET call to /statement MUST include an "Accept" header that matches one of the formats + // supported by 'SwordStatementDisseminator' (configured in swordv2-server.cfg) + RequestEntity request = RequestEntity.get(getURL(STATEMENT_PATH + "/" + item.getID().toString())) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + ResponseEntity response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + + // Body should include the statement path of the Item, as well as the title & author information + assertThat(response.getBody(), + containsString(STATEMENT_PATH + "/" + item.getID().toString())); + assertThat(response.getBody(), + containsString("" + itemTitle + "")); + assertThat(response.getBody(), + containsString("" + itemAuthor + "")); + // Also verify Item is in "archived" state + assertThat(response.getBody(), + containsString("f%iAVQZtR0tNJk@EuW{=)X|)00nvm zegvk2 z{Ku}UO~ugt;_)@Zdw74v3aAkdOp~#sFLtiKQ0E;XCZ>u%In^^VJLcyrfs3O=XEr+3 zBke`FjmIWp!FJs|%}bj~x!yGY92X9bU$yffL!&Df?mZcoyWO&mHuXIt(I$fFuYJCVL{n4TSLqiaNPvO{x z&fB${ti9}8c_R4y^u+4WtgShVAh|-FYIRq+i<1ln%6KQ zu@D%!6d83f;5GVvznbNB8y_b%m#eN)xKi;i$7SQr`vy40{dmffSm4A9I2eA#2tE^>-Vtx1o1g@ z5EbXD(2wU2Z|6k}q1{!#PYsJrJHQj&rymC1)i|tH7HEqrJkZI*CYadVsB+LqYQ;Ro zvdF_`5{u=~jJa#uM^Gb&a{tV%@gNautLFHpM5i{bnj)~h`=wfVl+Kr1r>}#@OARr| zOnnUwJ<(+i>)eA1PhDnO1Zkzz`>S&@rW23O02NmiGSP-p8JPx)#gx6h_{O-K5-P(X z+C|~HI&giYQ~Tjrnikp3!O_mzq+?yiz!V5R4TPq#Gx=gicradIBGw@EyweQA$p&b? zCxe;|?h0M_g#}sB43kNMsklUu^Kavl&-i{Ra*k($P@;~O<=_=*63Y^5q?nsh7MOA< z=P|Z`wV0s!4C7T0ToBQac@Cij8SZ_plSUM}WCRJQxkgw`00D0#e?E223-=tWOept& zGMpaaiD{e-e*@Za3?VT|LN0k&U^2}6lGxGQdJ0SgJnJ^TPFyZ6IDAauJ3Tm^UZG6{g|@awcTfW^*+E z280@I2Gt(e1uT4CM$n;Jg_fr>z%$xFUL*t2kH`y5TiDkrrYy1pB38qO)xBhf=xk4J z_u*j-T;*-Ww%Qc(h4azrZEKD5Na0>!jg)&^0}i2h3I_mr%p_eu^G*S=COEv>EuaSQ)C zjsJrV?rb_@3kM6YxXu4vTgzeAEP;@HK=5Dxk1A~g)bIe?Odgw2w&CB{OuwJ0koDTG zzjBFKcE|)M{GAAd$&I<&{z*JdO|+u3uI6|&-e7;r04nRTYc~BIppKvW39EKoL%G<` zJk_f%!TR+cv$$YpUl~)X0vGaoI5$Ki3}8EFMXL2VIlSJ4nor5|TSUA3hHH90a(#g; z-Rcf-jbLbED0MC%1fcb!8fy5buLsuT3Ef`Re4T5PL@&)PI+fqhmP6DM_{ZVth@60q z2Wix@PcY%JB}i&saM9gMg^uvp36J78AHxErg85x=Z8bX}EEWA>6Y37VHLZugZc=^0 zrNLBBk&#T&>TE%|b&@0!pDcB+$!*>`3{x-LTh3IzeJc0E>GUl1_I2d*5L0-XX)NVW%o)gc{ zbLdlgT!h-s*FiBaVg}FspqkH&O&c`C)xJ1Q8-^y&>LP0VF|@uQKe`b%hafw9!u&*K z@dTk_+=knkt>1rvA^oB`LKkp!+_Y!)Hb_Hmv&D7A`dXkjzF+oVjH}3ONfF9^n|coK zQtGt&j)E{I+nJ(ch1%YII0<3Q1A@#`SNpX*uaBpG->izt!QK6J4>U;H!CmNomIZ^`x(t&!w>j@-bG`0-^IF@_ZmO=X%iSO9 ziPO-~|5%tANfO;pT-aS#T*^oSfe8az^RSRHFckhSOsx%q4m<@-{0Wt%!-c&L7((xY z4+{$WSOE{?<&}7WcMI!Elc1+M`p%Ka7r=C|)nhIsB4MplA@FM4Pzc6gVWvP>D{g|ZMM zIar;UO|Td>ff|?a%r3t(^C2eq#;(nE&3HsjU74lz_YE!K}U5lL~6wC4Ui~Qt{LLHola1X*18@#36`1lEdCGptEbfEPu|#j&G9$ zDxpbFatCN&w=^hB4+=_3Y9|HgO%0%49G9qi=50ul(WBX}2l?rz zM^b(@t144+2XE5QDft8n23 z>)z|%CI#FLyHakRx`jI9hnKz=D}?d~-rUt|71SF0xKFSSjY&oB3%Nk%-yQioZCU6d zZdCR@@B%M?vJO&SgF3hexJqIhq;NCJKT!;gb2?CDH`W{K+LV7!d@1#{?C}*jB^phL z$=TEkvFjw1T(k2T#Q_aA)u-}o5!=>h$Z@KGMeD>=S72Lgc?^3h#9ima>7?^}&X^Yc zxBGdNa?hyw1{{+;*Udb`O~Z7f=f8UzQZ~L3NEMOQb+hghvBXVL+7d+-z`x6<;{)aG z&Oa|V{7i8@yOzN4&Ou5OV)H?`2N??E-Q(46HO)sV327gGLY)Ts-6B4uf;?860E(6G z7^cFEosY1sLX;EGE~>Rd;E#vMQ=+&!@88QJ$M18wFZgp0TU7YUDEJ)k7%v_WDo9D~ zf((fL%st5jb7Qze-j@-lNJC3M2=O}h0@%stxAxCjak+d%K@INM%Dl*O_9i zcKJX?zMMAxmPehZ2VLXZmvl}HFR6f28H;C+;FUrgZ-mRh!_Xm|k!;{s zNwO)-?}%~z>CAIYzRhGsrE}fJL5M1*LEQ{JHgnntD5qPSJ28y^O*@x- z9@Bn|qzS4a?oHeQZS=Wvvukmy>v69hk);Dza+v5KJ-?>to8=;j;=0M9fW6mNhD2!+ zmpdy+<8j<&6Y^L8q3t50MyNc{by#M30!qou6n?lkk$i#nd&% zPr@I{24`Wq?%bjtwpg{e;54cRR3q?gAm;4Vdv$EB^cy{ox=HWhdi2+|Mmj@VzCS#( zKkwfMKD&L2y_caeiKd+#TC;9TAsQso9@nn$(8=UcsX}~!_mS0;eew*6-;s|kq#<`$ETCv7(DW!Aaim2(<@P6JQO?h59ttqb&ny>JVd;Ok-i;pe@D*5s% zM1kI)P8vPw!b?mk0Lv(azf)SE)^BbvV|m@-n?))iUeUII-~RZzZf(^4D$2j`N)XJj z$u?JFvP3-e!Y&=5p5iT9#p;xG@!PfFXBk_+G*7r7^i*S$oI0&+y|)fDb*#<5%oH>) zl6am$`L4`PP|ozdUarsRtFk_F7NUR1*Jiv8-w+tGmBzMjt7-_R3}7t(I>W|eV?~W8Q)9q{Hu-l`-xk-p*sFgBMx_~-av`31@+SkmhQ9_ zeFK&u+f&?MV9J1n->EPXG*wM!by3xB_DgCL=Y^Vzj4(F?g)apmVp7yeWVt@k#;7`f zhYo)e0|}Mx$f!~tvQYsYX3ij}-=JahPgk2*3(00ZIV_q<9!FDK+5OKm21avP!1UlC z2-!p%sh0q-3@%?BN9z1S(mPQ%)wYS)p~61e!?BRPq$k}$w+sz^a?Mvf-NAO($K9Uv zv%JS%GLzMkjqgd(`srDSsPyShR^PZZ(|RjvTeKi=YErysb!`Z$bJSZF$Xy-(15ubw zZrLH4uv?uvs|>r5QAFSfqc5tiVG-V#yz~e39F#hhIn?LfIRUiP+lUfusK8ky@nY1{ z3_l|VWmAo?!zrPr-~r=}?l%fk#$3{5W@)n}&waG{vb23m9P_sGiH&-jh&cj0lB&{eZpg?sP%+0k@zSbJ+RdJD zn^7xewlP`utK+kdk?>0>TFHY>+|j6Yx^bO!fA7&6W=qOa*mw?-2n)Deq3zz%OI1n5 z?LA@?b5rSqa%N6Y zkbbZ-E@=gM;!=gBD|c1lxI8(S4QlsLogCaG$!iApQCO(97% zrUtYYOKVa8^diV@c7yL7iK`RGTI%i}P$d<`+)jh*KY=Z`DD4tO>Ss@1FJC3)Qs`(c zzTj5N(7N?xTr7)R#c5`EQGB~w>&LB?7x?~dmTqgz4FwG&GP(ZwITf8U+3$hwM;0bY zCFRC|?SUV9n!fLw)-~s#N`j$cRSI9BlRGCO=?Dk(7j`B!~?g_I~h<3X79w^m36R5%~l^~OwNj~9@b5bOxe*$tXvg(f5Z3?TrW2PVa~ zGxpZ?+MV9)$8Fw@15&q$Rrq9nWN%ZeS zaA=5!9d$}-$;4TMn$GRbWnG__v%Sz!{k0FecV>A!s=Y{qeF~{tT-#O#3+uQ54eSnT z#3^D2zTPe;{ny3~c>Rja-y&`HGdqOHfBj=i*u0E4`WRHAp0^&vKm4vVB{DR44cVo; zqVS@SpM6|n6mbQ@Z za>SXo7E;3F0Z7)AsEE3)73eQ%hGGZo0v7J+rW>VjIND$6KQ%1?1Af4A_e90tziM7R zz`wNlN}uDdrS4S7EW=Wy)zkMbAC8MR;0P!uO=PamJxS31|I~kkL&b4a9_^J+F@>q?;vBbP!8 zPwy!LXWUNI(~w=g)~6R4S@8Xidchs{?5pmrxlA(j|1=an^_wS;eJhUCQEC#OXS7rF z3$V+Oe{;QESAq)N9BCI&|HHc2KD=J)CjE;)$w57L2j(5={^{*yRjjp0CC2syi@it{ z8wvj2<*5IBLU!E%NB8f6fsSj`1zju&L}&+0hDrTF^WS6hUHIN)*c5{OyPk#5SHYL) zNi1{x=a|pIqy@sI^Vj%dcy4fJOcK^t?1^iykaTbDNd4Hfp0*<$HVV-yMHSZ-5^pPY z%o|TCz6apjny3s-x9a3&!=AFcOVq;d2EhwK9HXVZ)mlq3n=zk7-i1%8rQMta(rXAC z4~Jn}aeFufj2Hb_+@*w6nG&SL7}J|1_VaVP?L`_=RAdBiZCk+yFRHq$2u~b8upGZa^?0vqPs{u};avfEf z1RQv_%q}2oEYPy;yEyS8x)^d7nYauqPgJ8S4UW#kIvYmMYWL^63zZkvc1v7fH`ZcE zn(0{dltgt~H(<*{G>hLY0gNeG`#P_*k}k={`nf*P8Un7TT)WoY%xAr~g(R-(o$rcX zX=Q#7oPpgCUOlmyuUxw0{n}-K+Bw6Tb8lNA&CYsNn35fKZTLC$zBcOH1rG8FBS9Y$!j7yPH3jc*GXLLLNuV z{&g{KaeyX0bhM{UI42c}?00Yu>X7_5Y;d+M`QEwAP}yqsM%8Hql!8Y@F!{;b+@d`S z*VS%0)^A9c8Y|6=Qqjki7@$*7SY!q0jw;rLRC0 z>D|_oy{BjykD!&P?I2gSMJJc$+hDifK(an?30xw^u}+_kiubd*cZO+4G`xvVUcMdS zyZTo0;cw5A%w%U`=}01PGIhm?z2^&iG952#(V`-W*PhG%E!A`EMIrZ9a-cd=SJ z(1SZ(Ka@2GuLKrg0zQZl^T*m{?xoGBymBY77Ho%2^`vG4|`Y z%5d1vTuoCZ0l_cnmll1zFnhanBBE@rtjPbfoosk#-W*7%XR!K}X;)sLbw! zmTg+T+eqw0dIFsQb}Rbd#Nex|$8F3KDsqcQnxjyLqOPN&d3KQd(^4%YBp%bT@Q{00@eZ7+h^Y5BOgrMc2x-m%0}u1 zu~)$eU5(wy&?rVqb{iL`6=1C=3(wO3&k{1bUQUva;)eRV7eOd1$O>N}SCTVsLnWJP zsS^p&Hf(1cGb9%5N8rW*Sq^-n;Gy7DC!(y_SyoiY1C8;ALq#)1SQ< z++kw`dU^pKnTvHWQsAj1t~X_cpTV7$ASRY2;H|^rx?Q}Bij0oerUg@OW;r4$Jp3<$ zkuuGp2sMo!FK)vNZ^P12ID2_H+h6h$oC|)`9A*}R;*knwjFLv5kc4kZ&7I>%6xxly~Dtrh9BaUPlLc0VkF-_kT*Cw{XGcTyMka- zRhPfbrc+;hESUy?K5aV(IAbAY(J>Jl$%wi8k>DIqG1Zz^!$$prBzR@NB_p=Xz=wkRgUoNbY)PGvO=r z{`J_&nNZ@y}@kq9Z> zRWy?ArZJh!!phU{I{UAr;-po5w_pN}+ENHE&+lav-T!N#^Vdbb(*E8T?!$qTPC|K? zMtw8V%eL*4CqH3~eNZ(Zwqg`(OL&@Ms5x7pa79xTe1h=)nn^%W&0dx>S5;|5-5&U< zx1*vXqprC^aQI-T7Kd%NTvyGF=?{AyoyYYVY-rYe8EpE9ep#1W=P5q0P}4mk7JTAMX(CiSodpT;!7}*$NM4;sH{HAwR0mdq0c0 zIN1!hsqk`TcW*E&wOyM0t7akQkEpU}6LAB@Yg!Y*ezv`|gir&bKUx%1Ni{Qvg z(;rp+EJ2}%_VUlq5-er0EEwlMq4c#CX%y>Y%;5@g zQV^whT-7^ZCwG?G{|gx(Ki&>oh=N%=B0A_o3TMK95L+DQtecSWxB&$?l%Vz8A#75h zVvRMFi@^6??_bl161B{~hi^akj-=YsuAx0$%-)?3d}_Y$zKs~?gRCX0++-vfgYaF% zhYVrh;^DLqe%+Y>0!cU7LEkmoaN!$5>+qg=JyF^XgntHwpxhi>%X0?cE^F?NePGqTG&N&`9*_Q6+B#C-tjZof#{S(;-1a#p5iF}>+JmC zyE&9pUhuvq_FD`wz)pJy3GX+n!PbKi6{(G+kbVc`^4@N38AB#Rf2flr8jXT~cz)12 zFj`>G;C#0i3&DiEFB#FvAmnbyLq0elog+<8U802LZptJ*@&qqVx zO@u;+^#u*h*MyhpgX4XI6h&D=1_N^X)1>1cMFR1%yHaX?w-}IERVsJ$-Y`%)1IndIN<7?x;JkEWpTB#`NMxhzcKc&JV{vL?MAID@V3^u zTOmQKO#W?KK(XhyGfD%`%j@vD@=qtJ6JbCsr1`5e1Y_C#5}HilMWr9<9T~ehiWro?ha) z{sluv4Nc)Nlgl&NW}tiv717)izaRwHNEEXbOqMiUsy%lqk=_Zd^<>lc#`p3>muiY$3EVV+3Quj z+AXT=ThF!#ckcPS+mvw6a>XkgmChrIcQ#Vr2^Ns0qan}KS$rw;va+~y9JD&LcwmEM zcPo0}?qMIqX|8e-wOi2GB1hTjW&E+(E7Tp9?**Rai<&x0SwywZ?(( z@DkSey8;xtRhzQTa|+VUZ@EaiFuA!ZjE6vBnD|kZf9Z&FR3o>p-jHz1%x2Jswf(g{+Wc&^#BPmO{3eb?nt=rilrBwGDA zjVlwftNrnGFXTl$$g@Y$!Fjs8AH3%yjDnLmJ)EmyB2y+ko>P)N z46oTV<0eU%s!$$%?7G;#jE=lUkt~QtXzPf6sApXbh6F=si!PtnV@!F#T~0H|K*FMGL?MbRazRg?QBt*@Rl9B&>4b6Y-+ppWq!M*GM@j7(Cj;XA=r#;a6d zrV{=W*}V4y{8V-ZCT13!KC&Bjb!PH4iD6dM1!CuVeTW{SCmBG0Ju~iKnNsVxDe2&1 zAz{r8M=x?-eeeI-8Cq3c>p<5g9}eAl-nN$ro?=q%wz@`uN+}kZ<*d$;7Ao_jJt~eX z7Bke<1xwGTp&fnmHX-il=c-}~UD4T?bXZ#_?l}yF3MU>j(v?KjAZ)OzoI$8UPPCh_ zNP9m}d@*C`Sf0>HXfhf2C8l?V6GwQ^jIyhWMH*CX#neignLjv*d z79=YGaI%xT*5!$K`qgDL(@7DI_+Mko#b_ynngr*t(1{Ut;QvCXv>D;}mQBKQ%f64< zu4)r7llT72+YV+wa2ZWjI31+j!qS+5=#MW|2=eOIEKx%v&;vs8E|Q(JefD;npcF}a z0>t2qh@t$S0MkEi;fU)D?$KH}AFS9Vs+3CrLB@Gdxq)o`BG^bSquPhC^la_5v3aU@ z@(x_AZnPS^Fj-4Z>Adpu0`z>5bpRjJY_GLAON~OI0a|NgYFaG*m=;{lW`CBw+y(H1 zW=Rj*ywhpk!$;5M$bCekN{$yRo$vLEdvv41)w(20Bk#QJMg0UFo3;z(xPVMc-sHw- zagy120UKzY`j*f18UXiQxjxCB#gtW5QbK55!%$Q{lu>-3L&NVW?)6v$Kcn4|coGmw zBTc=SCMy9qS2=%>;`s!%QjZt>W4`|BtJ#*(0F)pJH3(uk8Oi~UBkAGeU~22TK#~?b z#n()7%1CAiFr1=1T-*X|R!u9AJ0*0(ZI#ddW?pVb>g&Q^H2KU084 zH+O)+=N+Df^U(iE$0wNheR>O6%lL(B`YE42Ev}9wQdrxV5?ol&+nrk)8{D{Qr>wQM zggiR^d|vQlZDnmqXLm_!Z(C|jQcd`Xc&bL4Si>KPdD2eL-`x<+%|ikKUI@n01d1U< zqzTAHs)0gvUT9@pK5b@hV+0LI2=ICN2vdUxVs{fG-K)R$gJM8OBrOQ2i>)ZEY>XUc z;2|vt3N9=~4uE?q_cm095oX*|H&5b4nD3t-c!`%IV`Q2BE$^0MY1*zq2|@O_3VkY6 zEZ_|bVkxiR0gFc&S_WEMd~@BV4L^jnpPKp#K9+pF2Kl#dpcXe@bqn_xjqystiZj zEPg1~j6uhHt9J!Y3$D;#YYPR5N`51LJ-wqvuiA;vSL~`0$QQXi7d2t~Oi(VIS|+JV zz=kJBIKi%iZ?$4`2A}f807DQMpPW~LWb&aexbdo%*?OmGWK?Tw$}4YgGR0UkLi?UkOkcRGi;WzgwJJyyKXvx3+)tKK>O1 zQReZ607lX6)*nq>`d-ZegQ~Ex+A}b!pPQP3HZTkX(iq&^CCLB-H2@9-%98Lm#@)jp zI!+8As70%l@kfIjc)@v-?!*%Ojr$nQLK^r%02}+oy-ar>u#d(lZ*p7tfDVJd5YQAf z+!GhZfSB#q`liEZ>Kf|@xb;^|esw>}?kwzVEXwUJ;NN*ecow7-23FoMjQu{$56yuX z82c4=MdY>?&qiZ}ro&!nt#5ug72ZOlDtH)nC0F(p_CWG0!0z28>Eh!T3`qMh@T~nA z@F!t=^oJV9KPO-!{HA~$&@XyR)qqm|>JOcWIVs7Mf|LN_mY)vzmo}Vq1@aJoq z4krT-*2oA<*g`NSg%FYKSzpM#k%d(V1Q;_jQQ+4P2oW8f96bPOPd)}|1k$tc?=2e5 zK>su5ZzNMw;BOdHpUAWOmHJYQ|M*&_hT!kW+Hd9f(AWn;%s0W0-p%SSGR)!h?5BH7 z=6!zK46Q#`c5v>^^s&`zbsKbxQPM zeElo)-?eA&XIs-q`T+iAh0{`Ot$hZJF$&trRI%}Ohl$jTWHG9DRe@rY-tXTGJkreXko1MDrs~Hg`D0sS`VtVwbEjewA(+p;RYprpJ&n}s=gE?NZ={3i zWK@+N)pKysIJLK;1s{FE+?A%V5K-HLv_)b{edI(8p=-EL^?dx<4{N+82 zzJ-vlM;rYHJWhOphBqrw69kbZ6%IiCV-7po8;l!8Egil7&z=4UNcr<7QV)}S>4eQ~ z#rhV_0q=fT_+-hzD;p?Tqc%srl3<+*lK9LqS=DrgX zE`N&i#jh{+@#Kkr^gtbNRYR#`F9Ntx+ce`U9dpXz-=YxLmgTA!LnJQc-B-efoWp;) zi_M!f%=W{F+UJWNOTMX!xLMms9p-r`F1Go z6h5)7Wwqb;G#G33H@pGoYoTiZX&lLw1$A1(3+aw(+fKJ1{usjXU;Oiv>>*nJ&MXO9|I%>**5YP*yFtqM zL1CL{I2^Pp_;{wdI^2`=23!$`2Z<#}Ed}5-ot(s0%3Rn$@72&S@i4SDgzhhiNCJ3u z3AE71dLV1;T{YGZlTa3-b;76a-n@Q7ssbz0B4;NZE_pU~{nXyYa+b?*1y*(0h6Y6Z zPfq(h@x7J+5y5YE;?S{&19gw@n+uOzw!P}(xLb3De``^Xp`-d+|nx4+qhgi9zRR^ z$Tg~O1uyB2eRBrD08_FY1Hh*5;CYyhS?Jta^V{1ylhUJiYE|&p#s67yIg#W;x~;^X zcy&=b-lt)1yMJnhyDJ{Lb)RhOR?pJx(@5Pe;{dNR8eGYA|l`TSBk(o(s}OOd)8{w zaS?x`EN^aGs@sES{*h|{nX1v5O9x7pSilu?9!0(R-%Z4EVDL736sNNrC>UX*(7tnW zYD_glQW^_9$AIJ(P3sneg<*nAAO*l0$N^Rv*<25%t7mrF2*_=w9d%ap1J9p!-Y|)R z4g0pe&Lky5wQ>cPptZuqB79UWxl>!kLor>!ydUCSRmaB<-DMTX0C2UI2sVU6T=RoM z!o;p^(}i4qy;OoxGDA{ez%c!xvP}Bz9CDAE=a_J9EPvv)op+xIU1p!uIOwwD8iEk_ z@;67sT#K$X=ambmtV>Lbeg)3uSvJlYj@5Z4JcW~1tU8O+V>6`V9rN4I%*VqL2$nQN z5sE5Rc8hP$Tmk!_rZe)-&FrcIn-+<>ZJVzs{#KtcD+9RK7WQ0S!-AQO>F~n!sc^f7 z0JT6Km?Lgw%R)_&QF^M+k!vTfpr603<6&}|%{!hExb@$%CpUZM&(r1?zKWB%UD-p4 z^Q_riXM;33&F6fk&(zHBH?$%@9T`NQ2o+y=k#|arp1eVcGU{F!;OOMxTjl0TTb^1C*4Gn8h{a7$pr%BBtsS_WGy;S zFHIRO|Mti8rYWi2?OY=0;SEEc*)l#U+K_F(K*wc|MC(aNg2>~EDk6mf)SCm13{+pE z8RF(F7PkA^>DKj=VLi++KC*i187)dJE!DrZ(A7+Bvx z)7pXw+)?zpd1Q=gSswTjM{GRcP`@Qw2Mrpgv8{yWR5t`T)j^E4IMr!y{a9L!^-H~okD1 z#pah=Cp4HpZ4=|oA1gcQE&u&iYLSaiyqks}pzcFe`HFSnKs7LIp8Dz|ultjAQ5>7K zq<9nevSmCewDqY}9J&xw)e%>Et&K{p?o7=#(=Oa*wJJ)m8Np0X@v~d;5M%kK=iP@9 z*;@{n2X;F6j}K0KX;jr%6uN<`f}XTDrb7>e^N|*fF;G!14YCT=ECWz{$i(b0?oI8x zu?Qm%u_i6M+b?uZBf6rV5`edI=KC=#@_ZN|#+UZFU-B!}JfnF?QMjdnxLM}Do1Xq% zbgU-CONl794^1&e>av)ZwDl~jZ4r#}#3*EhKUO9SZ-B5oQco(xTADsUWV`D^8R|3f zf#F1{LKTsB3D6-yOoo4*@(CNM5Yq|KqKH4o$w|WLiBs>G?UIwe4gxPMbhcN+Tbi{)*6x`&doy$NQ24uX*x3zegsrG+ zPWhgGoFgUFqf3r~nGTbymSSSH0m-PgICAhWL0Ogu@>ilYFx=WU6p^1-QTQWHc5w`N zaDEkPA7m!p=pi2tyUQYGKK9F<4S3~TRh8qWG|AHqxR~<$7g6&ks#o*U$JJg}mOK25 zDaWc7EmM^F8rrke#K>%&-b$Ef)Ta`C?Y5h9c*5R$ z25JmU)=&4O4Cm7D>W*?-v@E8X1Zmsuvb;#@{GNT96s)CfJa;o^x>XYPmfw^m2UqqV zV%i(Z${N6vl(aIRoz8TlBrm#AIWc%HA+3QQO?&YZ(bTCPSxt#L`Ho`6ZKDpe&yf@+ zrW@=&HSlIY%T!<5<|qmek_shz_fp$cAX(8F# zycQWF_SRd3hO*<5lkwaPm1%*DMV?v%jXxRI%)|!Oor_gwyV8y>WAjN*+r49N^&#vP zaEtL;uI|cY73rEZ+*zra&abE~!t$(9Ij=w=VwzIIdV8~fpDG;#--;psNupP5XeqSN z!dYTspkO%v7|}5)!5Yz^Od9=JKi5g@?DwkSrX=bWMteu{&*n01wN_hu>@tc^`HFmSBj6Mu(?ZC3jL|C`ZUt4Ihr* za)LW_Qv~we?KuYRRujly9sV7s3+{g4E(NDjbIDriizk5XW;~aU{l_1~N=`Ri<>C4I zq}AE22TI$2>V+?Q9VNl($g(Ix}z21 zPVUAgdTD5AxKgb35lFWO$U|xJH+J#$MnP)Xt}vvg=ep}6#OzF6wnw}qR3BaGFN9X$ z7|TRq^=6f|5ITn>2Ogg#b*)SYzI!Q;*#b zq30%7H%3YuBe~v5yS?GKrE`NylVq8jJ!@ys!FL^fH8T(VWx@1De-zLqT!ZXjV0n`1 z5I!Ahmtke=^jvik2lr#Dd{HjB8g#7JxNXc+!vn(aD1p^Bb_tE{+YB5mTIrqYOQM^f zp3kWB{giu~1S5e-EcN`!hno|jEkg6@O*N)ojj9;2j+TO2v1hjhlU%+UHJTVsmLeR+*2NMz7x%Ioiojg?%7= zwm0aFj@EKJJ2dycXF$VEv3>u|ZDL~Cu6%84@#sWVNP5|w;6W1Va$M9$5tP+02Y7|= z#jVrf!KGEKkBR-N=k;Sb9=B&*s>YOqC%IKnGryt~A0}Oq{rWO$QvB)5>ak$&^h?vO zy_r{apIO{Y-;UHCQoKqp88{{_0KR4_>8;Y1XVeYy#E8jd=gcJW(+&d1$VV5K6LYRh z6HT`luTv3COQM`VB8ml}1g z)_QuiKRibcAc?*|Pn^i<5dM-^U0D7KnxSrn!$oF(!#&Egmy543`4#f&oz z*!S)9TYwoS9g$+7%pU5Kyqd^0>R`InP?DKMx-VTl8+VEd8&#=c<(Sq@Ptvt*7q|MR=fg5gq) z>7mlC=gOaYFW=@}DO-VU2}wq9KwsUzk#6kxJF4Q#7D9yU>_T6voN}9ItOjZorM#2G zzs%X)UO;VF_9|I;dEjTIJ3wc&DN`igbCs!3xY9vbDhLCln-RRFgDQ>%h3MUpC+5oS9)CEKV#Z2)&pl=l=~Y5gHSH6@;bz!-;aUhGw@SvQS56wo0m}c< zCX*p6Qz>CvdP4DzjEf{&p27aZPjN0Oi?~(nBB*%eD^aWBGz>PcL!PBmv(f(Rk>Lw+ zorPOZYS4hkA-#iwU1+zgT<(r#GUn>kXqHl{wI<7Cq%gNNYg0F0mY&0_ZFws@BP@Fw zOK*;JCZ4N0(x+DX&b-Y&)!a{u$d7>JBcAxfDl5vfTy zwWx6<+eE2VdiF&PBEi3#n@9w^_|oTN4^f6QXqV$Fl@+-TYf#eOKsr?>d>wV^?s$4NQHQ@dzMR4~`&SIGwjF5*^~hi@)EE>46-{FG}J zw=^!C4H($BtRE4wGb>A$y(;%0bGIn!GE1-{QpmQqicvpa$z4~lQLloJCjk8ikV|NV&sy?2LNqx`B;R$lHP{@TL2jqp2+%G{7^s-QxMh zP|4rVDy{cRF>@#y@hzKX?X<8Ep9RQCA1<a>2d`lE5o|Eq#_T}(_=?he)2R1+uH++ zxrOX>!vRs6yrfx?xdN}%3B9AC!Pm!CO$72cMV}27OhmPx6@kv*D3M3ZMVvbqCNZ|r ziwUQIerXL3i3xEp{_IsM3QE=(iH&GpT}>0QjSMdkNLmURUCQm-OLeMha@lS!JmW96 z&Ozoy8d+j>t6&{qzE@)9jlNc@q6SC!P^Lr_arYcuv^Dv{e>j1ZIW{YhztF#nu*46U zqkq5LU-a#lwHp|$4BmW`Dk{tEW{j3}H@rmDh5SmHhxhWm&=znKnkxq7c-t}Kb3m<8 zm6PSqRCHbBKLnc$5>iQW70^#d}wrjr@1%+ zzg(qC-MKNym`@pfXCQ(p>2cP@I<}dQrk1W1Zpw5Om1Z7jci3;r6s_}34%PT1C3bi$ z&s^u5J<|xM<_ft_aDiE|P8-rED>F#rZgZsJ?y{dWg`@Skch#oZ=7;4aZlR^sPV>!7 z1H%J~vmhrq`xDgbmRFQV7s1l4GcTAS{dAW!wC^>|SD7>Tm1N(T?*cy+mz0*Wb{7>X z*&lj!N2R}RS5sTu!fklO;HaB#-ADD5zfQJ0s)f$eBioyH@BO0+oSkPMh^HO_b3M%`(wj4tMW~?+p!GKB*I) z#~V8LwtWJTPIC-(eguKgdW;^e+b5ERtE)jN&snC1X7Tea+N}4(8WK2{Xb&s>o$PO8 z4D11Y+371N4buTVtVH}l>tI<|P;X{# zVTNTR)m-WW(qEf>E>?PKAh}8#{kYRt{Cos>^o@$}YEfl5#^3~}kxis1RNbU)7Z5Hf zYDr7#Cb-mY>OVWEN0^4fKWX-)P+dl?8fCP6kH{$eMEvE`_e{7S$+`~92dV1Ru!8zx zSiZUONS;I)6%g{ZmG}TU`^JF;RC#J`B-Er*jiEnwC4a1BT6aZBG!$y%AEEoiYio^% zjtWU|jnm+M+j&4_gFauxDGr__*Rduod)(et(7lF$YrOGWkSClf3&9OZutmW_bTq)kdX`y z2+ugV^bvX!`H{}^c}qL*9xl5vzLUk{!16Us;V&D`!9@6Mm3uh9NRtN%PrNrkurcr| zZlJK%bY;6*pNX!qsGy)eB!|5c+bG`$$hXc#0~lYfdqx8|3oO_kqj|(fKe)BtYY5#r z(@?ckgjBoN)>cCxF159YCkqB$uBB1K6cBI(jvWNTUxxTP1`p-$s+vv0AqC*^a6g~? z*fW;IjbEw6ju##|pY?EN`_et6rwygW1^R4~nncJWrO%v+hHddPyzA;BX)EJ(*un9sN+NG@ ze#wBuL#~?AztJwC!`XJIB@(4>$F*)j>Mu35C9(L;f7$G35Jt<;F*njKpat}7<6;*R zJl1Ltidq}*_<+etU65)HfsnJrm z);gqmbT^XGhKv*&p=r4`-#ChRKgg}~tX1#zUq}7lvHwQgv6z%_UYuFrCXn6ce)n^2 zCqI94ARRAspg4KLgO@Qwl7R7EBBflW?&0n{s?ZqawV4*A{KLok5QXJeYbb65>d!=d zhI7qM-3RDw(MSweov|se+s5@}wQmT0#GdS((hLt2&-2biM3fup-R2+qZfXcNjm8dUDR!~y1@%f`Y}DK>k9UPh^n zOKGScYL9(H2a2y#rggbwv@Y6 z+-ySeS+`NQ_mug9(2eP^=P~FbefztycbTB=z8jp+hrTELy%iAipB8fw(yyv~@S-EG zS;|kIqY|TcJT&)vsEB8N-gb0wsaJ4v@Wl7p%6;)3gQYE)0MedI49+U>56)Q zYYRz4!f*x*S|NaLG??bx7sL`FpYvhSMCpu}Fr>NV30>P%XJw`LZcpgogYM{LP^hj_p#jf?F^N%`M_LiMLc3LVw}{~&mFsZF@n>~ zoPM0L|DX_b7&~g$`tbk7NlIe-$>(^5h zdPb{y_K+q7aHmSy9q7Zr_*za&OCCNQQssYt^x-~ag@F2aRg@@ZgSp7E$4r_+R>7y`SI(0VJv9zbWp9st=YS19f$H}RjYr0 z*2k=mfDG4BZQS92DO?`$?UcU-kW%wz5kAd;-d( zZLTR-?-MIG)?Ul2EU(lj977sr7(+TKUGwxms@A?x=U})&^grr zSCGIheoLZ3bcuLrszJNK)~HbB%f>d+HR80;D_c}Pqu}k#?x}ur=H^MJi7=JOe)+hV zu6_x)@(zPxOy9KpT{bxXdV^T@gtAA)5`Vd;#z9}1zR+Q8x?WJj!Y*Z6nqNj|exCkU zv|R%;=A{q+8U3Qe9kNc?@KM8QPz&O-NGWvB;+nGiKM>RK~r~~!gJ{zJ3#8VVS(Om z%U>DVv%l^$tlSF~b7*Lcen(N4HcYV%t^Zi)O^m~tN;BT3LJ{uvl=CSL=Q^vTLGRd@ zGS{$j9C5^(6Kr^{+?MDlFb5?_N7V6kwV+La%_2dcb@fp_(^H%qak>QI3_bc7OBqf9 ze9{WTa6Whqy+YXgETw5Ze6tDt&OE$fWksE!G{^Ev-;$h^eaSvbm3MCfj5AlAyt*oG z5PplrcbJw)t=sxR`8#*%vM+l*6BKhIJRivBrdYz`K4lc&eaXuFJSre(Fh_M+zwQ`b zm&5#0!_C-yUOpdI2q#D#PBV&A32B5Yxb+RkE`ISn`Q~A<@TS=@W2yM%H9=PoTZK%+ zOna@O(&zE*0GBdIgD%C=Zsr>8Lb1b|?ZosrxG-dDDQbp&U;P;!_1Z`N-o<9YrLERj z)LwSy@A=4RtXI7{Z%Tz?Sxciy%f|{Qf+x@fgU03aW4HWS)bn}7Y6eoBQ-yO>$X-IF z^>HA5_Gf&8W{vV z-8CP7g-GY@D&Br@hxjj)McPp5xe?-8up3yft$(srcca2MP`X1S3PwnbbB#XDZFUJx zXf?wM}x`DzeyNzKokO;B%O5TcIf+V}}Toz>$4HoODSa6(k?B_WsEHsJ3_i?n7 zW>uzid-)mHaG_r52<420OOVr|NY_6f;+&R zRbcKK2nRDqHBDtL!0)XlYiC+Mz#mx&3516M7YG8O<>CQ>X+c0BKdk_W$B0|r#mpXV zA?|2x52FPFxW%0ptTaSL|D6N$A8S`Ue0=|(RWYNOcH1u6C&34zLB0EC<8|_?;;aV5$Sp_s zJUUQNK1infgXGs1!MWP8fFqpaJ6^HQktOF%d-JmKU1|cVQeJ%GRv8?ZSN(z-n6@P< z=Kz0^;^%s2XA7~WKNhzZ63Ra>8fYwTt@K}@P-8SoKN@ccqZS?U{d5Hm4gL~J@Jsi2-1vgrd zp5}+YcmZSD#7Rs-MH-Qa?qBQG$q*O7V^kknFXzW|ZD5OS7aCg(_5ai%tq-ih^&2Jg zVSVPaoO9-u@?R!G;J-AVThrCt)bcOvvCCr>*e>ne-8Gs|5T_kPHkiUDYWcIIn zFo0VZZs}sZ4j-%KLkMklh$wRw0|`2jjACL7Md^@18y}b8E!3@hl|nQwf>_PYG&5I zzmUHtlkd+Kp9ahs;fl0?In(~}puw#SvxJ-d9{At(!2)1fDBu00%OVl3PWM(c?<;8L z=tKl$!Y@NZi5PsJ@=Enxp!j_>}u7yZMBNdHAcApg4Vbs@Ze z@p$fe5Sf43yjQqCfV@0^;k=-GyMNrn5sp&#&o--+AkS~JP#ysuATK`{%mDZe_$XW7zFu; z91jrmpO^rcAN)^D;2*jI`~v^t~s zz14(!!R|}Nt${$?4-C&=Vr)P00T)X*G9GY5Jy#b1OW8`^#O%?by$?$ z*0+F!NQ!hz3pmWc07FWHbT=}-+N!z zJ^Nn2m3ytV_x!Wy6hy^Yfp0j_>Dsc}8rrJb($GOvY*f|;X6Sr;02!E-3H$>U`y-+R z5I41e!|VX!7Em}$6lQ2`1QQTIw};!opqA*)$#g$?(Y@GkkXtQ9G|*la2e| z_ps;oQAcOI@^EG|+2xRkNYYtwqmL{=0tE$;_NgdwD~(W;*E8zRi2-eWPogpUj3|VZ ztflhQ=w{ZmcCsR93n$gH9H=^@w!p&&`4=Potoyx%kBtJwb7j3K6(vM}b*gH)UZzL~Dq|Yjw1D_e^kFp7t zN1x0PqTKqnReNlvc_5^FzI=;Eb{dXp(U8T9@mVEw^#G$5rru+1C5I@<{wiW!BuImu z{cGF3*)AalW?+#^!i_A!dE?aJ>2>mPoS)8Gnj`;tbilg#?V^mEcFXwUnrW^bHx{!_vcQ#0Im* zgx53aH=ev}PeeV#)>&B&`gDlzG|I`d6D)^EZ(q@RXs|v}t3kpNMo6ZUUN+@^rrzp4 zgqC8%Ti~O7h(`N^=n5B&N{F5BiJAc-mKL?|hb1d#%%XrN4Vd3Dh-Q}6Y4NWryeLO9 zIIrJK?Aa)v5-D|v;JIOk;{m3htfLigybH!uUm)avMyx<~Cv~d2>;8O!9(hh**&oml=PPE8u(7@$(@a0ZEVAH<1ODt;Yv2#d91h}ip~iYG!+D$9L1gF$bf;m zK%>vm!(5ro{tU>7R%@qpHP7f$eWH_7tVhTYanYCUG*Cg|9te6gA>B#~xU8RwLKniO zZkG86I{Djn6tGAueyG*pH-*hOBQN-1OCoBDH;G}bp7C8}ZK%c+lDC#TH{8+w-gvW2 z%`F}qm7{|a=RMQo^@McL*=K+mRVnzx8kxE@T7+2}z5G9lt&1?=6hdw(^_d{J~ z(phy^^5&>Pv|bhwZo(N+NuBlOh5z7MhB#BKU*i9|L%L+v^qDNK1=y_)%MPPvT4%Ai zvi(4>LFe?vc5G#ILoK}KxV65-y9x>s=}~UQ=BVc|$*Rt8Yk-sqwLLTSByLe zJf~%<-vim)2rCaMX~|)X)U%SG8-k+_HYwJlooN6^y_e!mAqVT$E7qG2DG#|gI9SgG zCo4>i`k^U&?4T333jTNOm@4{clp}nnux0{LBprobseP1_DVb7 zIZWsI0sgMW*2cEYV>yrH#jr_)d*D47;i*uN+3O>%!s-0l_ct`UrB;<1?}#(K3d@s#2SsbWw4FzP|aPW@JJ#1HoPde2TH_XrzN zUy3v-+S34~3N0i}v6(|H>7rJ18oi`$;|8M<`G7+bhWejz-41K6cFb2K_4}=j^U@Tw z5zA-pc4WnIKS|fP)|}S(u~On!hm#6*aMcIKSLBjvRCQ3RDT;*#0^`!2+wcwxU)W33 z7|t=@zFO#sI|4QPHYYt$Ja9a`c;NEJd*^SLp|?eV;=kjk+$e+vf*Cj#?a^@P_ zh55ewezh>k_R0cXN2_4;fxSRe@Mu}Xb>?S!oo_CvUN^*Jwhy9mc7jXAtHmMr%wwil zpSd_tYc_>m%Grl3VTS%9t>v{Hk%wSc6T14D^-C-XH41*OMLBavcEZzaZKfsjs&)y! zVTVZXw-g-V+2ts-xTogR1p-%7X|8MncAp4dk#KjfuEiOVpt*Q8rxh%}7}mlacx!gJ zb@R-c9&mItBn_kW*CE+n8?Yn2VyGBA>P7p(a)h}1iZA!=Vz9fh{!^MI$09U)t>?zk zgi>@ytts&bi)k)C#uokgiH%z(iyP8Vew7x2eF=rY(j!hi{zKSzztt^YE+J$mLLJh1 zS)KTcNrU?}{X`32yC|<|5E6{E>*Q5Rx&E8EPx<%wClA=bo$6@8Rz_T4nVfLxTL~*% zEjIQu=>2?1-192^gCv`z^Y0sCTU5kPumZD|KlSba^7C~S%iS~`RM$s7*?rW^wUm;w zRm`Y-TNW>-?4f5R=cr~Ze!U`=%hb6>D`Ft*maO+}{PFrpuMjia!=v$c$IVdCYOD)A zrz6Ha<`aD{Dyp(0oES9K)!ptNgOyFj9Wj!jz+=o5tXL3uU8Ynj_JT&xG`8Ke>?)7K z&)PE9wWFIDr5JYTfkKMInPlrT6$Ke%5tmGxdSN;b;TRs74Be&2TS>~McXHEYlIdii ziuY2W6*-oV3S~w^stJv)lfpVhdHFhu6kq}hHlxf%-L{UdN#Vi+_np^{$LA!IW5{+AnD&7x1v;*kUbOh{h zN`M?2zd$C%HgaY8xdtfvml?D`{IB-pWab#fv2@4TJ;!8r7WCF|mkSEr@d|4WS--cQ zW4-(Oqvl%jc{PtZA6>>_?a5iat%CDajaU^IOQ;^ z4#LyrkAjSRra9~BuZ<3Y8r)4qvINFg+GH2NN8&TTJAjxpkzZh*AYAMc`4U0=XC?=K zeNhO`=7a|ZZM{^CqIh;u;iI;X&$g`NZFIR!$IZMV{L%T9t*kp-+_H9t7SxkE{ z_LDt8%2k_iXn{e6+A8W>p#%x?wM}FI#ViBzW?;x>?I*pT+9U zwd3S6w;6`di2D)>F#Xcsqm`i8Ol`kmBzH1E=V;})Sj*yT-(j?gc^j*j5Aj|lJIsE% z((QQc(p^C;SRkn0JaFl8H)DS`0+EntKtE zYHAA)BPPnBtA&Dp`_gQ_%qSd>XB0_CD=Djw`-5eAgTs6ZG9DU-TAhQ>^_X2NFE5a1bOK}4 z3*2E)`aLaf5DyAn&=ZnEs)3_z>H84x% zK_KH=??OMcKS57LTBPwv=AD`4RV;gMWo2$9XOg9<*l0XWbvi8#*Ql0`MwFt47`k2Q z>mDs7u>yz^{*=YjZHtfRZ?z}m+v2T(N!h(mrJJ0Im!N3v8rB8X-j>+YrO1v?C7)D% zX61}3LHp7~`T zq3)nfxcRcM@Rg_QuK!)}!_COs)v_}e5vQW2ru@9fA9iT`2mI9(twxUfn@$ zw(8N*;=;VW$<3>ZZ+?0l0p~JZo0ZDpgLG@OYwpvR;MHF7e!10w4la@dEx6N6Ib?97 z(P+%%YxbLg-c1M7wa(q7<648pi5Mi~kbxLOHfiDw7G+zdDDPh35HEr1CYmpA^MI-{ zg^~{0W=gZPQkmn)7)|e<0@A8-3Uwv zvIbd=Ls70--izy()wm_D@tPGBt=kIgxMe3d_d3pyId(QFe4)fj#zW ztaO^S%eCv}tL2L)JGxkx;T!Gkrb=6ETlR35ugMtYV&)s%pO}W%J^;~1`jwiPz74TV zzYAGC3-$?E4T|}|W)$PCg4WS#P~@EWaTv<_`t{lf`>9yfN?^L*TPd;NtoHY>VsYDDEhf5$go+KfnBS z*Y3Q5t!}Bg^Yj^1`Ftjs;*b{AUzW%(q^lLiVM+8vdZx{Om#O+>%BvrC8G6rg7wd(c%U-E~OqSH;jav?* z8Cgi43g+jT=_y8XeZf3vo!MI6y7ZY7bFxynN~r>0v^N8f2ldZ7sAXL;AJy3GCI z9jp4X^S+JQTYtQCPFCyl*_FHsD}2twq){uiW!e&xrr;#v7zZbe-il8ZP^YLd;c( zEMv@7A8D(4^sF$uG<9f2WRBz`E4KcTWfYvpHuHTA&3ARXDVCO<+j}1-h2@J!tQScg z`~!@^Inu~tYQV<7t{kV+vD6XT`E!Y*v#VWtQkgUd3UkT{cTEiy{x6CUmb=GELY_f2NdANRtRhVNaP$h=KW z)0$~)MHK<|TIFX+7bXvdZ(TFHHlMWzIlTOfZ#i#H)EkMPV&^H5s26xGkyNn=Iyq|xhYz$?_T@1U zdv+ehXmi`!(JPM#&9Iqvf5SGJ^Sz)frMrtgrdP3MZNB*d@oOp%ug;ldCD-e4>0T7d9gS@-*a84o5s;4n?w5M>X`QeZP1#~bPv*>g@G}Jk@xeN|S{y;?`_P!5 zzuFUzF@d3d&b37HBv2X|EHi47q!?q#oM#44PuaEi-bxPp#%&i-ew5pAvZ9|1n!)Nj z2~wo@C3I2NYhw`*eyz?hDuYvuKmc3eOX5i|UUPwnM6!Ox?J)C|kq0I1XzAZjX=@*` zL{4{gq_d4CCT*{ex@Sm^e-1pd83+mT5Wm~@$P{hyKTj%#ib>GqWz&>%CyKSQ>;#+9 z+qHpNeu>9dI)YLuz*zX?Ox%J=q@#H&V!WE?^AJ)zl}QALy_N$1s=@2ItD~H)UZ8B}>-`HmB%ZwRT#UKGVfWcno6c+6W zwiL#rUCpKbcj07)Q7+S=RI|D2XLZ!Z5}LYP=^MOt^;G3<&O)~erl##3M%fEgpkkF> zp^H7E+5uj3XmO&%O{sGN-0)X;8_VSDWbx^4oYjps>7NO5lkRcG)>5mv5s5jhL%sj0Z{LDlgpfrjHicj zbSZMoo39y9MlJh_932M&KR00=Q1&RIo|cst02gWY%qEyjPTRU0$#!shpQ^l>dETnl zsU6HiCx~J^D0#En_b`nwkw9kvv9-2^XYH*q1Qrl8&=3>Fg?pdYmIG{!`-t)=2qim7rWO6D| z?p)7fzewLBHtc6s`{-cjfg1R@P(~l^F?+x37|XGy#hbh)qTW^ClZl+#whoTMWSSoS@V<&vC%!3lR8eXi+zkhw(5R*o6Ks=!_cnB%i4{P7bS^w_;<{T z>*{xgZ<5}eX2{PDCwo?uHopH*JEi%3ZW?)WPaI;J+?PkQIZb7g+O)|%dUrL|m@LgN zz2zQw?DwJx6&V>Yy=uiz z{&lC_Jd*^7)aHLC;(XrDJ+%2vTp?BE6BPLdGwp?+`QcA}WCLa#WpBc}uSPT#`7!w1 zbuT6IRBKSusLiGd)=Mzb7(ZmpJ(QO3H+zCQJ9`F47j|8yz#C53-E!5J=~Lh}g2`)f zH)_#f*(R?=!p|ZUi$Pl#JbMP^WKj=TiVzsR)uBt4>rPA3|j4Q+y63CKWgr7p8>K9#+RH`0fvDlkNE8xrW zbuR`SjNr1xgIHB!CWz&BRZQ3_#pI>9PE8i>#fKrl&Ahjg?Gtq(@?-8mQF%$SFTR<* z1cCh=bex--6I+lANp z{_rk|3#R8mvijOaWtZi%G=aiTQ>G4Qs>lA6^_|6*sGll8LTA6_N{dn0n-8HnnmE&Q z*DWO-!T91ETIh=~s!?2YWVhuX)D?@9q3LP)K3N+&nRw&mjsc$;$+Fpb=MkED zU&baB!(G=SKcnvLDvpIxKCuv$`31BKrD5~q!HzH^GRBhl-f)qC$W(5#7dK}q zj1Vz@;e5}Ow z7rw7MJ{}%cyeY|(=W*RZVnI+7UU(ue4&OrS;e`XW>(z3zK+zEVL=9JaoL z{qsAtVgV8Qfu6LZ1%IAm!~0WjRwRl7tG!)Dl}M9N^-A_wS^3EjT80;@?o-XIcr$n> z{XH!a9(tCM8h&{+Zfr+N8CRduCo+sWkrR{IjxeG~A|}I9-KEqqnIJjF5TWMTZ6U&1 zo6T2dk=K`0NzC;+6eL|l_Xv*kF-mZ+?b!9G6Tf5E0RFsh>^vlKKR!oA=WYVCtzY87 zYZiB&VGTQcj?3JK!lhy;W!EY3@VQjM6*kmgBdWY>`-d6ZjR=i;^@q6Uy>T5-5mc?j z$;giVff`8rDHMDP-?4RS+}{Pp!VQ!2N+q|AFMWKl8?r2u*rLQ&X1O zZzm4IfvpZ}3gmQCH5s_rUsmwP!E^@wBls}RQ2}jCJ1yMhg%O1-1hXTC^i9ou41SskaRd>u9>QXL>((^nR1! z%5z;_AJR#=Nm+Mlus#y zKTC0af|q>^Q;2v<_qFgW`zv68vYI!Xt25XZ-!{BvfIBRLa`WP-AsDbpdWq%1T$~V| znmjVDIxIM=kyDsIlV-z69c2Sru6-&i`cdg7`Ml!1K#j(XxdO}bTb2CI)o(Z^Oql%E-|Z5Yn6#uKx*%Z#R@RT9Sf^7)RgU2$WtaER>PUz4~DCPCWFOB z;lMHI9^-zE`iIXX1(+R}7wGFswUrGuw|YNl6AHc>j5fA5fg=har5j@;&>fP+#3Qg9 zX5xN#OkrkJB>}T#gd7Z}K0(W+H^A>RJN@ZQh{alenVQ%fFV>C)Q^QSzV~z&nhRuLd ze79}L8^%n4Ip7+9Z;LoV?-06zT?RxSwzc}f9g*buA+IPe%NFwjXN1ph44b-&YzKuU zM$4N5dWkmijkrl9N3i`crpJ$tQ{Kxc^?v(lp{xc;iABdsSEje}Exj|+bGxn?zmAA? zTo%M;;$qob`h92v#R|oWGYUEuHK2@BI*E*iI8ej#*HO}vzCX&MI^E^vWLCqKrI+3Z^Fj4w4xWwUCHyM z$wT@BniDWRIZ5x0INf?Jrl6cUR$ou-fB~3u25JmLiE}@GF>C5>NwN{A0*N^jFu?R8 zS>9w+H9Q4I4g&f1A7mFka@0bIS11?`gxKv~DWB1OHO0~p$y7cIo9dGpFK*qKQ@KO1 zxVO){&;QEMy(kHy)_+AQhu;7*lc70?=JY;)D5&y$Wobh+TRw$l@R4#1kGbjrq}m)f zvgmu#9DaA;B__Y!jG1DwAT-i>xTv)0Zl6~{sead~vQ1QU?Z61jUx27y6Bsn}sJgOq zR?B{IEuH3q` z<9`L81TWpp4_Q)6&l>ucyH-y?N>wb;KCrlLfZI5|t9 zDfZCY3baDqG&dFKyfPh~wGZ80ajYo#=3JIv>lBN@l1%wCJ(V+N7fpkaSfd+Pyec3v zBS}juk%5)Kf6H`4Jb1`G)mYRQy%qkaQ=joZ6``*b$x)IkcUmS8@Q2X&$wa-U(`>@UI#fLI4F*ZvK&} zdj`S%FAq)8)Gnet|~hT9>#^N<+g>3P7X=%V-EAD(8F6fZ(VwWrCi{MW3) zYqE`Pl^peqrlbO4xh^k~S#|lNyjP@Nf4GIhVdqHVIBo-7tl0kdDC^MGP2hcMffXGoO1cnC5 zMXLE=g0%Vi%LRYvvx!P`TeR`K95AZEK`C?*`I!CX{=olQ(p|cJcyg}y{u8*sBhi(t z6YuQXFUb2l=ewqB-?NA@^VVfO(Qm0G$z?X$tOC6?u(b8`b=;6xES>q2_^kW%bwL(4 zqQ+pq!_kqx2@Jy!!3t7z)NNE7Zz=*9M0;vtZ1e*H!@}8%rBXdVu~sF27!m>IEui3i z`!Rj2pds!OPeJpibqW><#sI)-uNH{EAKsxyD$A4LwjTKj_J#Rt=@--($2Q{}OU-W; zXJKCwrsTjX9bELQnB3m08V@+VgQd4vy`BcchhN<23@Owas<_aENQI6Nc&W9JEvXG| z0ENVp#s6ifiSvJkn%F_$e}Y{98f#K)J76!YVra8TMr$e42M~& zQE{{VWnSFW0tTY`J&L6yAn?yUaQ|&I3&g?kzl@XVe(p7EXTuP?xJL(k3;8jSpO3?+ zj8eAc`}OWgmzt7@A!&!^+JQOqC_!BbMEK$zVX*YW6fbE9vn&7GwP*(v=ozWm5>4Mk zY35qzFX#NN$q;lI+Jll}+}H)#Cd&k8=|=F%fIN0*giTlbbCS0^#va!-7`YTxI8FKp zgtbPxU6?Ya1JUS%W(Yz>_SQCKye9JDshrNfB1lpblPKwrNoQC*#GhYphoog4?wVILdZ)_+kUl zLf=Ju6w0!Y1+E++$K?Og5s?2rM#^eg?h9JEf{XKDC<3%F7&yPH-?QnzcifYW{c@*|s_Uugojb<%tjgxOf|;4$iN~it?r5sN%v5ib_auSix4dQF z@$Z3O_~!?|(VsQ>v;6zTdg{O9Z7wMH2mg^blNXf}H!HgkZ(;r6;yd~8%dUkj>QA2? zIEDSoZ5?wCrljQOY(++!86|JrkeKmLwVTV}NE7dP{! z!VR|DF3I2P+ z{$s|}{l^dSvHjgL;y^6$fT-`^T)R@!R2jB7zk?%x{*3-;ggId$sB ztXKZ*f7D!7Zdv)`#sLLGzdSQNjuvMhjxz_pMkH}e7YLZqJ=fWTM?+7i;otslo3dx~ zqb0N0{|758NSm9hTglFyvB%=qf5rfBMkaA)5eCrdm@fUk<~GIhe>bu*FfdAhM0kPX zKsFFBX#}xyQ%j2VDspo|Ls%J@6@G%Q`(|K3HUjgWaEK8g%}W~J01Zam8V)o9bZa;^ yBT!H6L^fifKG+E88J>8IKwWo=Y=lxI*a+w Date: Mon, 19 Feb 2024 21:43:44 +0100 Subject: [PATCH 36/58] [CST-12108] remove gson --- .../correctiontype/ReinstateCorrectionType.java | 14 +++++++++++--- .../correctiontype/WithdrawnCorrectionType.java | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 407d83568c..1a66b57c5f 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -15,7 +15,8 @@ import java.util.Date; import java.util.Objects; import java.util.UUID; -import com.google.gson.Gson; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -82,14 +83,14 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { - CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode reasonJson = createReasonJson(reason); QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, context.getCurrentUser().getID().toString(), targetItem.getID().toString(), targetItem.getName(), this.getTopic(), 1.0, - new Gson().toJson(mesasge), + reasonJson.toString(), new Date() ); @@ -97,6 +98,13 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean return qaEvent; } + private ObjectNode createReasonJson(QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode jsonNode = new ObjectMapper().createObjectNode(); + jsonNode.put("reason", mesasge.getReason()); + return jsonNode; + } + @Override public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { return this.createCorrection(context, targetItem, reason); diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index bb2accc404..7b732c4f33 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -15,7 +15,8 @@ import java.util.Date; import java.util.Objects; import java.util.UUID; -import com.google.gson.Gson; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -84,13 +85,14 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { - CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode reasonJson = createReasonJson(reason); QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, context.getCurrentUser().getID().toString(), targetItem.getID().toString(), targetItem.getName(), - this.getTopic(), 1.0, - new Gson().toJson(mesasge), + this.getTopic(), + 1.0, + reasonJson.toString(), new Date() ); @@ -98,6 +100,13 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean return qaEvent; } + private ObjectNode createReasonJson(QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode jsonNode = new ObjectMapper().createObjectNode(); + jsonNode.put("reason", mesasge.getReason()); + return jsonNode; + } + @Override public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException, SQLException { From fdd99d8844d21a3a7f87e3304d213c725dffada0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 19 Feb 2024 21:45:47 +0100 Subject: [PATCH 37/58] [CST-12108] added missing java doc --- .../impl/CorrectionTypeServiceImpl.java | 18 ++++---- .../action/QAReinstateRequestAction.java | 3 ++ .../action/QAWithdrawnRequestAction.java | 3 ++ .../AdministratorsOnlyQASecurity.java | 2 + .../dspace/qaevent/security/QASecurity.java | 4 ++ .../qaevent/service/QAEventService.java | 45 ++++++++++--------- .../service/dto/CorrectionTypeMessageDTO.java | 4 +- 7 files changed, 48 insertions(+), 31 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java index ef27a819cf..e64120c46a 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java @@ -31,11 +31,10 @@ public class CorrectionTypeServiceImpl implements CorrectionTypeService { @Override public CorrectionType findOne(String id) { - List correctionTypes = findAll(); - return correctionTypes.stream() - .filter(correctionType -> correctionType.getId().equals(id)) - .findFirst() - .orElse(null); + return findAll().stream() + .filter(correctionType -> correctionType.getId().equals(id)) + .findFirst() + .orElse(null); } @Override @@ -56,11 +55,10 @@ public class CorrectionTypeServiceImpl implements CorrectionTypeService { @Override public CorrectionType findByTopic(String topic) { - List correctionTypes = findAll(); - return correctionTypes.stream() - .filter(correctionType -> correctionType.getTopic().equals(topic)) - .findFirst() - .orElse(null); + return findAll().stream() + .filter(correctionType -> correctionType.getTopic().equals(topic)) + .findFirst() + .orElse(null); } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java index 7e416ce022..cd436f3966 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java @@ -20,6 +20,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** + * QAReinstateRequestAction is an implementation of the QualityAssuranceAction interface. + * It is responsible for applying a correction to reinstate a specified item. + * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ public class QAReinstateRequestAction implements QualityAssuranceAction { diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java index 8c9bc01bdc..7048a8285e 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java @@ -20,6 +20,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** + * QAWithdrawnRequestAction is an implementation of the QualityAssuranceAction interface. + * It is responsible for applying a correction to withdraw a specified item. + * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ public class QAWithdrawnRequestAction implements QualityAssuranceAction { diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java index 40a757dc44..38cf40ce39 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java @@ -30,6 +30,7 @@ public class AdministratorsOnlyQASecurity implements QASecurity { return Optional.empty(); } + @Override public boolean canSeeQASource(Context context, EPerson user) { try { return authorizeService.isAdmin(context, user); @@ -38,6 +39,7 @@ public class AdministratorsOnlyQASecurity implements QASecurity { } } + @Override public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { try { return authorizeService.isAdmin(context, user); diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java index 98a1f13cda..68ef106665 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java @@ -14,6 +14,10 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; /** + * The QASecurity interface defines methods for implementing security strategies + * related to Quality Assurance (QA) events. Classes implementing this interface should + * provide logic to filter and determine visibility of QA events based on the user's permissions. + * * @author Andrea Bollini (andrea.bollini at 4science.com) */ public interface QASecurity { diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index c6d8572bec..be7bec55ab 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -27,22 +27,27 @@ public interface QAEventService { /** * Find all the event's topics. * - * @param context the DSpace context - * @param offset the offset to apply - * @return the topics list + * @param context the DSpace context + * @param offset the offset to apply + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the topics list */ - public List findAllTopics(Context context, long offset, long pageSize); + public List findAllTopics(Context context, long offset, long count, String orderField, boolean ascending); /** * Find all the event's topics related to the given source. * - * @param context the DSpace context - * @param source the source to search for - * @param offset the offset to apply - * @param count the page size - * @return the topics list + * @param context the DSpace context + * @param source the source to search for + * @param offset the offset to apply + * @param count the page size + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the topics list */ - public List findAllTopicsBySource(Context context, String source, long offset, long count); + public List findAllTopicsBySource(Context context, String source, long offset, long count, + String orderField, boolean ascending); /** * Count all the event's topics. @@ -64,23 +69,23 @@ public interface QAEventService { * Find all the events by topic. * * @param context the DSpace context - * @param source the source name + * @param sourceName the source name * @param topic the topic to search for * @param offset the offset to apply * @param size the page size * @return the events */ - public List findEventsByTopic(Context context, String source, String topic, long offset, int size); + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size); /** * Find all the events by topic. * - * @param context the DSpace context - * @param source the source name - * @param topic the topic to search for - * @return the events count + * @param context the DSpace context + * @param sourceName the source name + * @param topic the topic to search for + * @return the events count */ - public long countEventsByTopic(Context context, String source, String topic); + public long countEventsByTopic(Context context, String sourceName, String topic); /** * Find an event by the given id. @@ -197,8 +202,8 @@ public interface QAEventService { * @param target the uuid of the QA event's target * @return the events */ - public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset, - int pageSize, UUID target); + public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize); /** * Check if a qaevent with the provided id is visible to the current user according to the source security @@ -255,6 +260,6 @@ public interface QAEventService { * @return the topics list */ public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, - int pageSize); + int pageSize, String orderField, boolean ascending); } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java index 7a39e6e262..e5e38c2396 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java @@ -10,7 +10,9 @@ package org.dspace.qaevent.service.dto; import java.io.Serializable; /** - * Implementation of {@link QAMessageDTO} that model empty message. + * The CorrectionTypeMessageDTO class implements the QAMessageDTO interface + * and represents a Data Transfer Object (DTO) for holding information + * related to a correction type message in the context of Quality Assurance (QA). * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ From 536c930dfca7444bb9243f502ee822a87e442b51 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 19 Feb 2024 21:46:42 +0100 Subject: [PATCH 38/58] [CST-12108] restored sort --- .../service/impl/QAEventServiceImpl.java | 79 ++++++++++--------- dspace/config/dspace.cfg | 4 + 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index a27af164c3..9de598140b 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -132,20 +132,19 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public long countTopicsBySource(Context context, String source) { - if (isNotSupportedSource(source) - || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { + public long countTopicsBySource(Context context, String sourceName) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return 0; } SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, - context.getCurrentUser(), source); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); - solrQuery.addFilterQuery("source:" + source); + solrQuery.addFilterQuery("source:" + sourceName); QueryResponse response; try { response = getSolr().query(solrQuery); @@ -157,16 +156,14 @@ public class QAEventServiceImpl implements QAEventService { @Override public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName,UUID target) { - if (isNotSupportedSource(sourceName) - || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName)) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return null; } SolrQuery solrQuery = new SolrQuery(); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); solrQuery.setRows(0); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, - context.getCurrentUser(), sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); - solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); solrQuery.addFilterQuery(TOPIC + ":\"" + topicName + "\""); if (target != null) { @@ -243,27 +240,29 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findAllTopics(Context context, long offset, long count) { - return findAllTopicsBySource(context, null, offset, count); + public List findAllTopics(Context context, long offset, long count, String orderField, boolean ascending) { + return findAllTopicsBySource(context, null, offset, count, orderField, ascending); } @Override - public List findAllTopicsBySource(Context context, String source, long offset, long count) { + public List findAllTopicsBySource(Context context, String sourceName, long offset, long count, + String orderField, boolean ascending) { var currentUser = context.getCurrentUser(); - if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return List.of(); } - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.setFacetLimit((int) (offset + count)); solrQuery.addFacetField(TOPIC); - if (source != null) { - solrQuery.addFilterQuery(SOURCE + ":" + source); + if (sourceName != null) { + solrQuery.addFilterQuery(SOURCE + ":" + sourceName); } QueryResponse response; List topics = new ArrayList<>(); @@ -277,7 +276,7 @@ public class QAEventServiceImpl implements QAEventService { continue; } QATopic topic = new QATopic(); - topic.setSource(source); + topic.setSource(sourceName); topic.setKey(c.getName()); topic.setTotalEvents(c.getCount()); topic.setLastEvent(new Date()); @@ -320,10 +319,17 @@ public class QAEventServiceImpl implements QAEventService { } } + /** + * Sends an email notification to the system administrator about a new + * Quality Assurance (QA) request event. The email includes details such as the + * topic, target, and message associated with the QA event. + * + * @param qaEvent The Quality Assurance event for which the notification is generated. + */ public void sentEmailToAdminAboutNewRequest(QAEvent qaEvent) { try { Email email = Email.getEmail(I18nUtil.getEmailFilename(Locale.getDefault(), "qaevent_admin_notification")); - email.addRecipient(configurationService.getProperty("mail.admin")); + email.addRecipient(configurationService.getProperty("qaevent.mail.notification")); email.addArgument(qaEvent.getTopic()); email.addArgument(qaEvent.getTarget()); email.addArgument(qaEvent.getMessage()); @@ -335,7 +341,7 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public QAEvent findEventByEventId(Context context, String eventId) { + public QAEvent findEventByEventId(String eventId) { SolrQuery solrQuery = new SolrQuery("*:*"); solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); try { @@ -354,9 +360,9 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findEventsByTopic(Context context, String source, String topic, long offset, int size) { + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size) { EPerson currentUser = context.getCurrentUser(); - if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return List.of(); } @@ -364,11 +370,11 @@ public class QAEventServiceImpl implements QAEventService { solrQuery.setStart(((Long) offset).intValue()); solrQuery.setRows(size); solrQuery.setSort(TRUST, ORDER.desc); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); - solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); try { QueryResponse response = getSolr().query(solrQuery); @@ -388,18 +394,18 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public long countEventsByTopic(Context context, String source, String topic) { + public long countEventsByTopic(Context context, String sourceName, String topic) { EPerson currentUser = context.getCurrentUser(); - if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return 0; } - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery(securityQuery.orElse("*:*")); - solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); try { return getSolr().query(solrQuery).getResults().getNumFound(); @@ -525,19 +531,19 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target) { + public long countEventsByTopicAndTarget(Context context, String sourceName, String topic, UUID target) { var currentUser = context.getCurrentUser(); - if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return 0; } SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); if (target != null) { solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); } - solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); QueryResponse response = null; try { @@ -549,8 +555,8 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, long offset, - int pageSize, UUID target) { + public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize) { var currentUser = context.getCurrentUser(); if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { return List.of(); @@ -649,7 +655,7 @@ public class QAEventServiceImpl implements QAEventService { @Override public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, - int pageSize) { + int pageSize, String orderField, boolean ascending) { var currentUser = context.getCurrentUser(); if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { return List.of(); @@ -657,6 +663,7 @@ public class QAEventServiceImpl implements QAEventService { Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index b475b3f24e..241b294676 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -896,6 +896,10 @@ org.dspace.app.itemexport.max.size = 200 # If you want to give consent to everyone, just configure the uuid of the Anonymous group. withdrawal.reinstate.group = +# Withdrawal&Reinstate email to notify the system administrator about a new +# Quality Assurance (QA) request event. +qaevent.mail.notification = ${mail.admin} + ### Batch Item import settings ### # The directory where the results of imports will be placed (mapfile, upload file) org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports From 2b1e6242ada6890ef6390c5315961982bf8c7f4c Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 19 Feb 2024 22:41:43 +0100 Subject: [PATCH 39/58] [CST-12108] added missing test --- .../service/impl/QAEventServiceImpl.java | 2 +- .../script/OpenaireEventsImportIT.java | 18 ++--- .../rest/CorrectionTypeRestRepositoryIT.java | 69 ++++++++++++++++++- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 9de598140b..d1401b519e 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -341,7 +341,7 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public QAEvent findEventByEventId(String eventId) { + public QAEvent findEventByEventId(Context context, String eventId) { SolrQuery solrQuery = new SolrQuery("*:*"); solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); try { diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index b88f2d1323..e19048ad61 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -67,6 +67,7 @@ import org.junit.Test; */ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase { + private static final String ORDER_FIELD = "topic"; private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class); @@ -168,7 +169,8 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); - List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); @@ -224,7 +226,8 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); @@ -265,7 +268,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20), + assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, ORDER_FIELD, false), contains(QATopicMatcher.with(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; @@ -293,13 +296,12 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20, ORDER_FIELD, false), empty()); verifyNoInteractions(mockBrokerClient); } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBroker() throws Exception { context.turnOffAuthorisationSystem(); @@ -340,7 +342,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE,0,20,ORDER_FIELD,false); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); @@ -395,7 +397,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(context, 0, 20), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20,ORDER_FIELD, false), empty()); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); @@ -445,7 +447,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20); + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE,0,20,ORDER_FIELD,false); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java index a58f2c61d1..8907519687 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java @@ -43,7 +43,7 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio private AuthorizeService authorizeService; @Test - public void findAllTest() throws Exception { + public void findAllAdminTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(get("/api/config/correctiontypes")) .andExpect(status().isOk()) @@ -64,7 +64,34 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio } @Test - public void findOneTest() throws Exception { + public void findAllEPersonTest() throws Exception { + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/config/correctiontypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$._embedded.correctiontypes", containsInAnyOrder( + allOf( + hasJsonPath("$.id", equalTo("request-withdrawn")), + hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), + hasJsonPath("$.creationForm", equalTo("provideReason")), + hasJsonPath("$.type", equalTo("correctiontype")) + ), + allOf( + hasJsonPath("$.id", equalTo("request-reinstate")), + hasJsonPath("$.topic", equalTo("REQUEST/REINSTATE")), + hasJsonPath("$.type", equalTo("correctiontype")) + ) + ))); + } + + @Test + public void findAllUnauthorizedTest() throws Exception { + getClient().perform(get("/api/config/correctiontypes")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneAdminTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(get("/api/config/correctiontypes/request-withdrawn")) .andExpect(status().isOk()) @@ -74,6 +101,23 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } + @Test + public void findOneEPersonTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/config/correctiontypes/request-withdrawn")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); + } + + @Test + public void findOneUnauthorizedTest() throws Exception { + getClient().perform(get("/api/config/correctiontypes/request-withdrawn")) + .andExpect(status().isUnauthorized()); + } + @Test public void findOneNotFoundTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); @@ -240,7 +284,7 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio } @Test - public void findByTopicTest() throws Exception { + public void findByTopicAdminTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(get("/api/config/correctiontypes/search/findByTopic") .param("topic", "REQUEST/WITHDRAWN")) @@ -251,4 +295,23 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } + @Test + public void findByTopicEPersonTest() throws Exception { + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/config/correctiontypes/search/findByTopic") + .param("topic", "REQUEST/WITHDRAWN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) + .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) + .andExpect(jsonPath("$.type", equalTo("correctiontype"))); + } + + @Test + public void findByTopicUnauthorizedTest() throws Exception { + getClient().perform(get("/api/config/correctiontypes/search/findByTopic") + .param("topic", "REQUEST/WITHDRAWN")) + .andExpect(status().isUnauthorized()); + } + } From f11ebf39591755c74ffdc070c64bcfca0cd9a5f4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 19 Feb 2024 22:42:09 +0100 Subject: [PATCH 40/58] [CST-12108] restore sort --- .../model/CorrectionTypeQAEventMessageRest.java | 4 ++++ .../rest/repository/QATopicRestRepository.java | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java index a222eae261..6929f39071 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java @@ -8,6 +8,10 @@ package org.dspace.app.rest.model; /** + * The CorrectionTypeQAEventMessageRest class implements the QAEventMessageRest + * interface and represents a message structure for Quality Assurance (QA) + * events related to correction types. + * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ public class CorrectionTypeQAEventMessageRest implements QAEventMessageRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index 885a3201cc..15d3e5fcdb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -20,6 +20,7 @@ import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -61,8 +62,12 @@ public class QATopicRestRepository extends DSpaceRestRepository findBySource(@Parameter(value = "source", required = true) String source, Pageable pageable) { Context context = obtainContext(); + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + } List topics = qaEventService.findAllTopicsBySource(context, source, - pageable.getOffset(), pageable.getPageSize()); + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); long count = qaEventService.countTopicsBySource(context, source); if (topics == null) { return null; @@ -73,11 +78,14 @@ public class QATopicRestRepository extends DSpaceRestRepository findByTarget(@Parameter(value = "target", required = true) UUID target, - @Parameter(value = "source", required = true) String source, - Pageable pageable) { + @Parameter(value = "source", required = true) String source, Pageable pageable) { Context context = obtainContext(); + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + } List topics = qaEventService.findAllTopicsBySourceAndTarget(context, source, target, - pageable.getOffset(), pageable.getPageSize()); + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); long count = qaEventService.countTopicsBySourceAndTarget(context, source, target); if (topics == null) { return null; From a2ee986f7f83b8129c1e2baa787197aa48828acd Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 20 Feb 2024 13:21:54 +0100 Subject: [PATCH 41/58] [CST-12108] move configurations to qaevents.cfg --- .../qaevent/script/OpenaireEventsImportIT.java | 3 ++- .../app/rest/QASourceRestRepositoryIT.java | 3 ++- .../dspace/app/rest/QATopicRestRepositoryIT.java | 5 +++-- dspace/config/dspace.cfg | 13 ------------- dspace/config/modules/qaevents.cfg | 16 +++++++++++++++- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index e19048ad61..85aceae9af 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -10,6 +10,7 @@ package org.dspace.qaevent.script; import static java.util.List.of; import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -98,7 +99,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase .build(); context.restoreAuthSystemState(); - configurationService.setProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java index 8355a34972..31a2c61806 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static org.dspace.app.rest.matcher.QASourceMatcher.matchQASourceEntry; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -59,7 +60,7 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); - configurationService.setProperty("qaevent.sources", new String[] { "openaire","test-source","test-source-2" }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire","test-source","test-source-2" }); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java index 51cf57ca3b..e6b2663810 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; 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.content; @@ -47,7 +48,7 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findOneTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty("qaevent.sources", new String[] { "openaire", "test-source" }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire", "test-source" }); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -129,7 +130,7 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findBySourceTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty("qaevent.sources", new String[] { "openaire","test-source","test-source-2" }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire","test-source","test-source-2" }); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 241b294676..16107ce116 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -890,16 +890,6 @@ org.dspace.app.itemexport.life.span.hours = 48 # cumulative sizes are more than this entry the export is not kicked off org.dspace.app.itemexport.max.size = 200 -### Withdrawal&Reinstate correction Group ### -# Members of this group enabled to make requests for the Withdrawn or Reinstate of an item. -# By defaul this property is empty, so in the default behaviour no one will see the button to make these requests. -# If you want to give consent to everyone, just configure the uuid of the Anonymous group. -withdrawal.reinstate.group = - -# Withdrawal&Reinstate email to notify the system administrator about a new -# Quality Assurance (QA) request event. -qaevent.mail.notification = ${mail.admin} - ### Batch Item import settings ### # The directory where the results of imports will be placed (mapfile, upload file) org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports @@ -1099,9 +1089,6 @@ webui.preview.brand.fontpoint = 12 # Solr: # ItemCountDAO.class = org.dspace.browse.ItemCountDAOSolr -###### QAEvent source Configuration ###### -qaevent.sources = openaire, DSpaceUsers - ###### Browse Configuration ###### # # Define the DAO class to use this must meet your storage choice for diff --git a/dspace/config/modules/qaevents.cfg b/dspace/config/modules/qaevents.cfg index 6cbaa12084..f117bc807f 100644 --- a/dspace/config/modules/qaevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -31,4 +31,18 @@ qaevents.openaire.pid-href-prefix.pmid = https://pubmed.ncbi.nlm.nih.gov/ qaevents.openaire.pid-href-prefix.ncid = https://ci.nii.ac.jp/ncid/ # The URI used by the OPENAIRE broker client to import QA events -qaevents.openaire.broker-url = http://api.openaire.eu/broker \ No newline at end of file +qaevents.openaire.broker-url = http://api.openaire.eu/broker + +###### QAEvent source Configuration ###### +qaevents.sources = OpenAIRE, DSpaceUsers + +### Withdrawal&Reinstate correction Group ### +# Members of this group enabled to make requests for the Withdrawn or Reinstate of an item. +# By default this property is empty, so only Administrators will see the button to make these requests. +# If you want to allow all authenticated users to have this feature, +# you can configure this setting to use the Anonymous group. +qaevents.withdraw-reinstate.group = + +# Withdrawal&Reinstate email to notify the system administrator about a new +# Quality Assurance (QA) request event. +qaevents.mail.notification = ${mail.admin} From 7079654f8bbf9d4203273b1855848ed01f6482f8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 20 Feb 2024 13:25:56 +0100 Subject: [PATCH 42/58] [CST-12108] fix community feedback --- .../ReinstateCorrectionType.java | 10 +- .../WithdrawnCorrectionType.java | 12 +-- .../service/impl/QAEventServiceImpl.java | 23 ++++- .../repository/QAEventRestRepository.java | 4 + .../app/rest/QAEventRestRepositoryIT.java | 94 ++++++++++++++++++- .../app/rest/matcher/QAEventMatcher.java | 5 +- .../config/emails/qaevent_admin_notification | 2 +- 7 files changed, 130 insertions(+), 20 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 1a66b57c5f..1c6d633aa9 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -12,8 +12,6 @@ import static org.dspace.correctiontype.WithdrawnCorrectionType.WITHDRAWAL_REINS import java.sql.SQLException; import java.util.Date; -import java.util.Objects; -import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -67,12 +65,12 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean } private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { - String withdrawalReinstateGroupUUID = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); - if (StringUtils.isBlank(withdrawalReinstateGroupUUID)) { + String groupName = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(groupName)) { return false; } - Group withdrawalReinstateGroup = groupService.find(context, UUID.fromString(withdrawalReinstateGroupUUID)); - return Objects.nonNull(withdrawalReinstateGroup) && groupService.isMember(context, withdrawalReinstateGroup); + Group withdrawalReinstateGroup = groupService.findByName(context, groupName); + return withdrawalReinstateGroup != null && groupService.isMember(context, withdrawalReinstateGroup); } @Override diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index 7b732c4f33..dd838a88d3 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -12,8 +12,6 @@ import static org.dspace.core.Constants.READ; import java.sql.SQLException; import java.util.Date; -import java.util.Objects; -import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -40,7 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean { - public static final String WITHDRAWAL_REINSTATE_GROUP = "withdrawal.reinstate.group"; + public static final String WITHDRAWAL_REINSTATE_GROUP = "qaevents.withdraw-reinstate.group"; private String id; private String topic; @@ -75,12 +73,12 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean } private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { - String withdrawalReinstateGroupUUID = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); - if (StringUtils.isBlank(withdrawalReinstateGroupUUID)) { + String groupName = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(groupName)) { return false; } - Group withdrawalReinstateGroup = groupService.find(context, UUID.fromString(withdrawalReinstateGroupUUID)); - return Objects.nonNull(withdrawalReinstateGroup) && groupService.isMember(context, withdrawalReinstateGroup); + Group withdrawalReinstateGroup = groupService.findByName(context, groupName); + return withdrawalReinstateGroup != null && groupService.isMember(context, withdrawalReinstateGroup); } @Override diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index d1401b519e..cbb7e6e318 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -23,6 +23,7 @@ import java.util.UUID; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.ArrayUtils; @@ -70,6 +71,8 @@ public class QAEventServiceImpl implements QAEventService { private static final Logger log = LoggerFactory.getLogger(QAEventServiceImpl.class); + public static final String QAEVENTS_SOURCES = "qaevents.sources"; + @Autowired(required = true) protected ConfigurationService configurationService; @@ -328,11 +331,12 @@ public class QAEventServiceImpl implements QAEventService { */ public void sentEmailToAdminAboutNewRequest(QAEvent qaEvent) { try { + String uiUrl = configurationService.getProperty("dspace.ui.url"); Email email = Email.getEmail(I18nUtil.getEmailFilename(Locale.getDefault(), "qaevent_admin_notification")); - email.addRecipient(configurationService.getProperty("qaevent.mail.notification")); + email.addRecipient(configurationService.getProperty("qaevents.mail.notification")); email.addArgument(qaEvent.getTopic()); - email.addArgument(qaEvent.getTarget()); - email.addArgument(qaEvent.getMessage()); + email.addArgument(uiUrl + "/items/" + qaEvent.getTarget()); + email.addArgument(parsJson(qaEvent.getMessage())); email.send(); } catch (Exception e) { log.warn("Error during sending email of Withdrawn/Reinstate request for item with uuid:" @@ -340,6 +344,17 @@ public class QAEventServiceImpl implements QAEventService { } } + private String parsJson(String jsonString) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(jsonString); + return jsonNode.get("reason").asText(); + } catch (Exception e) { + log.warn("Unable to parse the JSON:" + jsonString); + return jsonString; + } + } + @Override public QAEvent findEventByEventId(Context context, String eventId) { SolrQuery solrQuery = new SolrQuery("*:*"); @@ -595,7 +610,7 @@ public class QAEventServiceImpl implements QAEventService { } private String[] getSupportedSources() { - return configurationService.getArrayProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); + return configurationService.getArrayProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index a2b2f144c1..f61f8ac706 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -174,6 +174,10 @@ public class QAEventRestRepository extends DSpaceRestRepository idRef = new AtomicReference(); + + CorrectionTypeMessageDTO message = new CorrectionTypeMessageDTO("reasone"); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + // eperson is not present into the withdraw-reinstate group + // and so cannot make the request + getClient(ePersonToken).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .content(new ObjectMapper().writeValueAsBytes(message)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + String user1Token = getAuthToken(user1.getEmail(), password); + // instead user1 is present into the withdraw-reinstate group + getClient(user1Token).perform(post("/api/integration/qualityassuranceevents") + .param("correctionType", "request-withdrawn") + .param("target", publication.getID().toString()) + .content(new ObjectMapper().writeValueAsBytes(message)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.source", is(DSPACE_USERS_SOURCE))) + .andExpect(jsonPath("$.title", is(publication.getName()))) + .andExpect(jsonPath("$.topic", is("REQUEST/WITHDRAWN"))) + .andExpect(jsonPath("$.trust", is("1.000"))) + .andExpect(jsonPath("$.status", is("PENDING"))); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(true))) + .andExpect(jsonPath("$.withdrawn", is(false))); + + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); + + getClient(adminToken).perform(patch("/api/integration/qualityassuranceevents/" + idRef.get()) + .content(getPatchContent(acceptOp)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/items/" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", is(false))) + .andExpect(jsonPath("$.withdrawn", is(true))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java index b85746c64c..70e5fe6157 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java @@ -13,6 +13,8 @@ import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.is; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -49,10 +51,11 @@ public class QAEventMatcher { public static Matcher matchQAEventEntry(QAEvent event) { try { ObjectMapper jsonMapper = new JsonMapper(); + DecimalFormat decimalFormat = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.ENGLISH)); return allOf(hasJsonPath("$.id", is(event.getEventId())), hasJsonPath("$.originalId", is(event.getOriginalId())), hasJsonPath("$.title", is(event.getTitle())), - hasJsonPath("$.trust", is(new DecimalFormat("0.000").format(event.getTrust()))), + hasJsonPath("$.trust", is(decimalFormat.format(event.getTrust()))), hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())), hasJsonPath("$.message", matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), diff --git a/dspace/config/emails/qaevent_admin_notification b/dspace/config/emails/qaevent_admin_notification index b09ca2f28c..6c75f89a8f 100644 --- a/dspace/config/emails/qaevent_admin_notification +++ b/dspace/config/emails/qaevent_admin_notification @@ -9,5 +9,5 @@ Item Details: Type of request: ${params[0]} -Relatem to item with uuid: ${params[1]} +Relatem to item: ${params[1]} Reason: ${params[2]} \ No newline at end of file From 5826fd5694ff0714321de149098e783388bd3942 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 20 Feb 2024 15:11:37 +0100 Subject: [PATCH 43/58] [CST-12108] minor fix --- dspace-api/src/main/java/org/dspace/content/QAEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java index 8341903ead..d78d140dcf 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -30,7 +30,7 @@ public class QAEvent { public static final String REJECTED = "rejected"; public static final String DISCARDED = "discarded"; - public static final String OPENAIRE_SOURCE = "openaire"; + public static final String OPENAIRE_SOURCE = "OpenAIRE"; public static final String DSPACE_USERS_SOURCE = "DSpaceUsers"; private String source; From dbb74d13b8cd24e690db124fc24b5714328c51b0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 20 Feb 2024 16:09:50 +0100 Subject: [PATCH 44/58] [CST-12108] fix failed tests --- .../dspace/app/rest/QATopicRestRepositoryIT.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java index e6b2663810..bf3f2f02ed 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -48,7 +49,7 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findOneTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire", "test-source" }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { OPENAIRE_SOURCE, "test-source" }); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -79,12 +80,14 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/integration/qualityassurancetopics/openaire:ENRICH!MISSING!PID")) + getClient(adminToken).perform( + get("/api/integration/qualityassurancetopics/" + OPENAIRE_SOURCE + ":ENRICH!MISSING!PID")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry( QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 2))); - getClient(adminToken).perform(get("/api/integration/qualityassurancetopics/openaire:ENRICH!MISSING!ABSTRACT")) + getClient(adminToken).perform(get("/api/integration/qualityassurancetopics/" + + OPENAIRE_SOURCE + ":ENRICH!MISSING!ABSTRACT")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry( QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1))); @@ -130,7 +133,8 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findBySourceTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty(QAEVENTS_SOURCES, new String[] { "openaire","test-source","test-source-2" }); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { + OPENAIRE_SOURCE, "test-source", "test-source-2" }); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -172,7 +176,7 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") - .param("source", "openaire")) + .param("source", OPENAIRE_SOURCE)) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder( @@ -218,7 +222,7 @@ public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource") - .param("source", "openaire")) + .param("source", OPENAIRE_SOURCE)) .andExpect(status().isUnauthorized()); } From 4212ca4e07c373c2472a124ce6a855f5297d8b7d Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 20 Feb 2024 16:41:29 +0100 Subject: [PATCH 45/58] [CST-12108] fix community feedback --- .../org/dspace/qaevent/script/OpenaireEventsImport.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index 404f26bf4c..cbaf808422 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -9,6 +9,7 @@ package org.dspace.qaevent.script; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.substringAfter; +import static org.dspace.core.Constants.ITEM; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -27,6 +28,7 @@ import eu.dnetlib.broker.BrokerClient; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.core.Context; @@ -258,9 +260,10 @@ public class OpenaireEventsImport private String getResourceUUID(Context context, String originalId) throws IllegalStateException, SQLException { String id = getHandleFromOriginalId(originalId); - if (id != null) { - Item item = (Item) handleService.resolveToObject(context, id); - if (item != null) { + if (StringUtils.isNotBlank(id)) { + DSpaceObject dso = handleService.resolveToObject(context, id); + if (dso != null && dso.getType() == ITEM) { + Item item = (Item) dso; final String itemUuid = item.getID().toString(); context.uncacheEntity(item); return itemUuid; From 36bfe5806031e9c875e5969af5df736bcd4cabaa Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Tue, 20 Feb 2024 22:50:04 +0100 Subject: [PATCH 46/58] [CST-12108] remove creationForm attribute as not needed --- .../app/rest/converter/CorrectionTypeConverter.java | 1 - .../org/dspace/app/rest/model/CorrectionTypeRest.java | 9 --------- .../dspace/app/rest/CorrectionTypeRestRepositoryIT.java | 9 --------- dspace/config/spring/api/correction-types.xml | 1 - 4 files changed, 20 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java index aa08d17921..58330fdfae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java @@ -27,7 +27,6 @@ public class CorrectionTypeConverter implements DSpaceConverter { public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String topic; - private String creationForm; public String getTopic() { return topic; @@ -33,14 +32,6 @@ public class CorrectionTypeRest extends BaseObjectRest { this.topic = topic; } - public String getCreationForm() { - return creationForm; - } - - public void setCreationForm(String creationForm) { - this.creationForm = creationForm; - } - @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java index 8907519687..6e720fd63f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionTypeRestRepositoryIT.java @@ -52,7 +52,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio allOf( hasJsonPath("$.id", equalTo("request-withdrawn")), hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), - hasJsonPath("$.creationForm", equalTo("provideReason")), hasJsonPath("$.type", equalTo("correctiontype")) ), allOf( @@ -73,7 +72,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio allOf( hasJsonPath("$.id", equalTo("request-withdrawn")), hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), - hasJsonPath("$.creationForm", equalTo("provideReason")), hasJsonPath("$.type", equalTo("correctiontype")) ), allOf( @@ -97,7 +95,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) - .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } @@ -108,7 +105,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) - .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } @@ -213,7 +209,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio allOf( hasJsonPath("$.id", equalTo("request-withdrawn")), hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), - hasJsonPath("$.creationForm", equalTo("provideReason")), hasJsonPath("$.type", equalTo("correctiontype")) ) ))); @@ -238,7 +233,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio allOf( hasJsonPath("$.id", equalTo("request-withdrawn")), hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), - hasJsonPath("$.creationForm", equalTo("provideReason")), hasJsonPath("$.type", equalTo("correctiontype")) ) ))); @@ -262,7 +256,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio allOf( hasJsonPath("$.id", equalTo("request-withdrawn")), hasJsonPath("$.topic", equalTo("REQUEST/WITHDRAWN")), - hasJsonPath("$.creationForm", equalTo("provideReason")), hasJsonPath("$.type", equalTo("correctiontype")) ) ))); @@ -291,7 +284,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) - .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } @@ -303,7 +295,6 @@ public class CorrectionTypeRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.id", equalTo("request-withdrawn"))) .andExpect(jsonPath("$.topic", equalTo("REQUEST/WITHDRAWN"))) - .andExpect(jsonPath("$.creationForm", equalTo("provideReason"))) .andExpect(jsonPath("$.type", equalTo("correctiontype"))); } diff --git a/dspace/config/spring/api/correction-types.xml b/dspace/config/spring/api/correction-types.xml index 40d9664918..b79e2408d8 100644 --- a/dspace/config/spring/api/correction-types.xml +++ b/dspace/config/spring/api/correction-types.xml @@ -7,7 +7,6 @@ - From b94fc1384da20045115e091d5ad0acb065457306 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 21 Feb 2024 10:27:04 +0100 Subject: [PATCH 47/58] [CST-12108] update javadoc --- dspace-api/src/main/java/org/dspace/qaevent/QASource.java | 6 +++--- dspace-api/src/main/java/org/dspace/qaevent/QATopic.java | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java index 0f0c2e3b35..506f68e9a2 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -19,15 +19,15 @@ import java.util.UUID; public class QASource { /** - * if the QASource stats (see next attributes) are related to a specific target + * The focus attributes specify if the QASource object is describing the status of a specific + * quality assurance source for the whole repository (focus = null) or for a specific + * DSpaceObject (focus = uuid of the DSpaceObject). This would mostly affect the totalEvents attribute below. */ private UUID focus; - private String name; private Date lastEvent; private long totalEvents; - public String getName() { return name; } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java index 03f8333968..2f4a80cecc 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -18,11 +18,13 @@ import java.util.UUID; */ public class QATopic { - private String key; /** - * if the QASource stats (see next attributes) are related to a specific target + * The focus attributes specify if the QATopic object is describing the status of a specific + * quality assurance topic for the whole repository (focus = null) or for a specific + * DSpaceObject (focus = uuid of the DSpaceObject). This would mostly affect the totalEvents attribute below. */ private UUID focus; + private String key; private String source; private Date lastEvent; private long totalEvents; From 14f3c6c57520e711cd66ee32bceb8ac669d4615c Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 21 Feb 2024 12:39:21 +0100 Subject: [PATCH 48/58] [CST-12108] findByTopic should use also target --- .../org/dspace/qaevent/service/QAEventService.java | 4 ++-- .../qaevent/service/impl/QAEventServiceImpl.java | 2 +- .../app/rest/repository/QAEventRestRepository.java | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index be7bec55ab..bdc9e09f0f 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -202,8 +202,8 @@ public interface QAEventService { * @param target the uuid of the QA event's target * @return the events */ - public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, UUID target, - long offset, int pageSize); + public List findEventsByTopicAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize); /** * Check if a qaevent with the provided id is visible to the current user according to the source security diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index cbb7e6e318..d984934fea 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -570,7 +570,7 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findEventsByTopicAndPageAndTarget(Context context, String source, String topic, UUID target, + public List findEventsByTopicAndTarget(Context context, String source, String topic, UUID target, long offset, int pageSize) { var currentUser = context.getCurrentUser(); if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index f61f8ac706..2202e5562b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -93,17 +93,17 @@ public class QAEventRestRepository extends DSpaceRestRepository findByTopic(@Parameter(value = "topic", required = true) String topic, Pageable pageable) { Context context = obtainContext(); - String[] topicIdSplitted = topic.split(":", 2); - if (topicIdSplitted.length != 2) { + String[] topicIdSplitted = topic.split(":", 3); + if (topicIdSplitted.length < 2) { return null; } String sourceName = topicIdSplitted[0]; String topicName = topicIdSplitted[1].replaceAll("!", "/"); - - List qaEvents = qaEventService.findEventsByTopic(context, sourceName, topicName, - pageable.getOffset(), - pageable.getPageSize()); - long count = qaEventService.countEventsByTopic(context, sourceName, topicName); + UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; + List qaEvents = qaEventService.findEventsByTopicAndTarget(context, sourceName, topicName, target, + pageable.getOffset(), + pageable.getPageSize()); + long count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target); return converter.toRestPage(qaEvents, pageable, count, utils.obtainProjection()); } From 0e15589677f55f5d2b60a111e4b15839e1063b5a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 22 Feb 2024 19:01:38 +0100 Subject: [PATCH 49/58] [CST-12108] added new plural metod to CorrectionTypeRest --- .../java/org/dspace/app/rest/model/CorrectionTypeRest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java index eeeb0358b6..6a26313f92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java @@ -20,6 +20,7 @@ public class CorrectionTypeRest extends BaseObjectRest { private static final long serialVersionUID = -8297846719538025938L; public static final String NAME = "correctiontype"; + public static final String PLURAL_NAME = "correctiontypes"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String topic; @@ -43,6 +44,11 @@ public class CorrectionTypeRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; From d2861e1fc269a0ae4a7266c852394b5ba2faaf55 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 22 Feb 2024 21:02:59 +0100 Subject: [PATCH 50/58] [CST-12108] CorrectionType repository should use the plural name --- .../app/rest/repository/CorrectionTypeRestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java index 6e60f3037d..14dc7d9fb2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java @@ -34,7 +34,7 @@ import org.springframework.stereotype.Component; * * @author Mohamed Eskander (mohamed.eskander at 4science.com) */ -@Component(CorrectionTypeRest.CATEGORY + "." + CorrectionTypeRest.NAME) +@Component(CorrectionTypeRest.CATEGORY + "." + CorrectionTypeRest.PLURAL_NAME) public class CorrectionTypeRestRepository extends DSpaceRestRepository { @Autowired From a2daa0f7b874dc56d4c383d0385ab9f20d496fad Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 00:08:46 +0100 Subject: [PATCH 51/58] [CST-121108] restore sorting --- .../java/org/dspace/qaevent/service/QAEventService.java | 5 ++++- .../dspace/qaevent/service/impl/QAEventServiceImpl.java | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index bdc9e09f0f..01ef6c2802 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -73,9 +73,12 @@ public interface QAEventService { * @param topic the topic to search for * @param offset the offset to apply * @param size the page size + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise * @return the events */ - public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size); + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + String orderField, boolean ascending); /** * Find all the events by topic. diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index d984934fea..5ee3642e38 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -40,6 +40,7 @@ import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.common.params.FacetParams; import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; @@ -259,6 +260,7 @@ public class QAEventServiceImpl implements QAEventService { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); @@ -375,7 +377,8 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size) { + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + String orderField, boolean ascending) { EPerson currentUser = context.getCurrentUser(); if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return List.of(); @@ -384,7 +387,7 @@ public class QAEventServiceImpl implements QAEventService { SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(((Long) offset).intValue()); solrQuery.setRows(size); - solrQuery.setSort(TRUST, ORDER.desc); + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); From 506ae83763db9d09ccdf21d6503aeae237b10726 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 00:52:16 +0100 Subject: [PATCH 52/58] [CST-12108] minor fix --- .../org/dspace/qaevent/service/impl/QAEventServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 5ee3642e38..3c79d4daf8 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -377,7 +377,7 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int pageSize, String orderField, boolean ascending) { EPerson currentUser = context.getCurrentUser(); if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { @@ -386,7 +386,9 @@ public class QAEventServiceImpl implements QAEventService { SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(((Long) offset).intValue()); - solrQuery.setRows(size); + if (pageSize != -1) { + solrQuery.setRows(pageSize); + } solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); solrQuery.setQuery(securityQuery.orElse("*:*")); From bae2500d972657c7ca957335f876d8876f619c39 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 01:28:25 +0100 Subject: [PATCH 53/58] [CST-12108] added javaDoc --- .../service/QAEventSecurityService.java | 22 ++++---- .../impl/QAEventSecurityServiceImpl.java | 56 +++++++++++++++++-- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java index d797ad98a4..7f6ef7a12c 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java @@ -23,20 +23,20 @@ public interface QAEventSecurityService { /** * Check if the specified user can see a specific QASource - * @param context the context - * @param user the eperson to consider - * @param qaSource the qaSource involved + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name * @return true if the specified user can eventually see events in the QASource */ - boolean canSeeSource(Context context, EPerson user, String qaSource); + boolean canSeeSource(Context context, EPerson user, String sourceName); /** * Check if the specified user can see a specific QAEvent. It is expected that a QAEvent in a not visible QASource * cannot be accessed. So implementation of this method should enforce this rule. * - * @param context the context - * @param user the eperson to consider - * @param qaEvent the qaevent to check + * @param context the context + * @param user the eperson to consider + * @param qaEvent the qaevent to check * @return true if the specified user can see the specified event */ boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent); @@ -45,11 +45,11 @@ public interface QAEventSecurityService { * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the * specified user * - * @param context the context - * @param user the eperson to consider - * @param qaSource the qaSource involved + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name * @return the solr filter query */ - public Optional generateQAEventFilterQuery(Context context, EPerson user, String qaSource); + public Optional generateQAEventFilterQuery(Context context, EPerson user, String sourceName); } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java index 33b781ad58..ca689a79e0 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java @@ -16,10 +16,23 @@ import org.dspace.eperson.EPerson; import org.dspace.qaevent.security.QASecurity; import org.dspace.qaevent.service.QAEventSecurityService; +/** + * Implementation of the security service for QAEvents. + * This implementation manages a configuration of {@link QASecurity} instances, + * each responsible for security checks for a specific QA source. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ public class QAEventSecurityServiceImpl implements QAEventSecurityService { + /** + * The default security settings to be used when specific configurations are not available for a QA source. + */ private QASecurity defaultSecurity; + /** + * A mapping of QA source names to their corresponding QASecurity configurations. + */ private Map qaSecurityConfiguration; public void setQaSecurityConfiguration(Map qaSecurityConfiguration) { @@ -30,27 +43,58 @@ public class QAEventSecurityServiceImpl implements QAEventSecurityService { this.defaultSecurity = defaultSecurity; } + /** + * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the + * specified user + * + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name + * @return the solr filter query + */ @Override - public Optional generateQAEventFilterQuery(Context context, EPerson user, String qaSource) { - QASecurity qaSecurity = getQASecurity(qaSource); + public Optional generateQAEventFilterQuery(Context context, EPerson user, String sourceName) { + QASecurity qaSecurity = getQASecurity(sourceName); return qaSecurity.generateFilterQuery(context, user); } + /** + * Retrieves the QASecurity configuration for the specified QA source, or uses the default + * configuration if not available. + * + * @param qaSource The name of the QA source. + * @return The QASecurity configuration for the specified QA source, or the default configuration if not available. + */ private QASecurity getQASecurity(String qaSource) { return qaSecurityConfiguration.getOrDefault(qaSource, defaultSecurity); } + /** + * Determines whether the user is authorized to see the specified QA event. + * + * @param context the context + * @param user the eperson to consider + * @param qaEvent the qaevent to check + * @return true if the specified user can see the specified event + */ @Override public boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent) { String source = qaEvent.getSource(); QASecurity qaSecurity = getQASecurity(source); - return qaSecurity.canSeeQASource(context, user) - && qaSecurity.canSeeQAEvent(context, user, qaEvent); + return qaSecurity.canSeeQASource(context, user) && qaSecurity.canSeeQAEvent(context, user, qaEvent); } + /** + * Determines whether the user is authorized to see events from the specified QA source. + * @param context The context. + * @param user The EPerson to consider + * @param sourceName The source name + * + * @return True if the user is authorized to see events from the source, false otherwise. + */ @Override - public boolean canSeeSource(Context context, EPerson user, String qaSource) { - QASecurity qaSecurity = getQASecurity(qaSource); + public boolean canSeeSource(Context context, EPerson user, String sourceName) { + QASecurity qaSecurity = getQASecurity(sourceName); return qaSecurity.canSeeQASource(context, user); } From a139615dcb516ed210d93aab25e35e18e8309228 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 01:37:03 +0100 Subject: [PATCH 54/58] [CST-12108] removed creationForm method --- .../main/java/org/dspace/correctiontype/CorrectionType.java | 2 -- .../org/dspace/correctiontype/ReinstateCorrectionType.java | 5 ----- .../org/dspace/correctiontype/WithdrawnCorrectionType.java | 5 ----- 3 files changed, 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java index 627e00db1c..bc5abaef4e 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java @@ -32,8 +32,6 @@ public interface CorrectionType { */ public String getTopic(); - public String getCreationForm(); - /** * Checks whether the CorrectionType required related item. */ diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java index 1c6d633aa9..847236d110 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java @@ -134,11 +134,6 @@ public class ReinstateCorrectionType implements CorrectionType, InitializingBean @Override public void afterPropertiesSet() throws Exception {} - @Override - public String getCreationForm() { - return this.creationForm; - } - public void setCreationForm(String creationForm) { this.creationForm = creationForm; } diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java index dd838a88d3..edf71ed815 100644 --- a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -142,11 +142,6 @@ public class WithdrawnCorrectionType implements CorrectionType, InitializingBean @Override public void afterPropertiesSet() throws Exception {} - @Override - public String getCreationForm() { - return this.creationForm; - } - public void setCreationForm(String creationForm) { this.creationForm = creationForm; } From a85321039d8d485d86f617ff905e3650106147d0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 01:37:35 +0100 Subject: [PATCH 55/58] [CST-12108] add java doc --- dspace-api/src/main/java/org/dspace/qaevent/QATopic.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java index 2f4a80cecc..a101d9fa2c 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -25,6 +25,9 @@ public class QATopic { */ private UUID focus; private String key; + /** + * The source attributes contains the name of the AQ source like: OpenAIRE, DSpaceUsers + */ private String source; private Date lastEvent; private long totalEvents; From 8b3ad0d6eac50a0516b2471d14f917ee4a558b33 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 02:00:43 +0100 Subject: [PATCH 56/58] [CST-12108] added sorting field in tests --- .../script/OpenaireEventsImportIT.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 85aceae9af..9769f72dcd 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -184,15 +184,16 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0, 20), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0, 20, + ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, "Egypt, crossroad of translations and literary interweavings", projectMessage, "ENRICH/MORE/PROJECT", 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE,"ENRICH/MISSING/ABSTRACT", 0, 20), - contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE,"ENRICH/MISSING/ABSTRACT", 0, 20, + ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); @@ -235,8 +236,8 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), - contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20, + ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); @@ -275,7 +276,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), contains( + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); @@ -356,15 +357,16 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0 ,20), contains( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MORE/PROJECT", 0 , 20, + ORDER_FIELD, false), contains( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, "Egypt, crossroad of translations and literary interweavings", projectMessage, "ENRICH/MORE/PROJECT", 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20), - containsInAnyOrder( + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, "ENRICH/MISSING/ABSTRACT", 0, 20, + ORDER_FIELD, false), containsInAnyOrder( pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d), pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", thirdItem, "Test Publication 2", @@ -456,9 +458,9 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20), hasSize(1)); + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20, ORDER_FIELD, false), hasSize(1)); assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, - QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20), hasSize(2)); + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, false), hasSize(2)); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); From 8e19f81e30062f396b95691fe75fcb91a35ba9f3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 23 Feb 2024 10:43:38 +0100 Subject: [PATCH 57/58] [CST-12108] remove not used param --- .../java/org/dspace/qaevent/service/QAEventService.java | 5 ++--- .../org/dspace/qaevent/service/impl/QAEventServiceImpl.java | 2 +- .../org/dspace/app/rest/QAEventRelatedRestController.java | 4 ++-- .../app/rest/repository/QAEventRelatedLinkRepository.java | 2 +- .../dspace/app/rest/repository/QAEventRestRepository.java | 6 +++--- .../app/rest/repository/QAEventTargetLinkRepository.java | 2 +- .../app/rest/repository/QAEventTopicLinkRepository.java | 2 +- .../rest/security/QAEventRestPermissionEvaluatorPlugin.java | 2 +- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index 01ef6c2802..9f9fd3eaa5 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -92,12 +92,11 @@ public interface QAEventService { /** * Find an event by the given id. - * - * @param context the DSpace context * @param id the id of the event to search for + * * @return the event */ - public QAEvent findEventByEventId(Context context, String id); + public QAEvent findEventByEventId(String id); /** * Store the given event. diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 3c79d4daf8..817171bc26 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -358,7 +358,7 @@ public class QAEventServiceImpl implements QAEventService { } @Override - public QAEvent findEventByEventId(Context context, String eventId) { + public QAEvent findEventByEventId(String eventId) { SolrQuery solrQuery = new SolrQuery("*:*"); solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java index 1aab625578..b3afdf7945 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java @@ -78,7 +78,7 @@ public class QAEventRelatedRestController { @RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainCurrentRequestContext(); - QAEvent qaevent = qaEventService.findEventByEventId(context, qaeventId); + QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); if (qaevent == null) { throw new ResourceNotFoundException("No such qa event: " + qaeventId); } @@ -121,7 +121,7 @@ public class QAEventRelatedRestController { throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainCurrentRequestContext(); - QAEvent qaevent = qaEventService.findEventByEventId(context, qaeventId); + QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); if (qaevent == null) { throw new ResourceNotFoundException("No such qa event: " + qaeventId); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java index 6e867c9277..81f9aaab58 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java @@ -55,7 +55,7 @@ public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository i public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - QAEvent qaEvent = qaEventService.findEventByEventId(context, id); + QAEvent qaEvent = qaEventService.findEventByEventId(id); if (qaEvent == null) { throw new ResourceNotFoundException("No qa event with ID: " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index 27854189c1..7e80fd1c92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -76,7 +76,7 @@ public class QAEventRestRepository extends DSpaceRestRepository Date: Fri, 23 Feb 2024 14:58:52 +0100 Subject: [PATCH 58/58] [CST-12108] fix typing error --- dspace-api/src/main/java/org/dspace/qaevent/QATopic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java index a101d9fa2c..c96847e7a1 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -26,7 +26,7 @@ public class QATopic { private UUID focus; private String key; /** - * The source attributes contains the name of the AQ source like: OpenAIRE, DSpaceUsers + * The source attributes contains the name of the QA source like: OpenAIRE, DSpaceUsers */ private String source; private Date lastEvent;